From 9e312753855286a7e21c83218bb48589f4d1535d Mon Sep 17 00:00:00 2001 From: Nico Cesar Date: Tue, 10 Nov 2020 09:00:52 -0500 Subject: [PATCH] container requests in the new codepath for controller 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 --- .../container_requests_controller_test.rb | 1 - lib/boot/supervisor.go | 2 + lib/controller/cmd.go | 1 + lib/controller/fed_containers.go | 1 - lib/controller/federation.go | 6 + lib/controller/federation/conn.go | 60 ++++++++ lib/controller/federation/generate.go | 2 +- lib/controller/federation/generated.go | 41 ++++++ lib/controller/federation_test.go | 133 ------------------ lib/controller/handler.go | 2 + lib/controller/handler_test.go | 2 + lib/controller/integration_test.go | 131 ++++++++++++++++- lib/controller/localdb/conn.go | 9 +- lib/controller/localdb/login.go | 28 ++-- lib/controller/localdb/login_ldap.go | 6 +- lib/controller/localdb/login_ldap_test.go | 4 +- lib/controller/localdb/login_oidc.go | 4 +- lib/controller/localdb/login_pam.go | 6 +- lib/controller/localdb/login_pam_test.go | 4 +- lib/controller/localdb/login_testuser.go | 6 +- lib/controller/localdb/login_testuser_test.go | 4 +- lib/controller/router/response.go | 50 ++++--- lib/controller/router/router.go | 35 +++++ lib/controller/rpc/conn.go | 36 +++++ sdk/go/arvados/api.go | 10 ++ sdk/go/arvados/container.go | 19 +-- sdk/go/arvadostest/api.go | 20 +++ .../controllers/user_sessions_controller.rb | 20 ++- .../api/test/fixtures/container_requests.yml | 47 +++++-- 29 files changed, 476 insertions(+), 214 deletions(-) diff --git a/apps/workbench/test/controllers/container_requests_controller_test.rb b/apps/workbench/test/controllers/container_requests_controller_test.rb index 73d357f3a6..c8709df3c3 100644 --- a/apps/workbench/test/controllers/container_requests_controller_test.rb +++ b/apps/workbench/test/controllers/container_requests_controller_test.rb @@ -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 diff --git a/lib/boot/supervisor.go b/lib/boot/supervisor.go index 1e8e83ff3b..0e4de9d5c1 100644 --- a/lib/boot/supervisor.go +++ b/lib/boot/supervisor.go @@ -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{}) diff --git a/lib/controller/cmd.go b/lib/controller/cmd.go index 0c46e857b3..c6b53eb8f6 100644 --- a/lib/controller/cmd.go +++ b/lib/controller/cmd.go @@ -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 { diff --git a/lib/controller/fed_containers.go b/lib/controller/fed_containers.go index 51f243e69e..028f4f5976 100644 --- a/lib/controller/fed_containers.go +++ b/lib/controller/fed_containers.go @@ -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) diff --git a/lib/controller/federation.go b/lib/controller/federation.go index cab5e4c4ca..869fec58e4 100644 --- a/lib/controller/federation.go +++ b/lib/controller/federation.go @@ -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) diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go index 986faa7b05..446e3893d1 100644 --- a/lib/controller/federation/conn.go +++ b/lib/controller/federation/conn.go @@ -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) } diff --git a/lib/controller/federation/generate.go b/lib/controller/federation/generate.go index ab5d9966a4..9ce7fdcb21 100644 --- a/lib/controller/federation/generate.go +++ b/lib/controller/federation/generate.go @@ -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) diff --git a/lib/controller/federation/generated.go b/lib/controller/federation/generated.go index 8745f3b973..ab9db93a4d 100755 --- a/lib/controller/federation/generated.go +++ b/lib/controller/federation/generated.go @@ -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 diff --git a/lib/controller/federation_test.go b/lib/controller/federation_test.go index 6a9ad8c15f..891e49180f 100644 --- a/lib/controller/federation_test.go +++ b/lib/controller/federation_test.go @@ -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 diff --git a/lib/controller/handler.go b/lib/controller/handler.go index 25bba558dc..2b2d72f4f2 100644 --- a/lib/controller/handler.go +++ b/lib/controller/handler.go @@ -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) } diff --git a/lib/controller/handler_test.go b/lib/controller/handler_test.go index 7d8266a85c..d12e4fa33d 100644 --- a/lib/controller/handler_test.go +++ b/lib/controller/handler_test.go @@ -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) diff --git a/lib/controller/integration_test.go b/lib/controller/integration_test.go index 3da01ca682..6115ba5809 100644 --- a/lib/controller/integration_test.go +++ b/lib/controller/integration_test.go @@ -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) } } diff --git a/lib/controller/localdb/conn.go b/lib/controller/localdb/conn.go index 4f0035edf9..73bed21846 100644 --- a/lib/controller/localdb/conn.go +++ b/lib/controller/localdb/conn.go @@ -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) } diff --git a/lib/controller/localdb/login.go b/lib/controller/localdb/login.go index f4632751e3..61bad465ff 100644 --- a/lib/controller/localdb/login.go +++ b/lib/controller/localdb/login.go @@ -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. diff --git a/lib/controller/localdb/login_ldap.go b/lib/controller/localdb/login_ldap.go index 6c430d69bb..49f557ae5b 100644 --- a/lib/controller/localdb/login_ldap.go +++ b/lib/controller/localdb/login_ldap.go @@ -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"], diff --git a/lib/controller/localdb/login_ldap_test.go b/lib/controller/localdb/login_ldap_test.go index bce1ecfcf2..b8ba6b4676 100644 --- a/lib/controller/localdb/login_ldap_test.go +++ b/lib/controller/localdb/login_ldap_test.go @@ -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) } diff --git a/lib/controller/localdb/login_oidc.go b/lib/controller/localdb/login_oidc.go index 5f96da5624..a41a93da1d 100644 --- a/lib/controller/localdb/login_oidc.go +++ b/lib/controller/localdb/login_oidc.go @@ -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, }) diff --git a/lib/controller/localdb/login_pam.go b/lib/controller/localdb/login_pam.go index 2447713a2c..5d116a9e8f 100644 --- a/lib/controller/localdb/login_pam.go +++ b/lib/controller/localdb/login_pam.go @@ -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, }) diff --git a/lib/controller/localdb/login_pam_test.go b/lib/controller/localdb/login_pam_test.go index e6b967c944..c5876bbfad 100644 --- a/lib/controller/localdb/login_pam_test.go +++ b/lib/controller/localdb/login_pam_test.go @@ -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)}, } } diff --git a/lib/controller/localdb/login_testuser.go b/lib/controller/localdb/login_testuser.go index 5852273529..c567a06683 100644 --- a/lib/controller/localdb/login_testuser.go +++ b/lib/controller/localdb/login_testuser.go @@ -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, }) diff --git a/lib/controller/localdb/login_testuser_test.go b/lib/controller/localdb/login_testuser_test.go index 7589088899..7a520428b6 100644 --- a/lib/controller/localdb/login_testuser_test.go +++ b/lib/controller/localdb/login_testuser_test.go @@ -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) } diff --git a/lib/controller/router/response.go b/lib/controller/router/response.go index 543e25d0ce..055595c8eb 100644 --- a/lib/controller/router/response.go +++ b/lib/controller/router/response.go @@ -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") diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go index 2944524344..9fb2a0d32b 100644 --- a/lib/controller/router/router.go +++ b/lib/controller/router/router.go @@ -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{} { diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go index cd98b64718..5ffa668010 100644 --- a/lib/controller/rpc/conn.go +++ b/lib/controller/rpc/conn.go @@ -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"` diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go index 5a2cfb8800..a11872971a 100644 --- a/sdk/go/arvados/api.go +++ b/sdk/go/arvados/api.go @@ -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) diff --git a/sdk/go/arvados/container.go b/sdk/go/arvados/container.go index 265944e81d..433db3813c 100644 --- a/sdk/go/arvados/container.go +++ b/sdk/go/arvados/container.go @@ -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. diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go index fa5f539360..ce0a913b5f 100644 --- a/sdk/go/arvadostest/api.go +++ b/sdk/go/arvadostest/api.go @@ -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 diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb index 8e3c3ac5e3..0bf939d080 100644 --- a/services/api/app/controllers/user_sessions_controller.rb +++ b/services/api/app/controllers/user_sessions_controller.rb @@ -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: diff --git a/services/api/test/fixtures/container_requests.yml b/services/api/test/fixtures/container_requests.yml index ea86dca178..daaaf4d084 100644 --- a/services/api/test/fixtures/container_requests.yml +++ b/services/api/test/fixtures/container_requests.yml @@ -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 -- 2.30.2