+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)
+ kc, err := keepclient.MakeKeepClient(ac)
+ c.Assert(err, check.IsNil)
+ cfs, err := (&arvados.Collection{}).FileSystem(client, kc)
+ c.Assert(err, check.IsNil)
+ for name, content := range files {
+ for i, ch := range name {
+ if ch == '/' {
+ err := cfs.Mkdir("/"+name[:i], 0777)
+ c.Assert(err, check.IsNil)
+ }
+ }
+ f, err := cfs.OpenFile("/"+name, os.O_CREATE|os.O_WRONLY, 0777)
+ c.Assert(err, check.IsNil)
+ f.Write([]byte(content))
+ err = f.Close()
+ c.Assert(err, check.IsNil)
+ }
+ cfs.Sync()
+ s.gw.LogCollection = cfs
+}
+
+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{}{
+ "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) TestContainerLogViaTunnel(c *check.C) {
+ forceProxyForTest = true
+ defer func() { forceProxyForTest = false }()
+
+ s.gw = s.setupGatewayWithTunnel(c)
+ s.setupLogCollection(c)
+
+ for _, broken := range []bool{false, true} {
+ c.Logf("broken=%v", broken)
+
+ if broken {
+ delete(s.cluster.Services.Controller.InternalURLs, *forceInternalURLForTest)
+ } else {
+ s.cluster.Services.Controller.InternalURLs[*forceInternalURLForTest] = arvados.ServiceInstance{}
+ 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"},
+ })
+ if broken {
+ c.Check(err, check.ErrorMatches, `.*tunnel endpoint is invalid.*`)
+ continue
+ }
+ 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)
+ c.Assert(err, check.IsNil)
+ r.Header.Set("Authorization", "Bearer "+arvadostest.ActiveTokenV2)
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, r)
+ resp := rec.Result()
+ c.Check(resp.StatusCode, check.Equals, http.StatusOK)
+ buf, err := ioutil.ReadAll(resp.Body)
+ c.Check(err, check.IsNil)
+ c.Check(string(buf), check.Equals, "hello world\n")
+ }
+}
+
+func (s *ContainerGatewaySuite) TestContainerLogViaGateway(c *check.C) {
+ s.setupLogCollection(c)
+ s.testContainerLog(c)
+}
+
+func (s *ContainerGatewaySuite) TestContainerLogViaKeepWeb(c *check.C) {
+ s.setupLogCollection(c)
+ s.saveLogAndCloseGateway(c)
+ s.testContainerLog(c)
+}
+
+func (s *ContainerGatewaySuite) testContainerLog(c *check.C) {
+ for _, trial := range []struct {
+ method string
+ path string
+ header http.Header
+ expectStatus int
+ expectBodyRe string
+ expectHeader http.Header
+ }{
+ {
+ method: "GET",
+ path: "/stderr.txt",
+ expectStatus: http.StatusOK,
+ expectBodyRe: "hello world\n",
+ expectHeader: http.Header{
+ "Content-Type": {"text/plain; charset=utf-8"},
+ },
+ },
+ {
+ method: "GET",
+ path: "/stderr.txt",
+ header: http.Header{
+ "Range": {"bytes=-6"},
+ },
+ expectStatus: http.StatusPartialContent,
+ expectBodyRe: "world\n",
+ expectHeader: http.Header{
+ "Content-Type": {"text/plain; charset=utf-8"},
+ "Content-Range": {"bytes 6-11/12"},
+ },
+ },
+ {
+ method: "OPTIONS",
+ path: "/stderr.txt",
+ expectStatus: http.StatusOK,
+ expectBodyRe: "",
+ expectHeader: http.Header{
+ "Dav": {"1, 2"},
+ "Allow": {"OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"},
+ },
+ },
+ {
+ method: "PROPFIND",
+ path: "",
+ expectStatus: http.StatusMultiStatus,
+ expectBodyRe: `.*\Q<D:displayname>stderr.txt</D:displayname>\E.*`,
+ expectHeader: http.Header{
+ "Content-Type": {"text/xml; charset=utf-8"},
+ },
+ },
+ {
+ method: "PROPFIND",
+ path: "/a/b/c/",
+ expectStatus: http.StatusMultiStatus,
+ expectBodyRe: `.*\Q<D:displayname>d.html</D:displayname>\E.*`,
+ expectHeader: http.Header{
+ "Content-Type": {"text/xml; charset=utf-8"},
+ },
+ },
+ {
+ method: "GET",
+ path: "/a/b/c/d.html",
+ expectStatus: http.StatusOK,
+ expectBodyRe: "<html></html>\n",
+ expectHeader: http.Header{
+ "Content-Type": {"text/html; charset=utf-8"},
+ },
+ },
+ } {
+ c.Logf("trial %#v", trial)
+ handler, err := s.localdb.ContainerLog(s.userctx, arvados.ContainerLogOptions{
+ UUID: s.ctrUUID,
+ 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)
+ c.Assert(err, check.IsNil)
+ for k := range trial.header {
+ r.Header.Set(k, trial.header.Get(k))
+ }
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, r)
+ resp := rec.Result()
+ c.Check(resp.StatusCode, check.Equals, trial.expectStatus)
+ for k := range trial.expectHeader {
+ c.Check(resp.Header.Get(k), check.Equals, trial.expectHeader.Get(k))
+ }
+ buf, err := ioutil.ReadAll(resp.Body)
+ c.Check(err, check.IsNil)
+ c.Check(string(buf), check.Matches, trial.expectBodyRe)
+ }
+}
+
+func (s *ContainerGatewaySuite) TestContainerLogViaCadaver(c *check.C) {
+ s.setupLogCollection(c)
+
+ out := s.runCadaver(c, arvadostest.ActiveToken, "/arvados/v1/containers/"+s.ctrUUID+"/log", "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")
+ c.Check(out, check.Matches, `(?ms).*Downloading .* to stderr\.txt: .* succeeded\..*`)
+
+ s.saveLogAndCloseGateway(c)
+
+ out = s.runCadaver(c, arvadostest.ActiveTokenV2, "/arvados/v1/containers/"+s.ctrUUID+"/log", "get stderr.txt")
+ c.Check(out, check.Matches, `(?ms).*Downloading .* to stderr\.txt: .* succeeded\..*`)
+}
+
+func (s *ContainerGatewaySuite) runCadaver(c *check.C, password, path, stdin string) string {
+ // Replace s.srv with an HTTP server, otherwise cadaver will
+ // just fail on TLS cert verification.
+ s.srv.Close()
+ rtr := router.New(s.localdb, router.Config{})
+ s.srv = httptest.NewUnstartedServer(httpserver.AddRequestIDs(httpserver.LogRequests(rtr)))
+ s.srv.Start()
+
+ tempdir, err := ioutil.TempDir("", "localdb-test-")
+ c.Assert(err, check.IsNil)
+ defer os.RemoveAll(tempdir)
+
+ cmd := exec.Command("cadaver", s.srv.URL+path)
+ if password != "" {
+ cmd.Env = append(os.Environ(), "HOME="+tempdir)
+ f, err := os.OpenFile(filepath.Join(tempdir, ".netrc"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
+ c.Assert(err, check.IsNil)
+ _, err = fmt.Fprintf(f, "default login none password %s\n", password)
+ c.Assert(err, check.IsNil)
+ c.Assert(f.Close(), check.IsNil)
+ }
+ cmd.Stdin = bytes.NewBufferString(stdin)
+ cmd.Dir = tempdir
+ stdout, err := cmd.StdoutPipe()
+ c.Assert(err, check.Equals, nil)
+ cmd.Stderr = cmd.Stdout
+ c.Logf("cmd: %v", cmd.Args)
+ go cmd.Start()
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, stdout)
+ c.Check(err, check.Equals, nil)
+ err = cmd.Wait()
+ c.Check(err, check.Equals, nil)
+ return buf.String()
+}
+