18790: Merge branch 'main' into 18790-log-client
[arvados.git] / lib / controller / localdb / container_gateway_test.go
index cc0011864e16939fc21605a763d561d71739bf7c..dc8a8cea0db514d08660aaa19d7522b2f7dd81ee 100644 (file)
@@ -29,6 +29,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvadosclient"
        "git.arvados.org/arvados.git/sdk/go/arvadostest"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
        "git.arvados.org/arvados.git/sdk/go/keepclient"
        "golang.org/x/crypto/ssh"
        check "gopkg.in/check.v1"
@@ -38,6 +39,7 @@ var _ = check.Suite(&ContainerGatewaySuite{})
 
 type ContainerGatewaySuite struct {
        localdbSuite
+       reqUUID string
        ctrUUID string
        srv     *httptest.Server
        gw      *crunchrun.Gateway
@@ -46,14 +48,36 @@ type ContainerGatewaySuite struct {
 func (s *ContainerGatewaySuite) SetUpTest(c *check.C) {
        s.localdbSuite.SetUpTest(c)
 
-       s.ctrUUID = arvadostest.QueuedContainerUUID
+       cr, err := s.localdb.ContainerRequestCreate(s.userctx, arvados.CreateOptions{
+               Attrs: map[string]interface{}{
+                       "command":             []string{"echo", time.Now().Format(time.RFC3339Nano)},
+                       "container_count_max": 1,
+                       "container_image":     "arvados/apitestfixture:latest",
+                       "cwd":                 "/tmp",
+                       "environment":         map[string]string{},
+                       "output_path":         "/out",
+                       "priority":            1,
+                       "state":               arvados.ContainerRequestStateCommitted,
+                       "mounts": map[string]interface{}{
+                               "/out": map[string]interface{}{
+                                       "kind":     "tmp",
+                                       "capacity": 1000000,
+                               },
+                       },
+                       "runtime_constraints": map[string]interface{}{
+                               "vcpus": 1,
+                               "ram":   2,
+                       }}})
+       c.Assert(err, check.IsNil)
+       s.reqUUID = cr.UUID
+       s.ctrUUID = cr.ContainerUUID
 
        h := hmac.New(sha256.New, []byte(s.cluster.SystemRootToken))
        fmt.Fprint(h, s.ctrUUID)
        authKey := fmt.Sprintf("%x", h.Sum(nil))
 
        rtr := router.New(s.localdb, router.Config{})
-       s.srv = httptest.NewUnstartedServer(rtr)
+       s.srv = httptest.NewUnstartedServer(httpserver.AddRequestIDs(httpserver.LogRequests(rtr)))
        s.srv.StartTLS()
        // the test setup doesn't use lib/service so
        // service.URLFromContext() returns nothing -- instead, this
@@ -74,15 +98,14 @@ func (s *ContainerGatewaySuite) SetUpTest(c *check.C) {
                ArvadosClient: ac,
        }
        c.Assert(s.gw.Start(), check.IsNil)
+
        rootctx := ctrlctx.NewWithToken(s.ctx, s.cluster, s.cluster.SystemRootToken)
-       // OK if this line fails (because state is already Running
-       // from a previous test case) as long as the following line
-       // succeeds:
-       s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
+       _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
                UUID: s.ctrUUID,
                Attrs: map[string]interface{}{
                        "state": arvados.ContainerStateLocked}})
-       _, err := s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
+       c.Assert(err, check.IsNil)
+       _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
                UUID: s.ctrUUID,
                Attrs: map[string]interface{}{
                        "state":           arvados.ContainerStateRunning,
@@ -201,7 +224,11 @@ func (s *ContainerGatewaySuite) TestDirectTCP(c *check.C) {
        }
 }
 
-func (s *ContainerGatewaySuite) setupLogCollection(c *check.C, files map[string]string) {
+func (s *ContainerGatewaySuite) setupLogCollection(c *check.C) {
+       files := map[string]string{
+               "stderr.txt":   "hello world\n",
+               "a/b/c/d.html": "<html></html>\n",
+       }
        client := arvados.NewClientFromEnv()
        ac, err := arvadosclient.New(client)
        c.Assert(err, check.IsNil)
@@ -226,14 +253,40 @@ func (s *ContainerGatewaySuite) setupLogCollection(c *check.C, files map[string]
        s.gw.LogCollection = cfs
 }
 
-func (s *ContainerGatewaySuite) TestContainerLogViaTunnel(c *check.C) {
+func (s *ContainerGatewaySuite) saveLogAndCloseGateway(c *check.C) {
+       rootctx := ctrlctx.NewWithToken(s.ctx, s.cluster, s.cluster.SystemRootToken)
+       txt, err := s.gw.LogCollection.MarshalManifest(".")
+       c.Assert(err, check.IsNil)
+       coll, err := s.localdb.CollectionCreate(rootctx, arvados.CreateOptions{
+               Attrs: map[string]interface{}{
+                       "manifest_text": txt,
+               }})
+       c.Assert(err, check.IsNil)
+       _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
+               UUID: s.ctrUUID,
+               Attrs: map[string]interface{}{
+                       "state":     arvados.ContainerStateComplete,
+                       "exit_code": 0,
+                       "log":       coll.PortableDataHash,
+               }})
+       c.Assert(err, check.IsNil)
+       updatedReq, err := s.localdb.ContainerRequestGet(rootctx, arvados.GetOptions{UUID: s.reqUUID})
+       c.Assert(err, check.IsNil)
+       c.Logf("container request log UUID is %s", updatedReq.LogUUID)
+       crLog, err := s.localdb.CollectionGet(rootctx, arvados.GetOptions{UUID: updatedReq.LogUUID, Select: []string{"manifest_text"}})
+       c.Assert(err, check.IsNil)
+       c.Logf("collection log manifest:\n%s", crLog.ManifestText)
+       // Ensure localdb can't circumvent the keep-web proxy test by
+       // getting content from the container gateway.
+       s.gw.LogCollection = nil
+}
+
+func (s *ContainerGatewaySuite) TestContainerRequestLogViaTunnel(c *check.C) {
        forceProxyForTest = true
        defer func() { forceProxyForTest = false }()
 
        s.gw = s.setupGatewayWithTunnel(c)
-       s.setupLogCollection(c, map[string]string{
-               "stderr.txt": "hello world\n",
-       })
+       s.setupLogCollection(c)
 
        for _, broken := range []bool{false, true} {
                c.Logf("broken=%v", broken)
@@ -245,9 +298,9 @@ func (s *ContainerGatewaySuite) TestContainerLogViaTunnel(c *check.C) {
                        defer delete(s.cluster.Services.Controller.InternalURLs, *forceInternalURLForTest)
                }
 
-               handler, err := s.localdb.ContainerLog(s.userctx, arvados.ContainerLogOptions{
-                       UUID:          s.ctrUUID,
-                       WebDAVOptions: arvados.WebDAVOptions{Path: "/stderr.txt"},
+               handler, err := s.localdb.ContainerRequestLog(s.userctx, arvados.ContainerLogOptions{
+                       UUID:          s.reqUUID,
+                       WebDAVOptions: arvados.WebDAVOptions{Path: "/" + s.ctrUUID + "/stderr.txt"},
                })
                if broken {
                        c.Check(err, check.ErrorMatches, `.*tunnel endpoint is invalid.*`)
@@ -255,7 +308,7 @@ func (s *ContainerGatewaySuite) TestContainerLogViaTunnel(c *check.C) {
                }
                c.Check(err, check.IsNil)
                c.Assert(handler, check.NotNil)
-               r, err := http.NewRequestWithContext(s.userctx, "GET", "https://controller.example/arvados/v1/containers/"+s.ctrUUID+"/log/stderr.txt", nil)
+               r, err := http.NewRequestWithContext(s.userctx, "GET", "https://controller.example/arvados/v1/container_requests/"+s.reqUUID+"/log/"+s.ctrUUID+"/stderr.txt", nil)
                c.Assert(err, check.IsNil)
                r.Header.Set("Authorization", "Bearer "+arvadostest.ActiveTokenV2)
                rec := httptest.NewRecorder()
@@ -268,41 +321,18 @@ func (s *ContainerGatewaySuite) TestContainerLogViaTunnel(c *check.C) {
        }
 }
 
-func (s *ContainerGatewaySuite) TestContainerLogViaGateway(c *check.C) {
-       s.testContainerLog(c, true)
+func (s *ContainerGatewaySuite) TestContainerRequestLogViaGateway(c *check.C) {
+       s.setupLogCollection(c)
+       s.testContainerRequestLog(c)
 }
 
-func (s *ContainerGatewaySuite) TestContainerLogViaKeepWeb(c *check.C) {
-       s.testContainerLog(c, false)
+func (s *ContainerGatewaySuite) TestContainerRequestLogViaKeepWeb(c *check.C) {
+       s.setupLogCollection(c)
+       s.saveLogAndCloseGateway(c)
+       s.testContainerRequestLog(c)
 }
 
-func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
-       s.setupLogCollection(c, map[string]string{
-               "stderr.txt":   "hello world\n",
-               "a/b/c/d.html": "<html></html>\n",
-       })
-       if !viaGateway {
-               rootctx := ctrlctx.NewWithToken(s.ctx, s.cluster, s.cluster.SystemRootToken)
-               txt, err := s.gw.LogCollection.MarshalManifest(".")
-               c.Assert(err, check.IsNil)
-               coll, err := s.localdb.CollectionCreate(rootctx, arvados.CreateOptions{
-                       Attrs: map[string]interface{}{
-                               "manifest_text": txt,
-                       }})
-               c.Assert(err, check.IsNil)
-               _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
-                       UUID: s.ctrUUID,
-                       Attrs: map[string]interface{}{
-                               "log":             coll.PortableDataHash,
-                               "gateway_address": "",
-                       }})
-               c.Assert(err, check.IsNil)
-               // gateway_address="" above already ensures localdb
-               // can't circumvent the keep-web proxy test by getting
-               // content from the container gateway; this is just
-               // extra insurance.
-               s.gw.LogCollection = nil
-       }
+func (s *ContainerGatewaySuite) testContainerRequestLog(c *check.C) {
        for _, trial := range []struct {
                method       string
                path         string
@@ -313,7 +343,7 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
        }{
                {
                        method:       "GET",
-                       path:         "/stderr.txt",
+                       path:         s.ctrUUID + "/stderr.txt",
                        expectStatus: http.StatusOK,
                        expectBodyRe: "hello world\n",
                        expectHeader: http.Header{
@@ -322,7 +352,7 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
                },
                {
                        method: "GET",
-                       path:   "/stderr.txt",
+                       path:   s.ctrUUID + "/stderr.txt",
                        header: http.Header{
                                "Range": {"bytes=-6"},
                        },
@@ -335,7 +365,7 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
                },
                {
                        method:       "OPTIONS",
-                       path:         "/stderr.txt",
+                       path:         s.ctrUUID + "/stderr.txt",
                        expectStatus: http.StatusOK,
                        expectBodyRe: "",
                        expectHeader: http.Header{
@@ -345,25 +375,34 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
                },
                {
                        method:       "PROPFIND",
-                       path:         "",
+                       path:         s.ctrUUID + "/",
                        expectStatus: http.StatusMultiStatus,
-                       expectBodyRe: `.*\Q<D:displayname>stderr.txt</D:displayname>\E.*`,
+                       expectBodyRe: `.*\Q<D:displayname>stderr.txt</D:displayname>\E.*>\n?`,
                        expectHeader: http.Header{
                                "Content-Type": {"text/xml; charset=utf-8"},
                        },
                },
                {
                        method:       "PROPFIND",
-                       path:         "/a/b/c/",
+                       path:         s.ctrUUID,
                        expectStatus: http.StatusMultiStatus,
-                       expectBodyRe: `.*\Q<D:displayname>d.html</D:displayname>\E.*`,
+                       expectBodyRe: `.*\Q<D:displayname>stderr.txt</D:displayname>\E.*>\n?`,
+                       expectHeader: http.Header{
+                               "Content-Type": {"text/xml; charset=utf-8"},
+                       },
+               },
+               {
+                       method:       "PROPFIND",
+                       path:         s.ctrUUID + "/a/b/c/",
+                       expectStatus: http.StatusMultiStatus,
+                       expectBodyRe: `.*\Q<D:displayname>d.html</D:displayname>\E.*>\n?`,
                        expectHeader: http.Header{
                                "Content-Type": {"text/xml; charset=utf-8"},
                        },
                },
                {
                        method:       "GET",
-                       path:         "/a/b/c/d.html",
+                       path:         s.ctrUUID + "/a/b/c/d.html",
                        expectStatus: http.StatusOK,
                        expectBodyRe: "<html></html>\n",
                        expectHeader: http.Header{
@@ -372,13 +411,13 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
                },
        } {
                c.Logf("trial %#v", trial)
-               handler, err := s.localdb.ContainerLog(s.userctx, arvados.ContainerLogOptions{
-                       UUID:          s.ctrUUID,
-                       WebDAVOptions: arvados.WebDAVOptions{Path: trial.path},
+               handler, err := s.localdb.ContainerRequestLog(s.userctx, arvados.ContainerLogOptions{
+                       UUID:          s.reqUUID,
+                       WebDAVOptions: arvados.WebDAVOptions{Path: "/" + trial.path},
                })
                c.Assert(err, check.IsNil)
                c.Assert(handler, check.NotNil)
-               r, err := http.NewRequestWithContext(s.userctx, trial.method, "https://controller.example/arvados/v1/containers/"+s.ctrUUID+"/log"+trial.path, nil)
+               r, err := http.NewRequestWithContext(s.userctx, trial.method, "https://controller.example/arvados/v1/container_requests/"+s.reqUUID+"/log/"+trial.path, nil)
                c.Assert(err, check.IsNil)
                for k := range trial.header {
                        r.Header.Set(k, trial.header.Get(k))
@@ -396,12 +435,19 @@ func (s *ContainerGatewaySuite) testContainerLog(c *check.C, viaGateway bool) {
        }
 }
 
-func (s *ContainerGatewaySuite) TestContainerLogViaCadaver(c *check.C) {
-       out := s.runCadaver(c, arvadostest.ActiveToken, "/arvados/v1/containers/"+s.ctrUUID+"/log", "ls")
+func (s *ContainerGatewaySuite) TestContainerRequestLogViaCadaver(c *check.C) {
+       s.setupLogCollection(c)
+
+       out := s.runCadaver(c, arvadostest.ActiveToken, "/arvados/v1/container_requests/"+s.reqUUID+"/log/"+s.ctrUUID, "ls")
        c.Check(out, check.Matches, `(?ms).*stderr\.txt\s+12\s.*`)
        c.Check(out, check.Matches, `(?ms).*a\s+0\s.*`)
 
-       out = s.runCadaver(c, arvadostest.ActiveTokenV2, "/arvados/v1/containers/"+s.ctrUUID+"/log", "get stderr.txt")
+       out = s.runCadaver(c, arvadostest.ActiveTokenV2, "/arvados/v1/container_requests/"+s.reqUUID+"/log/"+s.ctrUUID, "get stderr.txt")
+       c.Check(out, check.Matches, `(?ms).*Downloading .* to stderr\.txt: .* succeeded\..*`)
+
+       s.saveLogAndCloseGateway(c)
+
+       out = s.runCadaver(c, arvadostest.ActiveTokenV2, "/arvados/v1/container_requests/"+s.reqUUID+"/log/"+s.ctrUUID, "get stderr.txt")
        c.Check(out, check.Matches, `(?ms).*Downloading .* to stderr\.txt: .* succeeded\..*`)
 }
 
@@ -410,14 +456,9 @@ func (s *ContainerGatewaySuite) runCadaver(c *check.C, password, path, stdin str
        // just fail on TLS cert verification.
        s.srv.Close()
        rtr := router.New(s.localdb, router.Config{})
-       s.srv = httptest.NewUnstartedServer(rtr)
+       s.srv = httptest.NewUnstartedServer(httpserver.AddRequestIDs(httpserver.LogRequests(rtr)))
        s.srv.Start()
 
-       s.setupLogCollection(c, map[string]string{
-               "stderr.txt":   "hello world\n",
-               "a/b/c/d.html": "<html></html>\n",
-       })
-
        tempdir, err := ioutil.TempDir("", "localdb-test-")
        c.Assert(err, check.IsNil)
        defer os.RemoveAll(tempdir)