container requests in the new codepath for controller
authorNico Cesar <nico@nicocesar.com>
Tue, 10 Nov 2020 14:00:52 +0000 (09:00 -0500)
committerNico Cesar <nico@nicocesar.com>
Tue, 10 Nov 2020 14:05:43 +0000 (09:05 -0500)
Controller's "new" codepath is a step towards sunsetting railsapi. This
commit is the initial work to get Container Requests in that path

The reason for deleting some tests is intentional. This is part of the migration
from
   apps/workbench/test/controllers/container_requests_controller_test.rb
to
   lib/controller/integration_test.go

This single-commit is the result of collaboration with Lucas Di Pentima
and Tom Clegg in the branch 17014-controller-cr

Arvados-DCO-1.1-Signed-off-by: Nico Cesar <nico@curii.com>

29 files changed:
apps/workbench/test/controllers/container_requests_controller_test.rb
lib/boot/supervisor.go
lib/controller/cmd.go
lib/controller/fed_containers.go
lib/controller/federation.go
lib/controller/federation/conn.go
lib/controller/federation/generate.go
lib/controller/federation/generated.go
lib/controller/federation_test.go
lib/controller/handler.go
lib/controller/handler_test.go
lib/controller/integration_test.go
lib/controller/localdb/conn.go
lib/controller/localdb/login.go
lib/controller/localdb/login_ldap.go
lib/controller/localdb/login_ldap_test.go
lib/controller/localdb/login_oidc.go
lib/controller/localdb/login_pam.go
lib/controller/localdb/login_pam_test.go
lib/controller/localdb/login_testuser.go
lib/controller/localdb/login_testuser_test.go
lib/controller/router/response.go
lib/controller/router/router.go
lib/controller/rpc/conn.go
sdk/go/arvados/api.go
sdk/go/arvados/container.go
sdk/go/arvadostest/api.go
services/api/app/controllers/user_sessions_controller.rb
services/api/test/fixtures/container_requests.yml

index 73d357f3a60f6a9da27db76a452a5ded6b0e3bd8..c8709df3c35154e172898e060a9cb526196ef064 100644 (file)
@@ -138,7 +138,6 @@ class ContainerRequestsControllerTest < ActionController::TestCase
     assert_includes @response.body, "href=\"\/collections/fa7aeb5140e2848d39b416daeef4ffc5+45/foobar\?" # locator on command
     assert_includes @response.body, "href=\"\/collections/fa7aeb5140e2848d39b416daeef4ffc5+45/foo" # mount input1
     assert_includes @response.body, "href=\"\/collections/fa7aeb5140e2848d39b416daeef4ffc5+45/bar" # mount input2
-    assert_includes @response.body, "href=\"\/collections/f9ddda46bb293b6847da984e3aa735db+290" # mount workflow
     assert_includes @response.body, "href=\"#Log\""
     assert_includes @response.body, "href=\"#Provenance\""
   end
index 1e8e83ff3b3327005fb545f39c2ab8d357409fdf..0e4de9d5c1d400cf1bd28f16279e39081d448a91 100644 (file)
@@ -59,6 +59,8 @@ type Supervisor struct {
        environ    []string // for child processes
 }
 
+func (super *Supervisor) Cluster() *arvados.Cluster { return super.cluster }
+
 func (super *Supervisor) Start(ctx context.Context, cfg *arvados.Config, cfgPath string) {
        super.ctx, super.cancel = context.WithCancel(ctx)
        super.done = make(chan struct{})
index 0c46e857b3aa8604743575a5f0be37f022e6297c..c6b53eb8f6d930f113936449dbb762740f344e8d 100644 (file)
@@ -13,6 +13,7 @@ import (
        "github.com/prometheus/client_golang/prometheus"
 )
 
+// Command intanciates a cmd.Handler with the service.Command used in package cmd
 var Command cmd.Handler = service.Command(arvados.ServiceNameController, newHandler)
 
 func newHandler(_ context.Context, cluster *arvados.Cluster, _ string, _ *prometheus.Registry) service.Handler {
index 51f243e69e48d106bec6239c8cc8a66597dff60e..028f4f597658da6d1427d894f3ab8866a47f8b04 100644 (file)
@@ -28,7 +28,6 @@ func remoteContainerRequestCreate(
        if effectiveMethod != "POST" || uuid != "" || remainder != "" {
                return false
        }
-
        // First make sure supplied token is valid.
        creds := auth.NewCredentials()
        creds.LoadTokensFromHTTPRequest(req)
index cab5e4c4ca45172edb28f07210b001456f1e11af..869fec58e4c21b200453880aecb7028344ef7c32 100644 (file)
@@ -164,6 +164,12 @@ func (h *Handler) validateAPItoken(req *http.Request, token string) (*CurrentUse
                uuid = sp[1]
                token = sp[2]
        }
+
+       if len(token) < 41 {
+               ctxlog.FromContext(req.Context()).Debugf("validateAPItoken(%s): The lenght of the token is not 41", token)
+               return nil, false, nil
+       }
+
        user.Authorization.APIToken = token
        var scopes string
        err = db.QueryRowContext(req.Context(), `SELECT api_client_authorizations.uuid, api_client_authorizations.scopes, users.uuid FROM api_client_authorizations JOIN users on api_client_authorizations.user_id=users.id WHERE api_token=$1 AND (expires_at IS NULL OR expires_at > current_timestamp AT TIME ZONE 'UTC') LIMIT 1`, token).Scan(&user.Authorization.UUID, &scopes, &user.UUID)
index 986faa7b05e33e325a6bc1c15b4283ec6d79d9ed..446e3893d1b6841e2ead81a8ef9c1edfd9ef23f5 100644 (file)
@@ -336,6 +336,66 @@ func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOption
        return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options)
 }
 
+func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
+       return conn.generated_ContainerRequestList(ctx, options)
+}
+
+func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
+       be := conn.chooseBackend(options.ClusterID)
+       if be == conn.local {
+               return be.ContainerRequestCreate(ctx, options)
+       }
+       if _, ok := options.Attrs["runtime_token"]; !ok {
+               // If runtime_token is not set, create a new token
+               aca, err := conn.local.APIClientAuthorizationCurrent(ctx, arvados.GetOptions{})
+               if err != nil {
+                       // This should probably be StatusUnauthorized
+                       // (need to update test in
+                       // lib/controller/federation_test.go):
+                       return arvados.ContainerRequest{}, httpErrorf(http.StatusForbidden, "%w", err)
+               }
+               user, err := conn.local.UserGetCurrent(ctx, arvados.GetOptions{})
+               if err != nil {
+                       return arvados.ContainerRequest{}, err
+               }
+               if len(aca.Scopes) != 0 || aca.Scopes[0] != "all" {
+                       return arvados.ContainerRequest{}, httpErrorf(http.StatusForbidden, "token scope is not [all]")
+               }
+               if strings.HasPrefix(aca.UUID, conn.cluster.ClusterID) {
+                       // Local user, submitting to a remote cluster.
+                       // Create a new (FIXME: needs to be
+                       // time-limited!) token.
+                       local, ok := conn.local.(*localdb.Conn)
+                       if !ok {
+                               return arvados.ContainerRequest{}, httpErrorf(http.StatusInternalServerError, "bug: local backend is a %T, not a *localdb.Conn", conn.local)
+                       }
+                       aca, err = local.CreateAPIClientAuthorization(ctx, conn.cluster.SystemRootToken, rpc.UserSessionAuthInfo{UserUUID: user.UUID})
+                       if err != nil {
+                               return arvados.ContainerRequest{}, err
+                       }
+                       options.Attrs["runtime_token"] = aca.TokenV2()
+               } else {
+                       // Remote user. Container request will use the
+                       // current token, minus the trailing portion
+                       // (optional container uuid).
+                       options.Attrs["runtime_token"] = aca.TokenV2()
+               }
+       }
+       return be.ContainerRequestCreate(ctx, options)
+}
+
+func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
+       return conn.chooseBackend(options.UUID).ContainerRequestUpdate(ctx, options)
+}
+
+func (conn *Conn) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
+       return conn.chooseBackend(options.UUID).ContainerRequestGet(ctx, options)
+}
+
+func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
+       return conn.chooseBackend(options.UUID).ContainerRequestDelete(ctx, options)
+}
+
 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
        return conn.generated_SpecimenList(ctx, options)
 }
index ab5d9966a4409479ec2bd1725e14a629c1770f12..9ce7fdcb21f6aa19b264b57982facaeee39305f9 100644 (file)
@@ -52,7 +52,7 @@ func main() {
                defer out.Close()
                out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
                io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
-               for _, t := range []string{"Container", "Specimen", "User"} {
+               for _, t := range []string{"Container", "ContainerRequest", "Specimen", "User"} {
                        _, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
                        if err != nil {
                                panic(err)
index 8745f3b9730b068faa2cc9e8a02d4c5638c7164a..ab9db93a4d105a17572beee6080cd5b13f2755c9 100755 (executable)
@@ -58,6 +58,47 @@ func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.L
        return merged, err
 }
 
+func (conn *Conn) generated_ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
+       var mtx sync.Mutex
+       var merged arvados.ContainerRequestList
+       var needSort atomic.Value
+       needSort.Store(false)
+       err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
+               options.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor
+               cl, err := backend.ContainerRequestList(ctx, options)
+               if err != nil {
+                       return nil, err
+               }
+               mtx.Lock()
+               defer mtx.Unlock()
+               if len(merged.Items) == 0 {
+                       merged = cl
+               } else if len(cl.Items) > 0 {
+                       merged.Items = append(merged.Items, cl.Items...)
+                       needSort.Store(true)
+               }
+               uuids := make([]string, 0, len(cl.Items))
+               for _, item := range cl.Items {
+                       uuids = append(uuids, item.UUID)
+               }
+               return uuids, nil
+       })
+       if needSort.Load().(bool) {
+               // Apply the default/implied order, "modified_at desc"
+               sort.Slice(merged.Items, func(i, j int) bool {
+                       mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt
+                       return mj.Before(mi)
+               })
+       }
+       if merged.Items == nil {
+               // Return empty results as [], not null
+               // (https://github.com/golang/go/issues/27589 might be
+               // a better solution in the future)
+               merged.Items = []arvados.ContainerRequest{}
+       }
+       return merged, err
+}
+
 func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
        var mtx sync.Mutex
        var merged arvados.SpecimenList
index 6a9ad8c15f3db2132bf5c122d8ae639764dbfff7..891e49180f89ba6d8441ea17d5c9082c73013c60 100644 (file)
@@ -587,139 +587,6 @@ func (s *FederationSuite) TestUpdateRemoteContainerRequest(c *check.C) {
        setPri(1) // Reset fixture so side effect doesn't break other tests.
 }
 
-func (s *FederationSuite) TestCreateContainerRequestBadToken(c *check.C) {
-       defer s.localServiceReturns404(c).Close()
-       // pass cluster_id via query parameter, this allows arvados-controller
-       // to avoid parsing the body
-       req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zzzzz",
-               strings.NewReader(`{"container_request":{}}`))
-       req.Header.Set("Authorization", "Bearer abcdefg")
-       req.Header.Set("Content-type", "application/json")
-       resp := s.testRequest(req).Result()
-       c.Check(resp.StatusCode, check.Equals, http.StatusForbidden)
-       var e map[string][]string
-       c.Check(json.NewDecoder(resp.Body).Decode(&e), check.IsNil)
-       c.Check(e["errors"], check.DeepEquals, []string{"invalid API token"})
-}
-
-func (s *FederationSuite) TestCreateRemoteContainerRequest(c *check.C) {
-       defer s.localServiceReturns404(c).Close()
-       // pass cluster_id via query parameter, this allows arvados-controller
-       // to avoid parsing the body
-       req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zzzzz",
-               strings.NewReader(`{
-  "container_request": {
-    "name": "hello world",
-    "state": "Uncommitted",
-    "output_path": "/",
-    "container_image": "123",
-    "command": ["abc"]
-  }
-}
-`))
-       req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
-       req.Header.Set("Content-type", "application/json")
-       resp := s.testRequest(req).Result()
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       var cr arvados.ContainerRequest
-       c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
-       c.Check(cr.Name, check.Equals, "hello world")
-       c.Check(strings.HasPrefix(cr.UUID, "zzzzz-"), check.Equals, true)
-}
-
-func (s *FederationSuite) TestCreateRemoteContainerRequestCheckRuntimeToken(c *check.C) {
-       // Send request to zmock and check that outgoing request has
-       // runtime_token set with a new random v2 token.
-
-       defer s.localServiceReturns404(c).Close()
-       // pass cluster_id via query parameter, this allows arvados-controller
-       // to avoid parsing the body
-       req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zmock",
-               strings.NewReader(`{
-  "container_request": {
-    "name": "hello world",
-    "state": "Uncommitted",
-    "output_path": "/",
-    "container_image": "123",
-    "command": ["abc"]
-  }
-}
-`))
-       req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveTokenV2)
-       req.Header.Set("Content-type", "application/json")
-
-       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
-       s.testHandler.Cluster.ClusterID = "zzzzz"
-
-       resp := s.testRequest(req).Result()
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       var cr struct {
-               arvados.ContainerRequest `json:"container_request"`
-       }
-       c.Check(json.NewDecoder(s.remoteMockRequests[0].Body).Decode(&cr), check.IsNil)
-       c.Check(strings.HasPrefix(cr.ContainerRequest.RuntimeToken, "v2/zzzzz-gj3su-"), check.Equals, true)
-       c.Check(cr.ContainerRequest.RuntimeToken, check.Not(check.Equals), arvadostest.ActiveTokenV2)
-}
-
-func (s *FederationSuite) TestCreateRemoteContainerRequestCheckSetRuntimeToken(c *check.C) {
-       // Send request to zmock and check that outgoing request has
-       // runtime_token set with the explicitly provided token.
-
-       defer s.localServiceReturns404(c).Close()
-       // pass cluster_id via query parameter, this allows arvados-controller
-       // to avoid parsing the body
-       req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zmock",
-               strings.NewReader(`{
-  "container_request": {
-    "name": "hello world",
-    "state": "Uncommitted",
-    "output_path": "/",
-    "container_image": "123",
-    "command": ["abc"],
-    "runtime_token": "xyz"
-  }
-}
-`))
-       req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
-       req.Header.Set("Content-type", "application/json")
-       resp := s.testRequest(req).Result()
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       var cr struct {
-               arvados.ContainerRequest `json:"container_request"`
-       }
-       c.Check(json.NewDecoder(s.remoteMockRequests[0].Body).Decode(&cr), check.IsNil)
-       c.Check(cr.ContainerRequest.RuntimeToken, check.Equals, "xyz")
-}
-
-func (s *FederationSuite) TestCreateRemoteContainerRequestRuntimeTokenFromAuth(c *check.C) {
-       // Send request to zmock and check that outgoing request has
-       // runtime_token set using the Auth token because the user is remote.
-
-       defer s.localServiceReturns404(c).Close()
-       // pass cluster_id via query parameter, this allows arvados-controller
-       // to avoid parsing the body
-       req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zmock",
-               strings.NewReader(`{
-  "container_request": {
-    "name": "hello world",
-    "state": "Uncommitted",
-    "output_path": "/",
-    "container_image": "123",
-    "command": ["abc"]
-  }
-}
-`))
-       req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveTokenV2+"/zzzzz-dz642-parentcontainer")
-       req.Header.Set("Content-type", "application/json")
-       resp := s.testRequest(req).Result()
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       var cr struct {
-               arvados.ContainerRequest `json:"container_request"`
-       }
-       c.Check(json.NewDecoder(s.remoteMockRequests[0].Body).Decode(&cr), check.IsNil)
-       c.Check(cr.ContainerRequest.RuntimeToken, check.Equals, arvadostest.ActiveTokenV2)
-}
-
 func (s *FederationSuite) TestCreateRemoteContainerRequestError(c *check.C) {
        defer s.localServiceReturns404(c).Close()
        // pass cluster_id via query parameter, this allows arvados-controller
index 25bba558dc68096796143b1d9bd4483d07a6f44f..2b2d72f4f22207c374e3f1e94e409cae13936218 100644 (file)
@@ -99,6 +99,8 @@ func (h *Handler) setup() {
                mux.Handle("/arvados/v1/collections/", rtr)
                mux.Handle("/arvados/v1/users", rtr)
                mux.Handle("/arvados/v1/users/", rtr)
+               mux.Handle("/arvados/v1/container_requests", rtr)
+               mux.Handle("/arvados/v1/container_requests/", rtr)
                mux.Handle("/login", rtr)
                mux.Handle("/logout", rtr)
        }
index 7d8266a85cab13af302cc22dcae44800465dca08..d12e4fa33d32a72d8f9b5342c94aab84664fbe03 100644 (file)
@@ -294,6 +294,8 @@ func (s *HandlerSuite) CheckObjectType(c *check.C, url string, token string, ski
        }
        resp2, err := client.Get(s.cluster.Services.RailsAPI.ExternalURL.String() + url + "/?api_token=" + token)
        c.Check(err, check.Equals, nil)
+       c.Assert(resp2.StatusCode, check.Equals, http.StatusOK,
+               check.Commentf("Wasn't able to get data from the RailsAPI at %q", url))
        defer resp2.Body.Close()
        db, err := ioutil.ReadAll(resp2.Body)
        c.Check(err, check.Equals, nil)
index 3da01ca6823562a6b13509adf58b9e621f704dec..6115ba5809ba4bf667c9247eb7e12320432651bc 100644 (file)
@@ -7,6 +7,7 @@ package controller
 import (
        "bytes"
        "context"
+       "database/sql"
        "encoding/json"
        "io"
        "io/ioutil"
@@ -383,6 +384,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")
@@ -409,10 +411,131 @@ 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.Check(err, check.IsNil)
-               c.Check(cr.UUID, check.Matches, "z2222-.*")
+       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) {
+       var (
+               body bytes.Buffer
+               resp *http.Response
+       )
+
+       conn1 := s.conn("z1111")
+       rootctx1, _, _ := s.rootClients("z1111")
+       _, ac1, _, au := s.userClients(rootctx1, c, conn1, "z1111", 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},
+       }
+
+       json.NewEncoder(&body).Encode(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.Bytes()))
+               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)
+       }
+}
+
+// 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) TestDatabaseConnection(c *check.C) {
+       ctx, cancel := context.WithCancel(context.Background())
+       defer cancel()
+       db, err := sql.Open("postgres", s.testClusters["z1111"].super.Cluster().PostgreSQL.Connection.String())
+       c.Assert(err, check.IsNil)
+       defer db.Close()
+       conn, err := db.Conn(ctx)
+       c.Assert(err, check.IsNil)
+       defer conn.Close()
+       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))
+}
+
+func (s *IntegrationSuite) TestRuntimeTokenInCR(c *check.C) {
+       conn1 := s.conn("z1111")
+       rootctx1, _, _ := s.rootClients("z1111")
+       _, ac1, _, au := s.userClients(rootctx1, c, conn1, "z1111", 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(rootctx1, arvados.CreateOptions{Attrs: rq})
+               if tt.expectAToGetAValidCR {
+                       c.Assert(err, check.IsNil)
+                       c.Assert(cr, check.NotNil)
+                       c.Assert(cr.UUID, check.Not(check.Equals), "")
+               }
+
+               if tt.expectedToken == nil {
+                       break
+               }
+
+               ctx2, cancel2 := context.WithCancel(context.Background())
+               defer cancel2()
+
+               db, err := sql.Open("postgres", s.testClusters["z1111"].super.Cluster().PostgreSQL.Connection.String())
+               c.Assert(err, check.IsNil)
+               defer db.Close()
+
+               conn, err := db.Conn(ctx2)
+               c.Assert(err, check.IsNil)
+               defer conn.Close()
+
+               c.Logf("cr.UUID: %s", cr.UUID)
+               row := conn.QueryRowContext(ctx2, `SELECT runtime_token from container_requests where uuid=$1`, cr.UUID)
+               c.Assert(row, check.NotNil)
+               // runtimeToken is *string and not a string because the database has a NULL column for this
+               var runtimeToken *string
+               err = row.Scan(&runtimeToken)
+               c.Assert(err, check.IsNil)
+               c.Assert(runtimeToken, check.NotNil)
+               c.Assert(*runtimeToken, check.DeepEquals, *tt.expectedToken)
        }
 }
 
index 4f0035edf993ad525c4d82b8d5e880049432c6c2..73bed2184624db6ef41e777706e278897ec71030 100644 (file)
@@ -24,21 +24,24 @@ func NewConn(cluster *arvados.Cluster) *Conn {
        railsProxy := railsproxy.NewConn(cluster)
        var conn Conn
        conn = Conn{
-               cluster:         cluster,
-               railsProxy:      railsProxy,
-               loginController: chooseLoginController(cluster, railsProxy),
+               cluster:    cluster,
+               railsProxy: railsProxy,
        }
+       conn.loginController = chooseLoginController(cluster, &conn)
        return &conn
 }
 
+// Logout handles the logout of conn giving to the appropiate loginController
 func (conn *Conn) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
        return conn.loginController.Logout(ctx, opts)
 }
 
+// Login handles the logout of conn giving to the appropiate loginController
 func (conn *Conn) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
        return conn.loginController.Login(ctx, opts)
 }
 
+// UserAuthenticate handles the User Authentication of conn giving to the appropiate loginController
 func (conn *Conn) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
        return conn.loginController.UserAuthenticate(ctx, opts)
 }
index f4632751e30dc24944d04157e939d676ee33c53a..61bad465ff2b6efb7653b952338772c3eff15af4 100644 (file)
@@ -27,7 +27,7 @@ type loginController interface {
        UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error)
 }
 
-func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) loginController {
+func chooseLoginController(cluster *arvados.Cluster, parent *Conn) loginController {
        wantGoogle := cluster.Login.Google.Enable
        wantOpenIDConnect := cluster.Login.OpenIDConnect.Enable
        wantSSO := cluster.Login.SSO.Enable
@@ -43,7 +43,7 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
        case wantGoogle:
                return &oidcLoginController{
                        Cluster:            cluster,
-                       RailsProxy:         railsProxy,
+                       Parent:             parent,
                        Issuer:             "https://accounts.google.com",
                        ClientID:           cluster.Login.Google.ClientID,
                        ClientSecret:       cluster.Login.Google.ClientSecret,
@@ -54,7 +54,7 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
        case wantOpenIDConnect:
                return &oidcLoginController{
                        Cluster:            cluster,
-                       RailsProxy:         railsProxy,
+                       Parent:             parent,
                        Issuer:             cluster.Login.OpenIDConnect.Issuer,
                        ClientID:           cluster.Login.OpenIDConnect.ClientID,
                        ClientSecret:       cluster.Login.OpenIDConnect.ClientSecret,
@@ -63,13 +63,13 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
                        UsernameClaim:      cluster.Login.OpenIDConnect.UsernameClaim,
                }
        case wantSSO:
-               return &ssoLoginController{railsProxy}
+               return &ssoLoginController{Parent: parent}
        case wantPAM:
-               return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &pamLoginController{Cluster: cluster, Parent: parent}
        case wantLDAP:
-               return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &ldapLoginController{Cluster: cluster, Parent: parent}
        case wantTest:
-               return &testLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &testLoginController{Cluster: cluster, Parent: parent}
        case wantLoginCluster:
                return &federatedLoginController{Cluster: cluster}
        default:
@@ -89,10 +89,16 @@ func countTrue(vals ...bool) int {
        return n
 }
 
-// Login and Logout are passed through to the wrapped railsProxy;
+// Login and Logout are passed through to the parent's railsProxy;
 // UserAuthenticate is rejected.
-type ssoLoginController struct{ *railsProxy }
+type ssoLoginController struct{ Parent *Conn }
 
+func (ctrl *ssoLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+       return ctrl.Parent.railsProxy.Login(ctx, opts)
+}
+func (ctrl *ssoLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+       return ctrl.Parent.railsProxy.Logout(ctx, opts)
+}
 func (ctrl *ssoLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
        return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(errors.New("username/password authentication is not available"), http.StatusBadRequest)
 }
@@ -135,9 +141,9 @@ func noopLogout(cluster *arvados.Cluster, opts arvados.LogoutOptions) (arvados.L
        return arvados.LogoutResponse{RedirectLocation: target}, nil
 }
 
-func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (resp arvados.APIClientAuthorization, err error) {
+func (conn *Conn) CreateAPIClientAuthorization(ctx context.Context, rootToken string, authinfo rpc.UserSessionAuthInfo) (resp arvados.APIClientAuthorization, err error) {
        ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{rootToken}})
-       newsession, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+       newsession, err := conn.railsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
                // Send a fake ReturnTo value instead of the caller's
                // opts.ReturnTo. We won't follow the resulting
                // redirect target anyway.
index 6c430d69bbfee5505c056430540000059c0b4c4b..49f557ae5b9ce50a8f7c3ceb59cc0fb31b50e187 100644 (file)
@@ -21,8 +21,8 @@ import (
 )
 
 type ldapLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *ldapLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -143,7 +143,7 @@ func (ctrl *ldapLoginController) UserAuthenticate(ctx context.Context, opts arva
                return arvados.APIClientAuthorization{}, errors.New("authentication succeeded but ldap returned no email address")
        }
 
-       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+       return ctrl.Parent.CreateAPIClientAuthorization(ctx, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                Email:     email,
                FirstName: attrs["givenname"],
                LastName:  attrs["sn"],
index bce1ecfcf260696247bb83de3cdce2fa9d27cabe..b8ba6b4676712efcad438bf9066195294b8702c3 100644 (file)
@@ -90,8 +90,8 @@ func (s *LDAPSuite) SetUpSuite(c *check.C) {
        s.cluster.Login.LDAP.SearchBase = "dc=example,dc=com"
        c.Assert(err, check.IsNil)
        s.ctrl = &ldapLoginController{
-               Cluster:    s.cluster,
-               RailsProxy: railsproxy.NewConn(s.cluster),
+               Cluster: s.cluster,
+               Parent:  &Conn{railsProxy: railsproxy.NewConn(s.cluster)},
        }
        s.db = arvadostest.DB(c, s.cluster)
 }
index 5f96da56244325d86b3e9d4f252ec714f55f534c..a41a93da1d1c440463ad5180749539ac6e326830 100644 (file)
@@ -46,7 +46,7 @@ const (
 
 type oidcLoginController struct {
        Cluster            *arvados.Cluster
-       RailsProxy         *railsProxy
+       Parent             *Conn
        Issuer             string // OIDC issuer URL, e.g., "https://accounts.google.com"
        ClientID           string
        ClientSecret       string
@@ -143,7 +143,7 @@ func (ctrl *oidcLoginController) Login(ctx context.Context, opts arvados.LoginOp
                return loginError(err)
        }
        ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{ctrl.Cluster.SystemRootToken}})
-       return ctrl.RailsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+       return ctrl.Parent.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
                ReturnTo: state.Remote + "," + state.ReturnTo,
                AuthInfo: *authinfo,
        })
index 2447713a2cf453ea05cfc29e2c643fa0713848a9..5d116a9e8f490be37225d0e3333ab6413c79e979 100644 (file)
@@ -20,8 +20,8 @@ import (
 )
 
 type pamLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *pamLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -87,7 +87,7 @@ func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvad
                "user":  user,
                "email": email,
        }).Debug("pam authentication succeeded")
-       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+       return ctrl.Parent.CreateAPIClientAuthorization(ctx, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                Username: user,
                Email:    email,
        })
index e6b967c9440b887cc6b2e68bd3ccb5e7a8fa78eb..c5876bbfad6280ad407aadece8cef5be606b187a 100644 (file)
@@ -36,8 +36,8 @@ func (s *PamSuite) SetUpSuite(c *check.C) {
        s.cluster.Login.PAM.DefaultEmailDomain = "example.com"
        s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
        s.ctrl = &pamLoginController{
-               Cluster:    s.cluster,
-               RailsProxy: rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider),
+               Cluster: s.cluster,
+               Parent:  &Conn{railsProxy: rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider)},
        }
 }
 
index 5852273529e6434b2f54ce7fcb551a85eb360880..c567a0668344b6eadc2d439f36a94147d3ae453f 100644 (file)
@@ -17,8 +17,8 @@ import (
 )
 
 type testLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *testLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -45,7 +45,7 @@ func (ctrl *testLoginController) UserAuthenticate(ctx context.Context, opts arva
                                "username": username,
                                "email":    user.Email,
                        }).Debug("test authentication succeeded")
-                       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+                       return ctrl.Parent.CreateAPIClientAuthorization(ctx, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                                Username: username,
                                Email:    user.Email,
                        })
index 7589088899744efca9187e2cc9d3094b8d39db03..7a520428b6d6836d9dafd6008ac9bf18433ca70f 100644 (file)
@@ -41,8 +41,8 @@ func (s *TestUserSuite) SetUpSuite(c *check.C) {
        }
        s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
        s.ctrl = &testLoginController{
-               Cluster:    s.cluster,
-               RailsProxy: rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider),
+               Cluster: s.cluster,
+               Parent:  &Conn{railsProxy: rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider)},
        }
        s.db = arvadostest.DB(c, s.cluster)
 }
index 543e25d0ce44f73c6cd87cc221aaace564f41a2c..055595c8eb26f206de60b27f2938d81d505e0ff9 100644 (file)
@@ -106,27 +106,43 @@ func (rtr *router) sendResponse(w http.ResponseWriter, req *http.Request, resp i
                tmp = applySelectParam(opts.Select, tmp)
        }
 
-       // Format non-nil timestamps as rfc3339NanoFixed (by default
-       // they will have been encoded to time.RFC3339Nano, which
-       // omits trailing zeroes).
        for k, v := range tmp {
-               if !strings.HasSuffix(k, "_at") {
-                       continue
+               if strings.HasSuffix(k, "_at") {
+                       // Format non-nil timestamps as
+                       // rfc3339NanoFixed (by default they will have
+                       // been encoded to time.RFC3339Nano, which
+                       // omits trailing zeroes).
+                       switch tv := v.(type) {
+                       case *time.Time:
+                               if tv == nil {
+                                       break
+                               }
+                               tmp[k] = tv.Format(rfc3339NanoFixed)
+                       case time.Time:
+                               if tv.IsZero() {
+                                       tmp[k] = nil
+                               } else {
+                                       tmp[k] = tv.Format(rfc3339NanoFixed)
+                               }
+                       case string:
+                               t, err := time.Parse(time.RFC3339Nano, tv)
+                               if err != nil {
+                                       break
+                               }
+                               tmp[k] = t.Format(rfc3339NanoFixed)
+                       }
                }
-               switch tv := v.(type) {
-               case *time.Time:
-                       if tv == nil {
-                               break
+               switch k {
+               // in all this cases, RoR returns nil instead the Zero value for the type.
+               // Maytbe, this should all go away when RoR is out of the picture.
+               case "output_uuid", "output_name", "log_uuid", "modified_by_client_uuid", "description", "requesting_container_uuid", "expires_at":
+                       if v == "" {
+                               tmp[k] = nil
                        }
-                       tmp[k] = tv.Format(rfc3339NanoFixed)
-               case time.Time:
-                       tmp[k] = tv.Format(rfc3339NanoFixed)
-               case string:
-                       t, err := time.Parse(time.RFC3339Nano, tv)
-                       if err != nil {
-                               break
+               case "container_count_max":
+                       if v == float64(0) {
+                               tmp[k] = nil
                        }
-                       tmp[k] = t.Format(rfc3339NanoFixed)
                }
        }
        w.Header().Set("Content-Type", "application/json")
index 2944524344e9028fa22cf0c9d18327cb39193733..9fb2a0d32b49b5af5c6337cc2187c94b7d86a991 100644 (file)
@@ -168,6 +168,41 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
+               {
+                       arvados.EndpointContainerRequestCreate,
+                       func() interface{} { return &arvados.CreateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerRequestCreate(ctx, *opts.(*arvados.CreateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerRequestUpdate,
+                       func() interface{} { return &arvados.UpdateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerRequestUpdate(ctx, *opts.(*arvados.UpdateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerRequestGet,
+                       func() interface{} { return &arvados.GetOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerRequestGet(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerRequestList,
+                       func() interface{} { return &arvados.ListOptions{Limit: -1} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerRequestList(ctx, *opts.(*arvados.ListOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerRequestDelete,
+                       func() interface{} { return &arvados.DeleteOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerRequestDelete(ctx, *opts.(*arvados.DeleteOptions))
+                       },
+               },
                {
                        arvados.EndpointContainerLock,
                        func() interface{} {
index cd98b64718a0b1f52702f4997f432d3d5210f353..5ffa668010e911398ba4f9a8c3cd583a86c0336f 100644 (file)
@@ -286,6 +286,41 @@ func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOption
        return resp, err
 }
 
+func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
+       ep := arvados.EndpointContainerRequestCreate
+       var resp arvados.ContainerRequest
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
+       ep := arvados.EndpointContainerRequestUpdate
+       var resp arvados.ContainerRequest
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
+       ep := arvados.EndpointContainerRequestGet
+       var resp arvados.ContainerRequest
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
+       ep := arvados.EndpointContainerRequestList
+       var resp arvados.ContainerRequestList
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
+       ep := arvados.EndpointContainerRequestDelete
+       var resp arvados.ContainerRequest
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
        ep := arvados.EndpointSpecimenCreate
        var resp arvados.Specimen
@@ -402,6 +437,7 @@ func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arv
 }
 
 type UserSessionAuthInfo struct {
+       UserUUID        string   `json:"user_uuid"`
        Email           string   `json:"email"`
        AlternateEmails []string `json:"alternate_emails"`
        FirstName       string   `json:"first_name"`
index 5a2cfb8800402496f3f8fe400cf38c786e57d6eb..a11872971a9dffc9a2563dedb7338a1438ca40b3 100644 (file)
@@ -41,6 +41,11 @@ var (
        EndpointContainerDelete               = APIEndpoint{"DELETE", "arvados/v1/containers/{uuid}", ""}
        EndpointContainerLock                 = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/lock", ""}
        EndpointContainerUnlock               = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/unlock", ""}
+       EndpointContainerRequestCreate        = APIEndpoint{"POST", "arvados/v1/container_requests", "container_request"}
+       EndpointContainerRequestUpdate        = APIEndpoint{"PATCH", "arvados/v1/container_requests/{uuid}", "container_request"}
+       EndpointContainerRequestGet           = APIEndpoint{"GET", "arvados/v1/container_requests/{uuid}", ""}
+       EndpointContainerRequestList          = APIEndpoint{"GET", "arvados/v1/container_requests", ""}
+       EndpointContainerRequestDelete        = APIEndpoint{"DELETE", "arvados/v1/container_requests/{uuid}", ""}
        EndpointUserActivate                  = APIEndpoint{"POST", "arvados/v1/users/{uuid}/activate", ""}
        EndpointUserCreate                    = APIEndpoint{"POST", "arvados/v1/users", "user"}
        EndpointUserCurrent                   = APIEndpoint{"GET", "arvados/v1/users/current", ""}
@@ -175,6 +180,11 @@ type API interface {
        ContainerDelete(ctx context.Context, options DeleteOptions) (Container, error)
        ContainerLock(ctx context.Context, options GetOptions) (Container, error)
        ContainerUnlock(ctx context.Context, options GetOptions) (Container, error)
+       ContainerRequestCreate(ctx context.Context, options CreateOptions) (ContainerRequest, error)
+       ContainerRequestUpdate(ctx context.Context, options UpdateOptions) (ContainerRequest, error)
+       ContainerRequestGet(ctx context.Context, options GetOptions) (ContainerRequest, error)
+       ContainerRequestList(ctx context.Context, options ListOptions) (ContainerRequestList, error)
+       ContainerRequestDelete(ctx context.Context, options DeleteOptions) (ContainerRequest, error)
        SpecimenCreate(ctx context.Context, options CreateOptions) (Specimen, error)
        SpecimenUpdate(ctx context.Context, options UpdateOptions) (Specimen, error)
        SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
index 265944e81d52fdab08d55e767b9626a52f40c3c2..433db3813c1dfa6fbfbe1ed73ff11863c11cbec3 100644 (file)
@@ -48,7 +48,7 @@ type ContainerRequest struct {
        Properties              map[string]interface{} `json:"properties"`
        State                   ContainerRequestState  `json:"state"`
        RequestingContainerUUID string                 `json:"requesting_container_uuid"`
-       ContainerUUID           string                 `json:"container_uuid"`
+       ContainerUUID           *string                `json:"container_uuid"`
        ContainerCountMax       int                    `json:"container_count_max"`
        Mounts                  map[string]Mount       `json:"mounts"`
        RuntimeConstraints      RuntimeConstraints     `json:"runtime_constraints"`
@@ -65,6 +65,9 @@ type ContainerRequest struct {
        LogUUID                 string                 `json:"log_uuid"`
        OutputUUID              string                 `json:"output_uuid"`
        RuntimeToken            string                 `json:"runtime_token"`
+       ExpiresAt               string                 `json:"expires_at"`
+       Filters                 []string               `json:"filters"`
+       ContainerCount          int                    `json:"container_count"`
 }
 
 // Mount is special behavior to attach to a filesystem path or device.
@@ -86,18 +89,18 @@ type Mount struct {
 // RuntimeConstraints specify a container's compute resources (RAM,
 // CPU) and network connectivity.
 type RuntimeConstraints struct {
-       API          *bool
-       RAM          int64 `json:"ram"`
-       VCPUs        int   `json:"vcpus"`
-       KeepCacheRAM int64 `json:"keep_cache_ram"`
+       API          *bool `json:",omitempty"`
+       RAM          int64 `json:"ram,omitempty"`
+       VCPUs        int   `json:"vcpus,omitempty"`
+       KeepCacheRAM int64 `json:"keep_cache_ram,omitempty"`
 }
 
 // SchedulingParameters specify a container's scheduling parameters
 // such as Partitions
 type SchedulingParameters struct {
-       Partitions  []string `json:"partitions"`
-       Preemptible bool     `json:"preemptible"`
-       MaxRunTime  int      `json:"max_run_time"`
+       Partitions  []string `json:"partitions,omitempty"`
+       Preemptible bool     `json:"preemptible,omitempty"`
+       MaxRunTime  int      `json:"max_run_time,omitempty"`
 }
 
 // ContainerList is an arvados#containerList resource.
index fa5f53936028504b9dd8f4bcc41f1304dd36656e..ce0a913b5fc6d52eda464e1b67f9ecdf4caa6597 100644 (file)
@@ -105,6 +105,26 @@ func (as *APIStub) ContainerUnlock(ctx context.Context, options arvados.GetOptio
        as.appendCall(as.ContainerUnlock, ctx, options)
        return arvados.Container{}, as.Error
 }
+func (as *APIStub) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
+       as.appendCall(as.ContainerRequestCreate, ctx, options)
+       return arvados.ContainerRequest{}, as.Error
+}
+func (as *APIStub) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
+       as.appendCall(as.ContainerRequestUpdate, ctx, options)
+       return arvados.ContainerRequest{}, as.Error
+}
+func (as *APIStub) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
+       as.appendCall(as.ContainerRequestGet, ctx, options)
+       return arvados.ContainerRequest{}, as.Error
+}
+func (as *APIStub) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
+       as.appendCall(as.ContainerRequestList, ctx, options)
+       return arvados.ContainerRequestList{}, as.Error
+}
+func (as *APIStub) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
+       as.appendCall(as.ContainerRequestDelete, ctx, options)
+       return arvados.ContainerRequest{}, as.Error
+}
 func (as *APIStub) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
        as.appendCall(as.SpecimenCreate, ctx, options)
        return arvados.Specimen{}, as.Error
index 8e3c3ac5e3d8b8656d587e86626f86f57c33b045..0bf939d080e497233c540711c0204289f0628f13 100644 (file)
@@ -30,12 +30,20 @@ class UserSessionsController < ApplicationController
       authinfo = request.env['omniauth.auth']['info'].with_indifferent_access
     end
 
-    begin
-      user = User.register(authinfo)
-    rescue => e
-      Rails.logger.warn "User.register error #{e}"
-      Rails.logger.warn "authinfo was #{authinfo.inspect}"
-      return redirect_to login_failure_url
+    if !authinfo['user_uuid'].blank?
+      user = User.find_by_uuid(authinfo['user_uuid'])
+      if !user
+        Rails.logger.warn "Nonexistent user_uuid in authinfo #{authinfo.inspect}"
+        return redirect_to login_failure_url
+      end
+    else
+      begin
+        user = User.register(authinfo)
+      rescue => e
+        Rails.logger.warn "User.register error #{e}"
+        Rails.logger.warn "authinfo was #{authinfo.inspect}"
+        return redirect_to login_failure_url
+      end
     end
 
     # For the benefit of functional and integration tests:
index ea86dca1784834d7ca0c37838c743aa785812a7b..daaaf4d08448fa9cd1dba3aa1a67ef64cb50f7e7 100644 (file)
@@ -20,6 +20,7 @@ queued:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 running:
   uuid: zzzzz-xvhdp-cr4runningcntnr
@@ -39,6 +40,7 @@ running:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 requester_for_running:
   uuid: zzzzz-xvhdp-req4runningcntr
@@ -59,6 +61,7 @@ requester_for_running:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 running_older:
   uuid: zzzzz-xvhdp-cr4runningcntn2
@@ -78,6 +81,7 @@ running_older:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 completed:
   uuid: zzzzz-xvhdp-cr4completedctr
@@ -99,6 +103,7 @@ completed:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 completed-older:
   uuid: zzzzz-xvhdp-cr4completedcr2
@@ -118,6 +123,7 @@ completed-older:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 requester:
   uuid: zzzzz-xvhdp-9zacv3o1xw6sxz5
@@ -137,6 +143,7 @@ requester:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 cr_for_requester:
   uuid: zzzzz-xvhdp-cr4requestercnt
@@ -157,6 +164,7 @@ cr_for_requester:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 cr_for_requester2:
   uuid: zzzzz-xvhdp-cr4requestercn2
@@ -176,6 +184,7 @@ cr_for_requester2:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 running_anonymous_accessible:
   uuid: zzzzz-xvhdp-runninganonaccs
@@ -195,6 +204,7 @@ running_anonymous_accessible:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 cr_for_failed:
   uuid: zzzzz-xvhdp-cr4failedcontnr
@@ -214,6 +224,7 @@ cr_for_failed:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 canceled_with_queued_container:
   uuid: zzzzz-xvhdp-canceledqueuedc
@@ -233,6 +244,7 @@ canceled_with_queued_container:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 canceled_with_locked_container:
   uuid: zzzzz-xvhdp-canceledlocekdc
@@ -252,6 +264,7 @@ canceled_with_locked_container:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 canceled_with_running_container:
   uuid: zzzzz-xvhdp-canceledrunning
@@ -271,6 +284,7 @@ canceled_with_running_container:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 running_to_be_deleted:
   uuid: zzzzz-xvhdp-cr5runningcntnr
@@ -290,6 +304,7 @@ running_to_be_deleted:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 completed_with_input_mounts:
   uuid: zzzzz-xvhdp-crwithinputmnts
@@ -311,18 +326,23 @@ completed_with_input_mounts:
   container_uuid: zzzzz-dz642-compltcontainer
   log_uuid: zzzzz-4zz18-y9vne9npefyxh8g
   output_uuid: zzzzz-4zz18-znfnqtbbv4spc3w
-  mounts:
-    /var/lib/cwl/cwl.input.json:
-      content:
-        input1:
-          basename: foo
-          class: File
-          location: "keep:fa7aeb5140e2848d39b416daeef4ffc5+45/foo"
-        input2:
-          basename: bar
-          class: File
-          location: "keep:fa7aeb5140e2848d39b416daeef4ffc5+45/bar"
-    /var/lib/cwl/workflow.json: "keep:f9ddda46bb293b6847da984e3aa735db+290"
+  mounts: {
+    "/var/lib/cwl/cwl.input.json": {
+      "kind": "json",
+      "content": {
+        "input1": {
+          "basename": "foo",
+          "class": "File",
+          "location": "keep:fa7aeb5140e2848d39b416daeef4ffc5+45/foo",
+        },
+        "input2": {
+          "basename": "bar",
+          "class": "File",
+          "location": "keep:fa7aeb5140e2848d39b416daeef4ffc5+45/bar",
+        }
+      }
+    }
+  }
 
 uncommitted:
   uuid: zzzzz-xvhdp-cr4uncommittedc
@@ -763,6 +783,7 @@ cr_in_trashed_project:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 runtime_token:
   uuid: zzzzz-xvhdp-11eklkhy0n4dm86
@@ -783,6 +804,7 @@ runtime_token:
   runtime_constraints:
     vcpus: 1
     ram: 123
+  mounts: {}
 
 
 # Test Helper trims the rest of the file
@@ -798,6 +820,7 @@ cr_<%=i%>_of_60:
   name: cr-<%= i.to_s %>
   output_path: test
   command: ["echo", "hello"]
+  mounts: {}
 <% end %>
 
 # Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper