17170: Test OpenSSH client -> shell gateway -> docker exec.
authorTom Clegg <tom@curii.com>
Tue, 26 Jan 2021 21:36:25 +0000 (16:36 -0500)
committerTom Clegg <tom@curii.com>
Tue, 26 Jan 2021 21:36:25 +0000 (16:36 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

cmd/arvados-client/container_gateway_test.go
lib/crunchrun/container_gateway.go

index deff7b1703c64a384c1a63497c69d42714a22301..39d6e910b8a6f7e5bea21cbeedb1c0dd236325d0 100644 (file)
@@ -6,9 +6,17 @@ package main
 
 import (
        "bytes"
+       "context"
+       "crypto/hmac"
+       "crypto/sha256"
+       "fmt"
+       "net/url"
        "os"
        "os/exec"
 
+       "git.arvados.org/arvados.git/lib/controller/rpc"
+       "git.arvados.org/arvados.git/lib/crunchrun"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
@@ -24,3 +32,51 @@ func (s *ClientSuite) TestShellGatewayNotAvailable(c *check.C) {
        c.Log(stderr.String())
        c.Check(stderr.String(), check.Matches, `(?ms).*gateway is not available, container is queued.*`)
 }
+
+func (s *ClientSuite) TestShellGateway(c *check.C) {
+       defer func() {
+               c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil)
+       }()
+       uuid := arvadostest.QueuedContainerUUID
+       h := hmac.New(sha256.New, []byte(arvadostest.SystemRootToken))
+       fmt.Fprint(h, uuid)
+       authSecret := fmt.Sprintf("%x", h.Sum(nil))
+       dcid := "theperthcountyconspiracy"
+       gw := crunchrun.Gateway{
+               DockerContainerID: &dcid,
+               ContainerUUID:     uuid,
+               Address:           "0.0.0.0:0",
+               AuthSecret:        authSecret,
+       }
+       err := gw.Start()
+       c.Assert(err, check.IsNil)
+
+       rpcconn := rpc.NewConn("",
+               &url.URL{
+                       Scheme: "https",
+                       Host:   os.Getenv("ARVADOS_API_HOST"),
+               },
+               true,
+               func(context.Context) ([]string, error) {
+                       return []string{arvadostest.SystemRootToken}, nil
+               })
+       _, err = rpcconn.ContainerUpdate(context.TODO(), arvados.UpdateOptions{UUID: uuid, Attrs: map[string]interface{}{
+               "state": arvados.ContainerStateLocked,
+       }})
+       c.Assert(err, check.IsNil)
+       _, err = rpcconn.ContainerUpdate(context.TODO(), arvados.UpdateOptions{UUID: uuid, Attrs: map[string]interface{}{
+               "state":           arvados.ContainerStateRunning,
+               "gateway_address": gw.Address,
+       }})
+       c.Assert(err, check.IsNil)
+
+       var stdout, stderr bytes.Buffer
+       cmd := exec.Command("go", "run", ".", "shell", uuid, "-o", "controlpath=none", "-o", "userknownhostsfile="+c.MkDir()+"/known_hosts", "echo", "ok")
+       cmd.Env = append(cmd.Env, os.Environ()...)
+       cmd.Env = append(cmd.Env, "ARVADOS_API_TOKEN="+arvadostest.ActiveTokenV2)
+       cmd.Stdout = &stdout
+       cmd.Stderr = &stderr
+       c.Check(cmd.Run(), check.NotNil)
+       c.Log(stderr.String())
+       c.Check(stderr.String(), check.Matches, `(?ms).*(No such container: theperthcountyconspiracy|exec: \"docker\": executable file not found in \$PATH).*`)
+}
index d234e934158dff30f5a07865374c6bf0c91c4f93..1116c4bb1285d11ae39b4e194f21df6fd01c5d24 100644 (file)
@@ -259,15 +259,18 @@ func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
                                                }
                                                cmd.Env = append(os.Environ(), termEnv...)
                                                err := cmd.Run()
-                                               errClose := ch.CloseWrite()
                                                var resp struct {
                                                        Status uint32
                                                }
-                                               if err, ok := err.(*exec.ExitError); ok {
-                                                       if status, ok := err.Sys().(syscall.WaitStatus); ok {
+                                               if exiterr, ok := err.(*exec.ExitError); ok {
+                                                       if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
                                                                resp.Status = uint32(status.ExitStatus())
                                                        }
+                                               } else if err != nil {
+                                                       // Propagate errors like `exec: "docker": executable file not found in $PATH`
+                                                       fmt.Fprintln(ch.Stderr(), err)
                                                }
+                                               errClose := ch.CloseWrite()
                                                if resp.Status == 0 && (err != nil || errClose != nil) {
                                                        resp.Status = 1
                                                }