1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
24 "git.arvados.org/arvados.git/lib/controller/rpc"
25 "git.arvados.org/arvados.git/lib/crunchrun"
26 "git.arvados.org/arvados.git/sdk/go/arvados"
27 "git.arvados.org/arvados.git/sdk/go/arvadostest"
28 "git.arvados.org/arvados.git/sdk/go/ctxlog"
29 "git.arvados.org/arvados.git/sdk/go/httpserver"
30 check "gopkg.in/check.v1"
33 func (s *ClientSuite) TestShellGatewayNotAvailable(c *check.C) {
34 var stdout, stderr bytes.Buffer
35 cmd := exec.Command("go", "run", ".", "shell", arvadostest.QueuedContainerUUID, "-o", "controlpath=none", "echo", "ok")
36 cmd.Env = append(cmd.Env, os.Environ()...)
37 cmd.Env = append(cmd.Env, "ARVADOS_API_TOKEN="+arvadostest.ActiveTokenV2)
40 c.Check(cmd.Run(), check.NotNil)
41 c.Log(stderr.String())
42 c.Check(stderr.String(), check.Matches, `(?ms).*container is not running yet \(state is "Queued"\).*`)
45 func (s *ClientSuite) TestShellGateway(c *check.C) {
47 c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil)
49 uuid := arvadostest.QueuedContainerUUID
50 h := hmac.New(sha256.New, []byte(arvadostest.SystemRootToken))
52 authSecret := fmt.Sprintf("%x", h.Sum(nil))
53 gw := crunchrun.Gateway{
56 AuthSecret: authSecret,
57 Log: ctxlog.TestLogger(c),
58 // Just forward connections to localhost instead of a
59 // container, so we can test without running a
61 Target: crunchrun.GatewayTargetStub{},
64 c.Assert(err, check.IsNil)
66 rpcconn := rpc.NewConn("",
69 Host: os.Getenv("ARVADOS_API_HOST"),
72 func(context.Context) ([]string, error) {
73 return []string{arvadostest.SystemRootToken}, nil
75 _, err = rpcconn.ContainerUpdate(context.TODO(), arvados.UpdateOptions{UUID: uuid, Attrs: map[string]interface{}{
76 "state": arvados.ContainerStateLocked,
78 c.Assert(err, check.IsNil)
79 _, err = rpcconn.ContainerUpdate(context.TODO(), arvados.UpdateOptions{UUID: uuid, Attrs: map[string]interface{}{
80 "state": arvados.ContainerStateRunning,
81 "gateway_address": gw.Address,
83 c.Assert(err, check.IsNil)
85 var stdout, stderr bytes.Buffer
86 cmd := exec.Command("go", "run", ".", "shell", uuid, "-o", "controlpath=none", "-o", "userknownhostsfile="+c.MkDir()+"/known_hosts", "echo", "ok")
87 cmd.Env = append(cmd.Env, os.Environ()...)
88 cmd.Env = append(cmd.Env, "ARVADOS_API_TOKEN="+arvadostest.ActiveTokenV2)
91 stdin, err := cmd.StdinPipe()
92 c.Assert(err, check.IsNil)
93 go fmt.Fprintln(stdin, "data appears on stdin, but stdin does not close; cmd should exit anyway, not hang")
94 time.AfterFunc(5*time.Second, func() {
95 c.Errorf("timed out -- remote end is probably hung waiting for us to close stdin")
98 c.Check(cmd.Run(), check.IsNil)
99 c.Check(stdout.String(), check.Equals, "ok\n")
101 // Set up an http server, and try using "arvados-client shell"
102 // to forward traffic to it.
103 httpTarget := &httpserver.Server{}
104 httpTarget.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105 c.Logf("httpTarget.Handler: incoming request: %s %s", r.Method, r.URL)
106 if r.URL.Path == "/foo" {
107 fmt.Fprintln(w, "bar baz")
109 w.WriteHeader(http.StatusNotFound)
112 err = httpTarget.Start()
113 c.Assert(err, check.IsNil)
115 ln, err := net.Listen("tcp", ":0")
116 c.Assert(err, check.IsNil)
117 _, forwardedPort, _ := net.SplitHostPort(ln.Addr().String())
122 ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
124 cmd = exec.CommandContext(ctx,
125 "go", "run", ".", "shell", uuid,
126 "-L", forwardedPort+":"+httpTarget.Addr,
127 "-o", "controlpath=none",
128 "-o", "userknownhostsfile="+c.MkDir()+"/known_hosts",
131 c.Logf("cmd.Args: %s", cmd.Args)
132 cmd.Env = append(cmd.Env, os.Environ()...)
133 cmd.Env = append(cmd.Env, "ARVADOS_API_TOKEN="+arvadostest.ActiveTokenV2)
138 forwardedURL := fmt.Sprintf("http://localhost:%s/foo", forwardedPort)
140 for range time.NewTicker(time.Second / 20).C {
141 resp, err := http.Get(forwardedURL)
143 if !strings.Contains(err.Error(), "connect") {
145 } else if ctx.Err() != nil {
146 if cmd.Process.Signal(syscall.Signal(0)) != nil {
147 c.Error("OpenSSH exited")
149 c.Errorf("timed out trying to connect: %s", err)
151 c.Logf("OpenSSH stdout:\n%s", stdout.String())
152 c.Logf("OpenSSH stderr:\n%s", stderr.String())
155 // Retry until OpenSSH starts listening
158 c.Check(resp.StatusCode, check.Equals, http.StatusOK)
159 body, err := ioutil.ReadAll(resp.Body)
160 c.Check(err, check.IsNil)
161 c.Check(string(body), check.Equals, "bar baz\n")
165 var wg sync.WaitGroup
166 for i := 0; i < 10; i++ {
170 resp, err := http.Get(forwardedURL)
171 if !c.Check(err, check.IsNil) {
174 body, err := ioutil.ReadAll(resp.Body)
175 c.Check(err, check.IsNil)
176 c.Check(string(body), check.Equals, "bar baz\n")