2960: Refactor keepstore into a streaming server.
[arvados.git] / services / keepstore / proxy_remote_test.go
index 4c505138089f3c88eec29a0551593a852e4a0781..886754e14a422d226ccc34c316a608a10bf36f27 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-package main
+package keepstore
 
 import (
        "crypto/md5"
@@ -16,19 +16,21 @@ import (
        "sync/atomic"
        "time"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
-       "git.curoverse.com/arvados.git/sdk/go/auth"
-       "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/arvadostest"
+       "git.arvados.org/arvados.git/sdk/go/auth"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
+       "git.arvados.org/arvados.git/sdk/go/keepclient"
+       "github.com/prometheus/client_golang/prometheus"
        check "gopkg.in/check.v1"
 )
 
-var _ = check.Suite(&ProxyRemoteSuite{})
+var _ = check.Suite(&proxyRemoteSuite{})
 
-type ProxyRemoteSuite struct {
+type proxyRemoteSuite struct {
        cluster *arvados.Cluster
-       vm      VolumeManager
-       rtr     http.Handler
+       handler *router
 
        remoteClusterID      string
        remoteBlobSigningKey []byte
@@ -39,7 +41,7 @@ type ProxyRemoteSuite struct {
        remoteAPI            *httptest.Server
 }
 
-func (s *ProxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
+func (s *proxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
        expectToken, err := auth.SaltToken(arvadostest.ActiveTokenV2, s.remoteClusterID)
        if err != nil {
                panic(err)
@@ -56,7 +58,7 @@ func (s *ProxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http
        http.Error(w, "404", 404)
 }
 
-func (s *ProxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
+func (s *proxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
        host, port, _ := net.SplitHostPort(strings.Split(s.remoteKeepproxy.URL, "//")[1])
        portnum, _ := strconv.Atoi(port)
        if r.URL.Path == "/arvados/v1/discovery/v1/rest" {
@@ -80,39 +82,36 @@ func (s *ProxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Reque
        http.Error(w, "404", 404)
 }
 
-func (s *ProxyRemoteSuite) SetUpTest(c *check.C) {
+func (s *proxyRemoteSuite) SetUpTest(c *check.C) {
        s.remoteClusterID = "z0000"
        s.remoteBlobSigningKey = []byte("3b6df6fb6518afe12922a5bc8e67bf180a358bc8")
-       s.remoteKeepproxy = httptest.NewServer(http.HandlerFunc(s.remoteKeepproxyHandler))
+       s.remoteKeepproxy = httptest.NewServer(httpserver.LogRequests(http.HandlerFunc(s.remoteKeepproxyHandler)))
        s.remoteAPI = httptest.NewUnstartedServer(http.HandlerFunc(s.remoteAPIHandler))
        s.remoteAPI.StartTLS()
-       s.cluster = arvados.IntegrationTestCluster()
+       s.cluster = testCluster(c)
        s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
-               s.remoteClusterID: arvados.RemoteCluster{
+               s.remoteClusterID: {
                        Host:     strings.Split(s.remoteAPI.URL, "//")[1],
                        Proxy:    true,
                        Scheme:   "http",
                        Insecure: true,
                },
        }
-       s.vm = MakeTestVolumeManager(2)
-       KeepVM = s.vm
-       theConfig = DefaultConfig()
-       theConfig.systemAuthToken = arvadostest.DataManagerToken
-       theConfig.Start()
-       s.rtr = MakeRESTRouter(s.cluster)
+       s.cluster.Volumes = map[string]arvados.Volume{"zzzzz-nyw5e-000000000000000": {Driver: "stub"}}
 }
 
-func (s *ProxyRemoteSuite) TearDownTest(c *check.C) {
-       s.vm.Close()
-       KeepVM = nil
-       theConfig = DefaultConfig()
-       theConfig.Start()
+func (s *proxyRemoteSuite) TearDownTest(c *check.C) {
        s.remoteAPI.Close()
        s.remoteKeepproxy.Close()
 }
 
-func (s *ProxyRemoteSuite) TestProxyRemote(c *check.C) {
+func (s *proxyRemoteSuite) TestProxyRemote(c *check.C) {
+       reg := prometheus.NewRegistry()
+       router, cancel := testRouter(c, s.cluster, reg)
+       defer cancel()
+       instrumented := httpserver.Instrument(reg, ctxlog.TestLogger(c), router)
+       handler := httpserver.LogRequests(instrumented.ServeAPI(s.cluster.ManagementToken, instrumented))
+
        data := []byte("foo bar")
        s.remoteKeepData = data
        locator := fmt.Sprintf("%x+%d", md5.Sum(data), len(data))
@@ -120,30 +119,108 @@ func (s *ProxyRemoteSuite) TestProxyRemote(c *check.C) {
 
        path := "/" + strings.Replace(s.remoteKeepLocator, "+A", "+R"+s.remoteClusterID+"-", 1)
 
-       var req *http.Request
-       var resp *httptest.ResponseRecorder
-       tryWithToken := func(token string) {
-               req = httptest.NewRequest("GET", path, nil)
-               req.Header.Set("Authorization", "Bearer "+token)
+       for _, trial := range []struct {
+               label            string
+               method           string
+               token            string
+               xKeepSignature   string
+               expectRemoteReqs int64
+               expectCode       int
+               expectSignature  bool
+       }{
+               {
+                       label:            "GET only",
+                       method:           "GET",
+                       token:            arvadostest.ActiveTokenV2,
+                       expectRemoteReqs: 1,
+                       expectCode:       http.StatusOK,
+               },
+               {
+                       label:            "obsolete token",
+                       method:           "GET",
+                       token:            arvadostest.ActiveToken,
+                       expectRemoteReqs: 0,
+                       expectCode:       http.StatusBadRequest,
+               },
+               {
+                       label:            "bad token",
+                       method:           "GET",
+                       token:            arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx",
+                       expectRemoteReqs: 1,
+                       expectCode:       http.StatusNotFound,
+               },
+               {
+                       label:            "HEAD only",
+                       method:           "HEAD",
+                       token:            arvadostest.ActiveTokenV2,
+                       expectRemoteReqs: 1,
+                       expectCode:       http.StatusOK,
+               },
+               {
+                       label:            "HEAD with local signature",
+                       method:           "HEAD",
+                       xKeepSignature:   "local, time=" + time.Now().Format(time.RFC3339),
+                       token:            arvadostest.ActiveTokenV2,
+                       expectRemoteReqs: 1,
+                       expectCode:       http.StatusOK,
+                       expectSignature:  true,
+               },
+               {
+                       label:            "GET with local signature",
+                       method:           "GET",
+                       xKeepSignature:   "local, time=" + time.Now().Format(time.RFC3339),
+                       token:            arvadostest.ActiveTokenV2,
+                       expectRemoteReqs: 1,
+                       expectCode:       http.StatusOK,
+                       expectSignature:  true,
+               },
+       } {
+               c.Logf("=== trial: %s", trial.label)
+
+               s.remoteKeepRequests = 0
+
+               var req *http.Request
+               var resp *httptest.ResponseRecorder
+               req = httptest.NewRequest(trial.method, path, nil)
+               req.Header.Set("Authorization", "Bearer "+trial.token)
+               if trial.xKeepSignature != "" {
+                       req.Header.Set("X-Keep-Signature", trial.xKeepSignature)
+               }
                resp = httptest.NewRecorder()
-               s.rtr.ServeHTTP(resp, req)
+               handler.ServeHTTP(resp, req)
+               c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
+               if !c.Check(resp.Code, check.Equals, trial.expectCode) {
+                       c.Logf("resp.Code %d came with resp.Body %q", resp.Code, resp.Body.String())
+               }
+               if resp.Code == http.StatusOK {
+                       if trial.method == "HEAD" {
+                               c.Check(resp.Body.String(), check.Equals, "")
+                               c.Check(resp.Result().ContentLength, check.Equals, int64(len(data)))
+                       } else {
+                               c.Check(resp.Body.String(), check.Equals, string(data))
+                       }
+               } else {
+                       c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
+               }
+
+               c.Check(resp.Header().Get("Vary"), check.Matches, `(.*, )?X-Keep-Signature(, .*)?`)
+
+               locHdr := resp.Header().Get("X-Keep-Locator")
+               if !trial.expectSignature {
+                       c.Check(locHdr, check.Equals, "")
+                       continue
+               }
+
+               c.Check(locHdr, check.Not(check.Equals), "")
+               c.Check(locHdr, check.Not(check.Matches), `.*\+R.*`)
+               c.Check(arvados.VerifySignature(locHdr, trial.token, s.cluster.Collections.BlobSigningTTL.Duration(), []byte(s.cluster.Collections.BlobSigningKey)), check.IsNil)
+
+               // Ensure block can be requested using new signature
+               req = httptest.NewRequest("GET", "/"+locHdr, nil)
+               req.Header.Set("Authorization", "Bearer "+trial.token)
+               resp = httptest.NewRecorder()
+               handler.ServeHTTP(resp, req)
+               c.Check(resp.Code, check.Equals, http.StatusOK)
+               c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
        }
-
-       // Happy path
-       tryWithToken(arvadostest.ActiveTokenV2)
-       c.Check(s.remoteKeepRequests, check.Equals, int64(1))
-       c.Check(resp.Code, check.Equals, http.StatusOK)
-       c.Check(resp.Body.String(), check.Equals, string(data))
-
-       // Obsolete token
-       tryWithToken(arvadostest.ActiveToken)
-       c.Check(s.remoteKeepRequests, check.Equals, int64(1))
-       c.Check(resp.Code, check.Equals, http.StatusBadRequest)
-       c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
-
-       // Bad token
-       tryWithToken(arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx")
-       c.Check(s.remoteKeepRequests, check.Equals, int64(2))
-       c.Check(resp.Code, check.Equals, http.StatusNotFound)
-       c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
 }