17830: Adds test cases & fixes one more issue.
[arvados.git] / lib / controller / integration_test.go
index 0e95c19ea5ca06aef24e815a2f280a40257f7267..26f0dbb0d1388da1886cea726fc644648b4d57e3 100644 (file)
@@ -6,6 +6,8 @@ package controller
 
 import (
        "bytes"
+       "context"
+       "database/sql"
        "encoding/json"
        "fmt"
        "io"
@@ -24,6 +26,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/arvadostest"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
        check "gopkg.in/check.v1"
 )
 
@@ -35,11 +38,6 @@ type IntegrationSuite struct {
 }
 
 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
-       if forceLegacyAPI14 {
-               c.Skip("heavy integration tests don't run with forceLegacyAPI14")
-               return
-       }
-
        cwd, _ := os.Getwd()
 
        s.oidcprovider = arvadostest.NewOIDCProvider(c)
@@ -116,6 +114,8 @@ func (s *IntegrationSuite) SetUpSuite(c *check.C) {
         ClientSecret: ` + s.oidcprovider.ValidClientSecret + `
         EmailClaim: email
         EmailVerifiedClaim: email_verified
+        AcceptAccessToken: true
+        AcceptAccessTokenScope: ""
 `
                } else {
                        yaml += `
@@ -133,8 +133,9 @@ func (s *IntegrationSuite) SetUpSuite(c *check.C) {
                tc := boot.NewTestCluster(
                        filepath.Join(cwd, "..", ".."),
                        id, cfg, "127.0.0."+id[3:], c.Log)
+               tc.Super.NoWorkbench1 = true
+               tc.Start()
                s.testClusters[id] = tc
-               s.testClusters[id].Start()
        }
        for _, tc := range s.testClusters {
                ok := tc.WaitReady()
@@ -362,6 +363,7 @@ func (s *IntegrationSuite) TestCreateContainerRequestWithFedToken(c *check.C) {
        c.Assert(err, check.IsNil)
        req.Header.Set("Content-Type", "application/json")
        err = ac2.DoAndDecode(&cr, req)
+       c.Assert(err, check.IsNil)
        c.Logf("err == %#v", err)
 
        c.Log("...get user with good token")
@@ -388,10 +390,221 @@ func (s *IntegrationSuite) TestCreateContainerRequestWithFedToken(c *check.C) {
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Authorization", "OAuth2 "+ac2.AuthToken)
        resp, err = arvados.InsecureHTTPClient.Do(req)
-       if c.Check(err, check.IsNil) {
-               err = json.NewDecoder(resp.Body).Decode(&cr)
+       c.Assert(err, check.IsNil)
+       err = json.NewDecoder(resp.Body).Decode(&cr)
+       c.Check(err, check.IsNil)
+       c.Check(cr.UUID, check.Matches, "z2222-.*")
+}
+
+func (s *IntegrationSuite) TestCreateContainerRequestWithBadToken(c *check.C) {
+       conn1 := s.testClusters["z1111"].Conn()
+       rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+       _, ac1, _, au := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
+
+       tests := []struct {
+               name         string
+               token        string
+               expectedCode int
+       }{
+               {"Good token", ac1.AuthToken, http.StatusOK},
+               {"Bogus token", "abcdef", http.StatusUnauthorized},
+               {"v1-looking token", "badtoken00badtoken00badtoken00badtoken00b", http.StatusUnauthorized},
+               {"v2-looking token", "v2/" + au.UUID + "/badtoken00badtoken00badtoken00badtoken00b", http.StatusUnauthorized},
+       }
+
+       body, _ := json.Marshal(map[string]interface{}{
+               "container_request": map[string]interface{}{
+                       "command":         []string{"echo"},
+                       "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
+                       "cwd":             "/",
+                       "output_path":     "/",
+               },
+       })
+
+       for _, tt := range tests {
+               c.Log(c.TestName() + " " + tt.name)
+               ac1.AuthToken = tt.token
+               req, err := http.NewRequest("POST", "https://"+ac1.APIHost+"/arvados/v1/container_requests", bytes.NewReader(body))
+               c.Assert(err, check.IsNil)
+               req.Header.Set("Content-Type", "application/json")
+               resp, err := ac1.Do(req)
+               c.Assert(err, check.IsNil)
+               c.Assert(resp.StatusCode, check.Equals, tt.expectedCode)
+       }
+}
+
+func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
+       conn1 := s.testClusters["z1111"].Conn()
+       rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+       userctx1, ac1, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
+
+       coll, err := conn1.CollectionCreate(userctx1, arvados.CreateOptions{})
+       c.Check(err, check.IsNil)
+       specimen, err := conn1.SpecimenCreate(userctx1, arvados.CreateOptions{})
+       c.Check(err, check.IsNil)
+
+       tests := []struct {
+               path            string
+               reqIdProvided   bool
+               notFoundRequest bool
+       }{
+               {"/arvados/v1/collections", false, false},
+               {"/arvados/v1/collections", true, false},
+               {"/arvados/v1/nonexistant", false, true},
+               {"/arvados/v1/nonexistant", true, true},
+               {"/arvados/v1/collections/" + coll.UUID, false, false},
+               {"/arvados/v1/collections/" + coll.UUID, true, false},
+               {"/arvados/v1/specimens/" + specimen.UUID, false, false},
+               {"/arvados/v1/specimens/" + specimen.UUID, true, false},
+               {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", false, true},
+               {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", true, true},
+               {"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", false, true},
+               {"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", true, true},
+       }
+
+       for _, tt := range tests {
+               c.Log(c.TestName() + " " + tt.path)
+               req, err := http.NewRequest("GET", "https://"+ac1.APIHost+tt.path, nil)
+               c.Assert(err, check.IsNil)
+               customReqId := "abcdeG"
+               if !tt.reqIdProvided {
+                       c.Assert(req.Header.Get("X-Request-Id"), check.Equals, "")
+               } else {
+                       req.Header.Set("X-Request-Id", customReqId)
+               }
+               resp, err := ac1.Do(req)
+               c.Assert(err, check.IsNil)
+               if tt.notFoundRequest {
+                       c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
+               } else {
+                       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
+               }
+               if !tt.reqIdProvided {
+                       c.Check(resp.Header.Get("X-Request-Id"), check.Matches, "^req-[0-9a-zA-Z]{20}$")
+                       if tt.notFoundRequest {
+                               var jresp httpserver.ErrorResponse
+                               err := json.NewDecoder(resp.Body).Decode(&jresp)
+                               c.Check(err, check.IsNil)
+                               c.Assert(jresp.Errors, check.HasLen, 1)
+                               c.Check(jresp.Errors[0], check.Matches, "^.*(req-[0-9a-zA-Z]{20}).*$")
+                       }
+               } else {
+                       c.Check(resp.Header.Get("X-Request-Id"), check.Equals, customReqId)
+                       if tt.notFoundRequest {
+                               var jresp httpserver.ErrorResponse
+                               err := json.NewDecoder(resp.Body).Decode(&jresp)
+                               c.Check(err, check.IsNil)
+                               c.Assert(jresp.Errors, check.HasLen, 1)
+                               c.Check(jresp.Errors[0], check.Matches, "^.*("+customReqId+").*$")
+                       }
+               }
+       }
+}
+
+// We test the direct access to the database
+// normally an integration test would not have a database access, but  in this case we need
+// to test tokens that are secret, so there is no API response that will give them back
+func (s *IntegrationSuite) dbConn(c *check.C, clusterID string) (*sql.DB, *sql.Conn) {
+       ctx := context.Background()
+       db, err := sql.Open("postgres", s.testClusters[clusterID].Super.Cluster().PostgreSQL.Connection.String())
+       c.Assert(err, check.IsNil)
+
+       conn, err := db.Conn(ctx)
+       c.Assert(err, check.IsNil)
+
+       rows, err := conn.ExecContext(ctx, `SELECT 1`)
+       c.Assert(err, check.IsNil)
+       n, err := rows.RowsAffected()
+       c.Assert(err, check.IsNil)
+       c.Assert(n, check.Equals, int64(1))
+       return db, conn
+}
+
+// TestRuntimeTokenInCR will test several different tokens in the runtime attribute
+// and check the expected results accessing directly to the database if needed.
+func (s *IntegrationSuite) TestRuntimeTokenInCR(c *check.C) {
+       db, dbconn := s.dbConn(c, "z1111")
+       defer db.Close()
+       defer dbconn.Close()
+       conn1 := s.testClusters["z1111"].Conn()
+       rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+       userctx1, ac1, _, au := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
+
+       tests := []struct {
+               name                 string
+               token                string
+               expectAToGetAValidCR bool
+               expectedToken        *string
+       }{
+               {"Good token z1111 user", ac1.AuthToken, true, &ac1.AuthToken},
+               {"Bogus token", "abcdef", false, nil},
+               {"v1-looking token", "badtoken00badtoken00badtoken00badtoken00b", false, nil},
+               {"v2-looking token", "v2/" + au.UUID + "/badtoken00badtoken00badtoken00badtoken00b", false, nil},
+       }
+
+       for _, tt := range tests {
+               c.Log(c.TestName() + " " + tt.name)
+
+               rq := map[string]interface{}{
+                       "command":         []string{"echo"},
+                       "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
+                       "cwd":             "/",
+                       "output_path":     "/",
+                       "runtime_token":   tt.token,
+               }
+               cr, err := conn1.ContainerRequestCreate(userctx1, arvados.CreateOptions{Attrs: rq})
+               if tt.expectAToGetAValidCR {
+                       c.Check(err, check.IsNil)
+                       c.Check(cr, check.NotNil)
+                       c.Check(cr.UUID, check.Not(check.Equals), "")
+               }
+
+               if tt.expectedToken == nil {
+                       continue
+               }
+
+               c.Logf("cr.UUID: %s", cr.UUID)
+               row := dbconn.QueryRowContext(rootctx1, `SELECT runtime_token from container_requests where uuid=$1`, cr.UUID)
+               c.Check(row, check.NotNil)
+               var token sql.NullString
+               row.Scan(&token)
+               if c.Check(token.Valid, check.Equals, true) {
+                       c.Check(token.String, check.Equals, *tt.expectedToken)
+               }
+       }
+}
+
+// TestIntermediateCluster will send a container request to
+// one cluster with another cluster as the destination
+// and check the tokens are being handled properly
+func (s *IntegrationSuite) TestIntermediateCluster(c *check.C) {
+       conn1 := s.testClusters["z1111"].Conn()
+       rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+       uctx1, ac1, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
+
+       tests := []struct {
+               name                 string
+               token                string
+               expectedRuntimeToken string
+               expectedUUIDprefix   string
+       }{
+               {"Good token z1111 user sending a CR to z2222", ac1.AuthToken, "", "z2222-xvhdp-"},
+       }
+
+       for _, tt := range tests {
+               c.Log(c.TestName() + " " + tt.name)
+               rq := map[string]interface{}{
+                       "command":         []string{"echo"},
+                       "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
+                       "cwd":             "/",
+                       "output_path":     "/",
+                       "runtime_token":   tt.token,
+               }
+               cr, err := conn1.ContainerRequestCreate(uctx1, arvados.CreateOptions{ClusterID: "z2222", Attrs: rq})
+
                c.Check(err, check.IsNil)
-               c.Check(cr.UUID, check.Matches, "z2222-.*")
+               c.Check(strings.HasPrefix(cr.UUID, tt.expectedUUIDprefix), check.Equals, true)
+               c.Check(cr.RuntimeToken, check.Equals, tt.expectedRuntimeToken)
        }
 }
 
@@ -537,15 +750,16 @@ func (s *IntegrationSuite) TestOIDCAccessTokenAuth(c *check.C) {
        accesstoken := s.oidcprovider.ValidAccessToken()
 
        for _, clusterID := range []string{"z1111", "z2222"} {
-               c.Logf("trying clusterid %s", clusterID)
-
-               conn := s.testClusters[clusterID].Conn()
-               ctx, ac, kc := s.testClusters[clusterID].ClientsWithToken(accesstoken)
 
                var coll arvados.Collection
 
                // Write some file data and create a collection
                {
+                       c.Logf("save collection to %s", clusterID)
+
+                       conn := s.testClusters[clusterID].Conn()
+                       ctx, ac, kc := s.testClusters[clusterID].ClientsWithToken(accesstoken)
+
                        fs, err := coll.FileSystem(ac, kc)
                        c.Assert(err, check.IsNil)
                        f, err := fs.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
@@ -562,15 +776,22 @@ func (s *IntegrationSuite) TestOIDCAccessTokenAuth(c *check.C) {
                        c.Assert(err, check.IsNil)
                }
 
-               // Read the collection & file data
-               {
+               // Read the collection & file data -- both from the
+               // cluster where it was created, and from the other
+               // cluster.
+               for _, readClusterID := range []string{"z1111", "z2222", "z3333"} {
+                       c.Logf("retrieve %s from %s", coll.UUID, readClusterID)
+
+                       conn := s.testClusters[readClusterID].Conn()
+                       ctx, ac, kc := s.testClusters[readClusterID].ClientsWithToken(accesstoken)
+
                        user, err := conn.UserGetCurrent(ctx, arvados.GetOptions{})
                        c.Assert(err, check.IsNil)
                        c.Check(user.FullName, check.Equals, "Example User")
-                       coll, err = conn.CollectionGet(ctx, arvados.GetOptions{UUID: coll.UUID})
+                       readcoll, err := conn.CollectionGet(ctx, arvados.GetOptions{UUID: coll.UUID})
                        c.Assert(err, check.IsNil)
-                       c.Check(coll.ManifestText, check.Not(check.Equals), "")
-                       fs, err := coll.FileSystem(ac, kc)
+                       c.Check(readcoll.ManifestText, check.Not(check.Equals), "")
+                       fs, err := readcoll.FileSystem(ac, kc)
                        c.Assert(err, check.IsNil)
                        f, err := fs.Open("test.txt")
                        c.Assert(err, check.IsNil)