Merge branch '16470-api-rails-52'
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 17 Aug 2020 19:48:31 +0000 (16:48 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 17 Aug 2020 19:48:31 +0000 (16:48 -0300)
Refs #16470

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

38 files changed:
apps/workbench/app/views/application/_show_sharing.html.erb
doc/admin/upgrading.html.textile.liquid
doc/install/install-keep-web.html.textile.liquid
doc/install/install-keepproxy.html.textile.liquid
doc/user/cwl/cwl-versions.html.textile.liquid
lib/config/deprecated_test.go
lib/config/load_test.go
lib/controller/federation/generated.go
lib/controller/federation/list.go
lib/controller/integration_test.go
lib/controller/localdb/login_ldap_test.go
lib/controller/router/router_test.go
lib/controller/rpc/conn_test.go
lib/dispatchcloud/scheduler/run_queue_test.go
lib/dispatchcloud/scheduler/sync.go
lib/dispatchcloud/scheduler/sync_test.go
lib/dispatchcloud/worker/pool_test.go
lib/pam/pam_arvados.go
lib/recovercollection/cmd_test.go
lib/service/cmd_test.go
sdk/cwl/arvados_cwl/__init__.py
sdk/cwl/arvados_cwl/arv-cwl-schema-v1.2.yml [new file with mode: 0644]
sdk/cwl/setup.py
sdk/go/arvados/api.go
sdk/go/arvados/duration_test.go
sdk/go/arvados/fs_project_test.go
sdk/go/arvados/fs_site_test.go
sdk/go/arvadosclient/arvadosclient_test.go
sdk/go/httpserver/logger_test.go
sdk/go/keepclient/collectionreader_test.go
sdk/go/keepclient/keepclient_test.go
sdk/python/tests/fed-migrate/check.py
services/api/app/models/arvados_model.rb
services/api/app/models/link.rb
services/api/test/unit/link_test.rb
services/keep-balance/main_test.go
services/keep/tools/traffic_test.py [deleted file]
services/keepstore/s3aws_volume_test.go

index 7877e60d3018096bdc3ad6b9ef4c4bd892631925..75773ab90082ba0a79a64ba49b497216e1087094 100644 (file)
@@ -8,6 +8,8 @@ SPDX-License-Identifier: AGPL-3.0 %>
      [User, Group].each do |type|
        type
          .filter([['uuid','in',@share_links.collect(&:tail_uuid)]])
+         .with_count("none")
+         .fetch_multiple_pages(false)
          .each do |o|
          uuid_map[o.uuid] = o
        end
index db1f47fced057a993ae008c449b5ee3f55ca9844..84ef780faa09354037151f054fdb15daf9ba937f 100644 (file)
@@ -10,14 +10,14 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-What you need to know and do in order to upgrade your Arvados installation.
+For Arvados administrators, this page will cover what you need to know and do in order to ensure a smooth upgrade of your Arvados installation.  For general release notes covering features added and bugs fixed, see "Arvados releases":https://arvados.org/releases .
 
 h2. General process
 
 # Consult upgrade notes below to see if any manual configuration updates are necessary.
 # Wait for the cluster to be idle and stop Arvados services.
 # Install new packages using @apt-get upgrade@ or @yum upgrade@.
-# Package installation scripts will perform any necessary data migrations.
+# Wait for package installation scripts as they perform any necessary data migrations.
 # Restart Arvados services.
 
 h2. Upgrade notes
index b31827bf70ed6c0a062cef1321cce68c393caaba..24f37bfb4f8ee25b3b32b691624e06586f9b42d1 100644 (file)
@@ -142,6 +142,7 @@ server {
     client_max_body_size    0;
     proxy_http_version      1.1;
     proxy_request_buffering off;
+    proxy_max_temp_file_size 0;
   }
 }
 </pre></notextile>
index ae6bd3989c340cbc64bb67932d9c1c3d8a8121e9..b4edd4f57b6e682560f8da16759b6042921dd0c3 100644 (file)
@@ -66,6 +66,7 @@ server {
   proxy_set_header        X-Real-IP $remote_addr;
   proxy_http_version      1.1;
   proxy_request_buffering off;
+  proxy_max_temp_file_size 0;
 
   ssl_certificate     <span class="userinput">/YOUR/PATH/TO/cert.pem</span>;
   ssl_certificate_key <span class="userinput">/YOUR/PATH/TO/cert.key</span>;
index 7bb9fdcbe602c5009242798295b043cfb2508204..5fcfcbe3bc3e8c00b3e4f20467ad224271e47c97 100644 (file)
@@ -9,7 +9,13 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-h2. Upgrading to CWL v1.1
+h2(#v12). Upgrading your workflows to CWL v1.2
+
+If you are starting from a CWL v1.0 document, see "Upgrading your workflows to CWL v1.1":#v11 below.
+
+If you are starting from a CWL v1.1 document, you should be able to trivially change @cwlVersion: v1.1@ to @cwlVersion: v1.2@ to be able to take advantage of the new features of v1.2, such as conditional workflow steps.
+
+h2(#v11). Upgrading your workflows to CWL v1.1
 
 CWL v1.1 introduces several features to the standard that were previously available as Arvados extensions.  CWL v1.1 syntax is backwards compatible with v1.0, so you can just change @cwlVersion: v1.0@ to @cwlVersion: v1.1@ and update your script to using the standard features.  On Arvados, there is only one behavior change between CWL v1.0 and v1.1 to be aware of: for performance reasons, Directory listings are no longer loaded by default.  To control loading Directory listings, use "loadListing":https://www.commonwl.org/v1.1/CommandLineTool.html#CommandInputParameter or "LoadListingRequirement":https://www.commonwl.org/v1.1/CommandLineTool.html#LoadListingRequirement (the extension @cwltool:LoadListingRequirement@ is deprecated.)
 
index 87e26fd09672805aa5bd840b757df71778477057..ca376ba0bb233f56f6606adb8404d1e39bffa4aa 100644 (file)
@@ -205,6 +205,7 @@ func (s *LoadSuite) TestLegacyKeepproxyConfig(c *check.C) {
 
        content = []byte(fmtKeepproxyConfig("", false))
        cluster, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.IsNil)
        c.Check(cluster.SystemLogs.LogLevel, check.Equals, "info")
 
        content = []byte(fmtKeepproxyConfig(`"DisableGet": true,`, true))
index 5e912f91aa835e51cec5210b1d6b52c1a9016e39..58ddf950efdd82d1f1a2f26b4d3b4a248f116764 100644 (file)
@@ -164,6 +164,7 @@ func (s *LoadSuite) TestSampleKeys(c *check.C) {
                cfg, err := testLoader(c, yaml, nil).Load()
                c.Assert(err, check.IsNil)
                cc, err := cfg.GetCluster("z1111")
+               c.Assert(err, check.IsNil)
                _, hasSample := cc.InstanceTypes["SAMPLE"]
                c.Check(hasSample, check.Equals, false)
                if strings.Contains(yaml, "Foo") {
index 20edd90b95dad70791b21cf4f19bfce6b496bff4..8745f3b9730b068faa2cc9e8a02d4c5638c7164a 100755 (executable)
@@ -23,6 +23,7 @@ func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.L
        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.ContainerList(ctx, options)
                if err != nil {
                        return nil, err
@@ -63,6 +64,7 @@ func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.Li
        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.SpecimenList(ctx, options)
                if err != nil {
                        return nil, err
@@ -103,6 +105,7 @@ func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOp
        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.UserList(ctx, options)
                if err != nil {
                        return nil, err
index 0a596eb9cb6ac6aac690dc19a2e43ca3dc723340..bc6d3e00a493361b4d9897aeab77e3abf5cced27 100644 (file)
@@ -27,6 +27,7 @@ func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.
        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.CollectionList(ctx, options)
                if err != nil {
                        return nil, err
@@ -107,7 +108,7 @@ func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.
 // backend.
 func (conn *Conn) splitListRequest(ctx context.Context, opts arvados.ListOptions, fn func(context.Context, string, arvados.API, arvados.ListOptions) ([]string, error)) error {
 
-       if opts.BypassFederation {
+       if opts.BypassFederation || opts.ForwardedFor != "" {
                // Client requested no federation.  Pass through.
                _, err := fn(ctx, conn.cluster.ClusterID, conn.local, opts)
                return err
@@ -249,7 +250,7 @@ func (conn *Conn) splitListRequest(ctx context.Context, opts arvados.ListOptions
 
                                done, err := fn(ctx, clusterID, backend, remoteOpts)
                                if err != nil {
-                                       errs <- httpErrorf(http.StatusBadGateway, err.Error())
+                                       errs <- httpErrorf(http.StatusBadGateway, "%s", err.Error())
                                        return
                                }
                                progress := false
index 3bf64771d70b30d08d6c53312384071bef14a259..a73f5f9f828574b1c234932432a2a4b63c769087 100644 (file)
@@ -300,6 +300,7 @@ func (s *IntegrationSuite) TestCreateContainerRequestWithFedToken(c *check.C) {
        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-.*")
        }
 }
index 0c94fa6c0e21be72949f6fd5b402ae252d7ce1cc..700d757c274d707c703ad0c58dbac812440a45a6 100644 (file)
@@ -49,6 +49,7 @@ func (s *LDAPSuite) SetUpSuite(c *check.C) {
        c.Assert(err, check.IsNil)
 
        ln, err := net.Listen("tcp", "127.0.0.1:0")
+       c.Assert(err, check.IsNil)
        s.ldap = &godap.LDAPServer{
                Listener: ln,
                Handlers: []godap.LDAPRequestHandler{
index c73bc64915f12aff293f23c803e25771669fe8a9..18fff7c9cc4f5d4a10f347c3da55ab79ca1bf38d 100644 (file)
@@ -273,7 +273,7 @@ func (s *RouterIntegrationSuite) TestContainerLock(c *check.C) {
        c.Check(rr.Code, check.Equals, http.StatusOK)
        c.Check(jresp["uuid"], check.HasLen, 27)
        c.Check(jresp["state"], check.Equals, "Locked")
-       _, rr, jresp = doRequest(c, s.rtr, token, "POST", "/arvados/v1/containers/"+uuid+"/lock", nil, nil)
+       _, rr, _ = doRequest(c, s.rtr, token, "POST", "/arvados/v1/containers/"+uuid+"/lock", nil, nil)
        c.Check(rr.Code, check.Equals, http.StatusUnprocessableEntity)
        c.Check(rr.Body.String(), check.Not(check.Matches), `.*"uuid":.*`)
        _, rr, jresp = doRequest(c, s.rtr, token, "POST", "/arvados/v1/containers/"+uuid+"/unlock", nil, nil)
index b97c0f87b85f6e4f8c2c0ee798256bde9fced23c..f43cc1ddee295d506854fc97447c0cfe46d868ab 100644 (file)
@@ -81,10 +81,12 @@ func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
        c.Check(sp.Properties["foo"], check.Equals, "bar")
 
        spGet, err := s.conn.SpecimenGet(s.ctx, arvados.GetOptions{UUID: sp.UUID})
+       c.Check(err, check.IsNil)
        c.Check(spGet.UUID, check.Equals, sp.UUID)
        c.Check(spGet.Properties["foo"], check.Equals, "bar")
 
        spList, err := s.conn.SpecimenList(s.ctx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+       c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 1)
        c.Assert(spList.Items, check.HasLen, 1)
        c.Check(spList.Items[0].UUID, check.Equals, sp.UUID)
@@ -92,9 +94,11 @@ func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
 
        anonCtx := context.WithValue(context.Background(), contextKeyTestTokens, []string{arvadostest.AnonymousToken})
        spList, err = s.conn.SpecimenList(anonCtx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+       c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 0)
        c.Check(spList.Items, check.HasLen, 0)
 
        spDel, err := s.conn.SpecimenDelete(s.ctx, arvados.DeleteOptions{UUID: sp.UUID})
+       c.Check(err, check.IsNil)
        c.Check(spDel.UUID, check.Equals, sp.UUID)
 }
index 8ab1cd9ba7337a0a2c33d9628b33f257a46fa6e9..32c6b3b24d198b90adb5f2899580783beb2dd9cb 100644 (file)
@@ -36,6 +36,7 @@ type stubPool struct {
        notify    <-chan struct{}
        unalloc   map[arvados.InstanceType]int // idle+booting+unknown
        idle      map[arvados.InstanceType]int
+       unknown   map[arvados.InstanceType]int
        running   map[string]time.Time
        atQuota   bool
        canCreate int
@@ -62,7 +63,7 @@ func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
        defer p.Unlock()
        r := map[arvados.InstanceType]int{}
        for it, n := range p.unalloc {
-               r[it] = n
+               r[it] = n - p.unknown[it]
        }
        return r
 }
@@ -96,6 +97,7 @@ func (p *stubPool) CountWorkers() map[worker.State]int {
                worker.StateBooting: len(p.unalloc) - len(p.idle),
                worker.StateIdle:    len(p.idle),
                worker.StateRunning: len(p.running),
+               worker.StateUnknown: len(p.unknown),
        }
 }
 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
index de69df98227e624fc29ef8e55884e8457db29592..116ca7643117d3f4df3b6e8d4e99864a44d6dfe6 100644 (file)
@@ -8,6 +8,7 @@ import (
        "fmt"
 
        "git.arvados.org/arvados.git/lib/dispatchcloud/container"
+       "git.arvados.org/arvados.git/lib/dispatchcloud/worker"
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "github.com/sirupsen/logrus"
 )
@@ -23,6 +24,7 @@ import (
 // Running containers whose crunch-run processes have exited are
 // cancelled.
 func (sch *Scheduler) sync() {
+       anyUnknownWorkers := sch.pool.CountWorkers()[worker.StateUnknown] > 0
        running := sch.pool.Running()
        qEntries, qUpdated := sch.queue.Entries()
        for uuid, ent := range qEntries {
@@ -30,7 +32,9 @@ func (sch *Scheduler) sync() {
                switch ent.Container.State {
                case arvados.ContainerStateRunning:
                        if !running {
-                               go sch.cancel(uuid, "not running on any worker")
+                               if !anyUnknownWorkers {
+                                       go sch.cancel(uuid, "not running on any worker")
+                               }
                        } else if !exited.IsZero() && qUpdated.After(exited) {
                                go sch.cancel(uuid, "state=Running after crunch-run exited")
                        } else if ent.Container.Priority == 0 {
index 305ab9e04eb379c82288853b3df9891bc639bf5b..538f5ea8cfd0b9e14edec62d629eaa104ff70514 100644 (file)
@@ -54,3 +54,65 @@ func (*SchedulerSuite) TestForgetIrrelevantContainers(c *check.C) {
        ents, _ = queue.Entries()
        c.Check(ents, check.HasLen, 0)
 }
+
+func (*SchedulerSuite) TestCancelOrphanedContainers(c *check.C) {
+       ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
+       pool := stubPool{
+               unalloc: map[arvados.InstanceType]int{test.InstanceType(1): 1},
+               unknown: map[arvados.InstanceType]int{test.InstanceType(1): 1},
+       }
+       queue := test.Queue{
+               ChooseType: chooseType,
+               Containers: []arvados.Container{
+                       {
+                               UUID:     test.ContainerUUID(1),
+                               Priority: 0,
+                               State:    arvados.ContainerStateRunning,
+                               RuntimeConstraints: arvados.RuntimeConstraints{
+                                       VCPUs: 1,
+                                       RAM:   1 << 30,
+                               },
+                       },
+               },
+       }
+       queue.Update()
+
+       ents, _ := queue.Entries()
+       c.Check(ents, check.HasLen, 1)
+
+       sch := New(ctx, &queue, &pool, time.Millisecond, time.Millisecond)
+
+       // Sync shouldn't cancel the container because it might be
+       // running on the VM with state=="unknown".
+       //
+       // (Cancel+forget happens asynchronously and requires multiple
+       // sync() calls, so even after 10x sync-and-sleep iterations,
+       // we aren't 100% confident that sync isn't trying to
+       // cancel. But in the test environment, the goroutines started
+       // by sync() access stubs and therefore run quickly, so it
+       // works fine in practice. We accept that if the code is
+       // broken, the test will still pass occasionally.)
+       for i := 0; i < 10; i++ {
+               sch.sync()
+               time.Sleep(time.Millisecond)
+       }
+       ents, _ = queue.Entries()
+       c.Check(ents, check.HasLen, 1)
+       c.Check(ents[test.ContainerUUID(1)].Container.State, check.Equals, arvados.ContainerStateRunning)
+
+       // Sync should cancel & forget the container when the
+       // "unknown" node goes away.
+       //
+       // (As above, cancel+forget is async and requires multiple
+       // sync() calls, but stubs are fast so in practice this takes
+       // much less than 1s to complete.)
+       pool.unknown = nil
+       for deadline := time.Now().Add(time.Second); ; time.Sleep(time.Millisecond) {
+               sch.sync()
+               ents, _ = queue.Entries()
+               if len(ents) == 0 || time.Now().After(deadline) {
+                       break
+               }
+       }
+       c.Check(ents, check.HasLen, 0)
+}
index 1948c1e874859f2d8355115b3671f2c5ef0ae32d..0c173c107d4a248ec38ca635f5fa0ac219af6a4b 100644 (file)
@@ -72,8 +72,8 @@ func (suite *PoolSuite) TestResumeAfterRestart(c *check.C) {
        newExecutor := func(cloud.Instance) Executor {
                return &stubExecutor{
                        response: map[string]stubResp{
-                               "crunch-run --list": stubResp{},
-                               "true":              stubResp{},
+                               "crunch-run --list": {},
+                               "true":              {},
                        },
                }
        }
@@ -146,6 +146,59 @@ func (suite *PoolSuite) TestResumeAfterRestart(c *check.C) {
        pool2.Stop()
 }
 
+func (suite *PoolSuite) TestDrain(c *check.C) {
+       logger := ctxlog.TestLogger(c)
+       driver := test.StubDriver{}
+       instanceSet, err := driver.InstanceSet(nil, "test-instance-set-id", nil, logger)
+       c.Assert(err, check.IsNil)
+
+       ac := arvados.NewClientFromEnv()
+
+       type1 := test.InstanceType(1)
+       pool := &Pool{
+               arvClient:   ac,
+               logger:      logger,
+               newExecutor: func(cloud.Instance) Executor { return &stubExecutor{} },
+               instanceSet: &throttledInstanceSet{InstanceSet: instanceSet},
+               instanceTypes: arvados.InstanceTypeMap{
+                       type1.Name: type1,
+               },
+       }
+       notify := pool.Subscribe()
+       defer pool.Unsubscribe(notify)
+
+       pool.Create(type1)
+
+       // Wait for the instance to either return from its Create
+       // call, or show up in a poll.
+       suite.wait(c, pool, notify, func() bool {
+               pool.mtx.RLock()
+               defer pool.mtx.RUnlock()
+               return len(pool.workers) == 1
+       })
+
+       tests := []struct {
+               state        State
+               idleBehavior IdleBehavior
+               result       bool
+       }{
+               {StateIdle, IdleBehaviorHold, false},
+               {StateIdle, IdleBehaviorDrain, false},
+               {StateIdle, IdleBehaviorRun, true},
+       }
+
+       for _, test := range tests {
+               for _, wkr := range pool.workers {
+                       wkr.state = test.state
+                       wkr.idleBehavior = test.idleBehavior
+               }
+
+               // Try to start a container
+               started := pool.StartContainer(type1, arvados.Container{UUID: "testcontainer"})
+               c.Check(started, check.Equals, test.result)
+       }
+}
+
 func (suite *PoolSuite) TestCreateUnallocShutdown(c *check.C) {
        logger := ctxlog.TestLogger(c)
        driver := test.StubDriver{HoldCloudOps: true}
index 34b90805363fc3230ef7f78f1bf3605ecebdfdea..ee967af6cc6f1f773d429b69a4999c051e22989e 100644 (file)
@@ -102,7 +102,7 @@ func authenticate(logger *logrus.Logger, username, token string, argv []string)
                } else if arg == "debug" {
                        logger.SetLevel(logrus.DebugLevel)
                } else {
-                       logger.Warnf("unkown option: %s\n", arg)
+                       logger.Warnf("unknown option: %s\n", arg)
                }
        }
        if hostname == "" || hostname == "-" {
index 57c2c64cdab01289911043c5767ed92edbcd8c36..7b3c8e1b4ed95b59f5e155fcb0392b7fe0d58a26 100644 (file)
@@ -90,6 +90,7 @@ func (*Suite) TestUntrashAndTouchBlock(c *check.C) {
                c.Assert(err, check.IsNil)
                t := time.Now().Add(-time.Hour * 24 * 365)
                err = os.Chtimes(trashfile, t, t)
+               c.Assert(err, check.IsNil)
        }
 
        var stdout, stderr bytes.Buffer
index ec7834972c2609aeb5e4cd14099d35367a7e3c09..4a984c9e780a9fba5bca0ff3d964e972ec2eb728 100644 (file)
@@ -107,6 +107,7 @@ Clusters:
                                continue
                        }
                        body, err := ioutil.ReadAll(resp.Body)
+                       c.Check(err, check.IsNil)
                        c.Logf("status %d, body %s", resp.StatusCode, string(body))
                        c.Check(resp.StatusCode, check.Equals, http.StatusOK)
                        break
index 341929454ad71a87912e566dfdd86153f7f0881e..f3629b68972650e90f06e772404b8c5b29a46f94 100644 (file)
@@ -226,15 +226,12 @@ def arg_parser():  # type: () -> argparse.ArgumentParser
 def add_arv_hints():
     cwltool.command_line_tool.ACCEPTLIST_EN_RELAXED_RE = re.compile(r".*")
     cwltool.command_line_tool.ACCEPTLIST_RE = cwltool.command_line_tool.ACCEPTLIST_EN_RELAXED_RE
-    res10 = pkg_resources.resource_stream(__name__, 'arv-cwl-schema-v1.0.yml')
-    res11 = pkg_resources.resource_stream(__name__, 'arv-cwl-schema-v1.1.yml')
-    customschema10 = res10.read().decode('utf-8')
-    customschema11 = res11.read().decode('utf-8')
-    use_custom_schema("v1.0", "http://arvados.org/cwl", customschema10)
-    use_custom_schema("v1.1.0-dev1", "http://arvados.org/cwl", customschema11)
-    use_custom_schema("v1.1", "http://arvados.org/cwl", customschema11)
-    res10.close()
-    res11.close()
+    supported_versions = ["v1.0", "v1.1", "v1.2"]
+    for s in supported_versions:
+        res = pkg_resources.resource_stream(__name__, 'arv-cwl-schema-%s.yml' % s)
+        customschema = res.read().decode('utf-8')
+        use_custom_schema(s, "http://arvados.org/cwl", customschema)
+        res.close()
     cwltool.process.supportedProcessRequirements.extend([
         "http://arvados.org/cwl#RunInSingleContainer",
         "http://arvados.org/cwl#OutputDirType",
diff --git a/sdk/cwl/arvados_cwl/arv-cwl-schema-v1.2.yml b/sdk/cwl/arvados_cwl/arv-cwl-schema-v1.2.yml
new file mode 100644 (file)
index 0000000..b9b9e61
--- /dev/null
@@ -0,0 +1,206 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+$base: "http://arvados.org/cwl#"
+$namespaces:
+  cwl: "https://w3id.org/cwl/cwl#"
+  cwltool: "http://commonwl.org/cwltool#"
+$graph:
+- $import: https://w3id.org/cwl/CommonWorkflowLanguage.yml
+
+- name: cwltool:Secrets
+  type: record
+  inVocab: false
+  extends: cwl:ProcessRequirement
+  fields:
+    class:
+      type: string
+      doc: "Always 'Secrets'"
+      jsonldPredicate:
+        "_id": "@type"
+        "_type": "@vocab"
+    secrets:
+      type: string[]
+      doc: |
+        List one or more input parameters that are sensitive (such as passwords)
+        which will be deliberately obscured from logging.
+      jsonldPredicate:
+        "_type": "@id"
+        refScope: 0
+
+- name: RunInSingleContainer
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Indicates that a subworkflow should run in a single container
+    and not be scheduled as separate steps.
+  fields:
+    - name: class
+      type: string
+      doc: "Always 'arv:RunInSingleContainer'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+
+- name: OutputDirType
+  type: enum
+  symbols:
+    - local_output_dir
+    - keep_output_dir
+  doc:
+    - |
+      local_output_dir: Use regular file system local to the compute node.
+      There must be sufficient local scratch space to store entire output;
+      specify this with `outdirMin` of `ResourceRequirement`.  Files are
+      batch uploaded to Keep when the process completes.  Most compatible, but
+      upload step can be time consuming for very large files.
+    - |
+      keep_output_dir: Use writable Keep mount.  Files are streamed to Keep as
+      they are written.  Does not consume local scratch space, but does consume
+      RAM for output buffers (up to 192 MiB per file simultaneously open for
+      writing.)  Best suited to processes which produce sequential output of
+      large files (non-sequential writes may produced fragmented file
+      manifests).  Supports regular files and directories, does not support
+      special files such as symlinks, hard links, named pipes, named sockets,
+      or device nodes.
+
+
+- name: RuntimeConstraints
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Set Arvados-specific runtime hints.
+  fields:
+    - name: class
+      type: string
+      doc: "Always 'arv:RuntimeConstraints'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+    - name: keep_cache
+      type: int?
+      doc: |
+        Size of file data buffer for Keep mount in MiB. Default is 256
+        MiB. Increase this to reduce cache thrashing in situations such as
+        accessing multiple large (64+ MiB) files at the same time, or
+        performing random access on a large file.
+    - name: outputDirType
+      type: OutputDirType?
+      doc: |
+        Preferred backing store for output staging.  If not specified, the
+        system may choose which one to use.
+
+- name: PartitionRequirement
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Select preferred compute partitions on which to run jobs.
+  fields:
+    - name: class
+      type: string
+      doc: "Always 'arv:PartitionRequirement'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+    - name: partition
+      type:
+        - string
+        - string[]
+
+- name: APIRequirement
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Indicates that process wants to access to the Arvados API.  Will be granted
+    limited network access and have ARVADOS_API_HOST and ARVADOS_API_TOKEN set
+    in the environment.
+  fields:
+    - name: class
+      type: string
+      doc: "Always 'arv:APIRequirement'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+
+- name: IntermediateOutput
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Specify desired handling of intermediate output collections.
+  fields:
+    class:
+      type: string
+      doc: "Always 'arv:IntermediateOutput'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+    outputTTL:
+      type: int
+      doc: |
+        If the value is greater than zero, consider intermediate output
+        collections to be temporary and should be automatically
+        trashed. Temporary collections will be trashed `outputTTL` seconds
+        after creation.  A value of zero means intermediate output should be
+        retained indefinitely (this is the default behavior).
+
+        Note: arvados-cwl-runner currently does not take workflow dependencies
+        into account when setting the TTL on an intermediate output
+        collection. If the TTL is too short, it is possible for a collection to
+        be trashed before downstream steps that consume it are started.  The
+        recommended minimum value for TTL is the expected duration of the
+        entire the workflow.
+
+- name: WorkflowRunnerResources
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Specify memory or cores resource request for the CWL runner process itself.
+  fields:
+    class:
+      type: string
+      doc: "Always 'arv:WorkflowRunnerResources'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+    ramMin:
+      type: int?
+      doc: Minimum RAM, in mebibytes (2**20)
+      jsonldPredicate: "https://w3id.org/cwl/cwl#ResourceRequirement/ramMin"
+    coresMin:
+      type: int?
+      doc: Minimum cores allocated to cwl-runner
+      jsonldPredicate: "https://w3id.org/cwl/cwl#ResourceRequirement/coresMin"
+    keep_cache:
+      type: int?
+      doc: |
+        Size of collection metadata cache for the workflow runner, in
+        MiB.  Default 256 MiB.  Will be added on to the RAM request
+        when determining node size to request.
+      jsonldPredicate: "http://arvados.org/cwl#RuntimeConstraints/keep_cache"
+
+- name: ClusterTarget
+  type: record
+  extends: cwl:ProcessRequirement
+  inVocab: false
+  doc: |
+    Specify where a workflow step should run
+  fields:
+    class:
+      type: string
+      doc: "Always 'arv:ClusterTarget'"
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+    cluster_id:
+      type: string?
+      doc: The cluster to run the container
+    project_uuid:
+      type: string?
+      doc: The project that will own the container requests and intermediate collections
index d703fcbc55fdec7889108ca01c898365117733cd..c8ab71e50b3dd5bc3cfd2e6f08fa03cdb46f3100 100644 (file)
@@ -30,7 +30,7 @@ setup(name='arvados-cwl-runner',
       download_url="https://github.com/arvados/arvados.git",
       license='Apache 2.0',
       packages=find_packages(),
-      package_data={'arvados_cwl': ['arv-cwl-schema-v1.0.yml', 'arv-cwl-schema-v1.1.yml']},
+      package_data={'arvados_cwl': ['arv-cwl-schema-v1.0.yml', 'arv-cwl-schema-v1.1.yml', 'arv-cwl-schema-v1.2.yml']},
       scripts=[
           'bin/cwl-runner',
           'bin/arvados-cwl-runner',
@@ -39,7 +39,7 @@ setup(name='arvados-cwl-runner',
       # file to determine what version of cwltool and schema-salad to
       # build.
       install_requires=[
-          'cwltool==3.0.20200720165847',
+          'cwltool==3.0.20200807132242',
           'schema-salad==7.0.20200612160654',
           'arvados-python-client{}'.format(pysdk_dep),
           'setuptools',
index c32f88864f88750c00fe896286e147ccd9d061ce..5a2cfb8800402496f3f8fe400cf38c786e57d6eb 100644 (file)
@@ -86,6 +86,7 @@ type ListOptions struct {
        IncludeTrash       bool                   `json:"include_trash"`
        IncludeOldVersions bool                   `json:"include_old_versions"`
        BypassFederation   bool                   `json:"bypass_federation"`
+       ForwardedFor       string                 `json:"forwarded_for,omitempty"`
 }
 
 type CreateOptions struct {
index 257a2b4ef54156d65b22bafb3152cc067de6cd13..86fe218c3361a1b986e0cd38ab09a39eac1ec9bc 100644 (file)
@@ -23,6 +23,7 @@ func (s *DurationSuite) TestMarshalJSON(c *check.C) {
        c.Check(err, check.IsNil)
        c.Check(d.D, check.Equals, Duration(time.Second+234*time.Millisecond))
        buf, err := json.Marshal(d)
+       c.Check(err, check.IsNil)
        c.Check(string(buf), check.Equals, `{"D":"1.234s"}`)
 
        for _, trial := range []struct {
index 5628dcc9c43b42ef6560ddfa1eba2bc0482001d3..61d82c7fa9f4e442d6492ba8fc0f285df76bd5f2 100644 (file)
@@ -52,6 +52,7 @@ func (s *SiteFSSuite) testHomeProject(c *check.C, path string) {
        f, err := s.fs.Open(path)
        c.Assert(err, check.IsNil)
        fis, err := f.Readdir(-1)
+       c.Assert(err, check.IsNil)
        c.Check(len(fis), check.Not(check.Equals), 0)
 
        ok := false
index fff0b7e010b22b1811991ce3b6249093c50b616b..80cc03df37b88ad82ad246db6d4d7bce68dd68a2 100644 (file)
@@ -79,6 +79,7 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
                f, err = s.fs.Open("/by_id/" + path)
                c.Assert(err, check.IsNil)
                fis, err = f.Readdir(-1)
+               c.Assert(err, check.IsNil)
                var names []string
                for _, fi := range fis {
                        names = append(names, fi.Name())
@@ -89,6 +90,7 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
        f, err = s.fs.Open("/by_id/" + fixtureAProjectUUID + "/A Subproject/baz_file")
        c.Assert(err, check.IsNil)
        fis, err = f.Readdir(-1)
+       c.Assert(err, check.IsNil)
        var names []string
        for _, fi := range fis {
                names = append(names, fi.Name())
index e0a41c924bb3de6a0f9ae7826fd4abd2febfd5eb..fc686ad63739e51340d5e254f8f68d65ac4db3e7 100644 (file)
@@ -63,6 +63,7 @@ func (s *ServerRequiredSuite) TestMakeArvadosClientInsecure(c *C) {
 
 func (s *ServerRequiredSuite) TestGetInvalidUUID(c *C) {
        arv, err := MakeArvadosClient()
+       c.Assert(err, IsNil)
 
        getback := make(Dict)
        err = arv.Get("collections", "", nil, &getback)
@@ -80,6 +81,7 @@ func (s *ServerRequiredSuite) TestGetInvalidUUID(c *C) {
 
 func (s *ServerRequiredSuite) TestGetValidUUID(c *C) {
        arv, err := MakeArvadosClient()
+       c.Assert(err, IsNil)
 
        getback := make(Dict)
        err = arv.Get("collections", "zzzzz-4zz18-abcdeabcdeabcde", nil, &getback)
@@ -95,6 +97,7 @@ func (s *ServerRequiredSuite) TestGetValidUUID(c *C) {
 
 func (s *ServerRequiredSuite) TestInvalidResourceType(c *C) {
        arv, err := MakeArvadosClient()
+       c.Assert(err, IsNil)
 
        getback := make(Dict)
        err = arv.Get("unicorns", "zzzzz-zebra-unicorn7unicorn", nil, &getback)
@@ -141,6 +144,7 @@ func (s *ServerRequiredSuite) TestErrorResponse(c *C) {
 
 func (s *ServerRequiredSuite) TestAPIDiscovery_Get_defaultCollectionReplication(c *C) {
        arv, err := MakeArvadosClient()
+       c.Assert(err, IsNil)
        value, err := arv.Discovery("defaultCollectionReplication")
        c.Assert(err, IsNil)
        c.Assert(value, NotNil)
@@ -148,6 +152,7 @@ func (s *ServerRequiredSuite) TestAPIDiscovery_Get_defaultCollectionReplication(
 
 func (s *ServerRequiredSuite) TestAPIDiscovery_Get_noSuchParameter(c *C) {
        arv, err := MakeArvadosClient()
+       c.Assert(err, IsNil)
        value, err := arv.Discovery("noSuchParameter")
        c.Assert(err, NotNil)
        c.Assert(value, IsNil)
index 32802b6a9e00f341195712284b897e1dc083eb48..7d5eb2b64f326e2cd10b41bbeacd147a2bff682a 100644 (file)
@@ -58,6 +58,7 @@ func (s *Suite) TestLogRequests(c *check.C) {
 
        gotReq := make(map[string]interface{})
        err = dec.Decode(&gotReq)
+       c.Check(err, check.IsNil)
        c.Logf("%#v", gotReq)
        c.Check(gotReq["RequestID"], check.Matches, "req-[a-z0-9]{20}")
        c.Check(gotReq["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
@@ -65,6 +66,7 @@ func (s *Suite) TestLogRequests(c *check.C) {
 
        gotResp := make(map[string]interface{})
        err = dec.Decode(&gotResp)
+       c.Check(err, check.IsNil)
        c.Logf("%#v", gotResp)
        c.Check(gotResp["RequestID"], check.Equals, gotReq["RequestID"])
        c.Check(gotResp["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
@@ -111,9 +113,11 @@ func (s *Suite) TestLogErrorBody(c *check.C) {
 
                gotReq := make(map[string]interface{})
                err = dec.Decode(&gotReq)
+               c.Check(err, check.IsNil)
                c.Logf("%#v", gotReq)
                gotResp := make(map[string]interface{})
                err = dec.Decode(&gotResp)
+               c.Check(err, check.IsNil)
                c.Logf("%#v", gotResp)
                if trial.expectLog {
                        c.Check(gotResp["respBody"], check.Equals, trial.expectBody, comment)
index c6c9f044416a7fb93c4b898dfcafcdc121dfc1e4..75603f1baa2bcd59f3af7249e7fc540a06d63dae 100644 (file)
@@ -134,11 +134,11 @@ func (s *CollectionReaderUnit) TestCollectionReaderContent(c *check.C) {
                        c.Check(err, check.Equals, want)
                case string:
                        buf := make([]byte, len(want))
-                       n, err := io.ReadFull(rdr, buf)
+                       _, err := io.ReadFull(rdr, buf)
                        c.Check(err, check.IsNil)
                        for i := 0; i < 4; i++ {
                                c.Check(string(buf), check.Equals, want)
-                               n, err = rdr.Read(buf)
+                               n, err := rdr.Read(buf)
                                c.Check(n, check.Equals, 0)
                                c.Check(err, check.Equals, io.EOF)
                        }
@@ -173,6 +173,7 @@ func (s *CollectionReaderUnit) TestCollectionReaderManyBlocks(c *check.C) {
        filesize := 0
        for i := range locs {
                _, err := rand.Read(buf[:i])
+               c.Assert(err, check.IsNil)
                h.Write(buf[:i])
                locs[i], _, err = s.kc.PutB(buf[:i])
                c.Assert(err, check.IsNil)
@@ -202,9 +203,9 @@ func (s *CollectionReaderUnit) TestCollectionReaderManyBlocks(c *check.C) {
                offset := rand.Intn(len(buf) - 1)
                count := rand.Intn(len(buf) - offset)
                if rand.Intn(2) == 0 {
-                       curPos, err = rdr.Seek(int64(offset)-curPos, io.SeekCurrent)
+                       curPos, _ = rdr.Seek(int64(offset)-curPos, io.SeekCurrent)
                } else {
-                       curPos, err = rdr.Seek(int64(offset), io.SeekStart)
+                       curPos, _ = rdr.Seek(int64(offset), io.SeekStart)
                }
                c.Check(curPos, check.Equals, int64(offset))
                for count > 0 {
@@ -215,6 +216,7 @@ func (s *CollectionReaderUnit) TestCollectionReaderManyBlocks(c *check.C) {
                        count -= n
                }
                curPos, err = rdr.Seek(0, io.SeekCurrent)
+               c.Check(err, check.IsNil)
                c.Check(curPos, check.Equals, int64(offset))
        }
        c.Check(md5.Sum(buf), check.DeepEquals, md5.Sum(testdata))
index e25faed33c1d8c44341e88ec2938948f64140117..a1801b21456b9a6d8bbb716f4db19eaa78feaa4a 100644 (file)
@@ -80,10 +80,12 @@ func (s *ServerRequiredSuite) TestDefaultReplications(c *C) {
        c.Assert(err, Equals, nil)
 
        kc, err := MakeKeepClient(arv)
+       c.Check(err, IsNil)
        c.Assert(kc.Want_replicas, Equals, 2)
 
        arv.DiscoveryDoc["defaultCollectionReplication"] = 3.0
        kc, err = MakeKeepClient(arv)
+       c.Check(err, IsNil)
        c.Assert(kc.Want_replicas, Equals, 3)
 
        arv.DiscoveryDoc["defaultCollectionReplication"] = 1.0
@@ -367,6 +369,7 @@ func (s *StandaloneSuite) TestPutWithFail(c *C) {
                make(chan string, 1)}
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
 
        kc.Want_replicas = 2
@@ -426,6 +429,7 @@ func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
                make(chan string, 4)}
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
 
        kc.Want_replicas = 2
@@ -487,6 +491,7 @@ func (s *StandaloneSuite) TestGet(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -511,6 +516,7 @@ func (s *StandaloneSuite) TestGet404(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -552,6 +558,7 @@ func (s *StandaloneSuite) TestGetFail(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -583,6 +590,7 @@ func (s *StandaloneSuite) TestGetFailRetry(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -609,6 +617,7 @@ func (s *StandaloneSuite) TestGetNetError(c *C) {
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": "http://localhost:62222"}, nil, nil)
@@ -645,6 +654,7 @@ func (s *StandaloneSuite) TestGetWithServiceHint(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(
@@ -688,6 +698,7 @@ func (s *StandaloneSuite) TestGetWithLocalServiceHint(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(
@@ -735,6 +746,7 @@ func (s *StandaloneSuite) TestGetWithServiceHintFailoverToLocals(c *C) {
        defer ksGateway.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(
@@ -772,11 +784,13 @@ func (s *StandaloneSuite) TestChecksum(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
 
        r, n, _, err := kc.Get(barhash)
+       c.Check(err, IsNil)
        _, err = ioutil.ReadAll(r)
        c.Check(n, Equals, int64(3))
        c.Check(err, Equals, nil)
@@ -784,6 +798,7 @@ func (s *StandaloneSuite) TestChecksum(c *C) {
        <-st.handled
 
        r, n, _, err = kc.Get(foohash)
+       c.Check(err, IsNil)
        _, err = ioutil.ReadAll(r)
        c.Check(n, Equals, int64(3))
        c.Check(err, Equals, BadChecksum)
@@ -806,6 +821,7 @@ func (s *StandaloneSuite) TestGetWithFailures(c *C) {
                content}
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        localRoots := make(map[string]string)
@@ -852,6 +868,7 @@ func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
        content := []byte("TestPutGetHead")
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, err := MakeKeepClient(arv)
        c.Assert(err, Equals, nil)
 
@@ -912,6 +929,7 @@ func (s *StandaloneSuite) TestPutProxy(c *C) {
        st := StubProxyHandler{make(chan string, 1)}
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
 
        kc.Want_replicas = 2
@@ -940,6 +958,7 @@ func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
        st := StubProxyHandler{make(chan string, 1)}
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
 
        kc.Want_replicas = 3
@@ -1133,6 +1152,7 @@ func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -1159,6 +1179,7 @@ func (s *StandaloneSuite) TestGetIndexIncomplete(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -1181,6 +1202,7 @@ func (s *StandaloneSuite) TestGetIndexWithNoSuchServer(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
@@ -1201,6 +1223,7 @@ func (s *StandaloneSuite) TestGetIndexWithNoSuchPrefix(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
index c231cc0735795cae9577f9e52f7e5f4bae449bb3..e31ac05418a1154a14f87fc7ed4298e283564e3d 100644 (file)
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import arvados
+import arvados.errors
 import json
 import sys
 
@@ -113,4 +114,64 @@ for i in (3, 5, 9):
 users = apiC.users().list().execute()
 check_A(users)
 
+
+####
+# bug 16683 tests
+
+# Check that this query returns empty, instead of returning a 500 or
+# 502 error.
+# Yes, we're asking for a group from the users endpoint.  This is not a
+# mistake, this is something workbench does to populate the sharing
+# dialog.
+clusterID_B = apiB.configs().get().execute()["ClusterID"]
+i = apiB.users().list(filters=[["uuid", "in", ["%s-j7d0g-fffffffffffffff" % clusterID_B]]], count="none").execute()
+assert len(i["items"]) == 0
+
+# Check that we can create a project and give a remote user access to it
+
+tok3 = apiA.api_client_authorizations().create(body={"api_client_authorization": {"owner_uuid": by_username["case3"]}}).execute()
+tok4 = apiA.api_client_authorizations().create(body={"api_client_authorization": {"owner_uuid": by_username["case4"]}}).execute()
+
+v2_token3 = "v2/%s/%s" % (tok3["uuid"], tok3["api_token"])
+v2_token4 = "v2/%s/%s" % (tok4["uuid"], tok4["api_token"])
+
+apiB_3 = arvados.api(host=j["arvados_api_hosts"][1], token=v2_token3, insecure=True)
+apiB_4 = arvados.api(host=j["arvados_api_hosts"][1], token=v2_token4, insecure=True)
+
+assert apiB_3.users().current().execute()["uuid"] == by_username["case3"]
+assert apiB_4.users().current().execute()["uuid"] == by_username["case4"]
+
+newproject = apiB_3.groups().create(body={"group_class": "project",
+                                           "name":"fed test project"},
+                                    ensure_unique_name=True).execute()
+
+try:
+    # Expect to fail
+    apiB_4.groups().get(uuid=newproject["uuid"]).execute()
+except arvados.errors.ApiError as e:
+    if e.resp['status'] == '404':
+        pass
+    else:
+        raise
+
+l = apiB_3.links().create(body={"link_class": "permission",
+                            "name":"can_read",
+                            "tail_uuid": by_username["case4"],
+                            "head_uuid": newproject["uuid"]}).execute()
+
+# Expect to succeed
+apiB_4.groups().get(uuid=newproject["uuid"]).execute()
+
+# remove permission
+apiB_3.links().delete(uuid=l["uuid"]).execute()
+
+try:
+    # Expect to fail again
+    apiB_4.groups().get(uuid=newproject["uuid"]).execute()
+except arvados.errors.ApiError as e:
+    if e.resp['status'] == '404':
+        pass
+    else:
+        raise
+
 print("Passed checks")
index c3e1ff42ad0781ed91c8da1250e9a5d0f2e47f28..6fb8ff2b33549af8e4e512a1374363f8dee8fa64 100644 (file)
@@ -751,6 +751,20 @@ class ArvadosModel < ApplicationRecord
     %r/[a-z0-9]{5}-#{uuid_prefix}-[a-z0-9]{15}/
   end
 
+  def check_readable_uuid attr, attr_value
+    return if attr_value.nil?
+    if (r = ArvadosModel::resource_class_for_uuid attr_value)
+      unless skip_uuid_read_permission_check.include? attr
+        r = r.readable_by(current_user)
+      end
+      if r.where(uuid: attr_value).count == 0
+        errors.add(attr, "'#{attr_value}' not found")
+      end
+    else
+      # Not a valid uuid or PDH, but that (currently) is not an error.
+    end
+  end
+
   def ensure_valid_uuids
     specials = [system_user_uuid]
 
@@ -759,16 +773,7 @@ class ArvadosModel < ApplicationRecord
         next if skip_uuid_existence_check.include? attr
         attr_value = send attr
         next if specials.include? attr_value
-        if attr_value
-          if (r = ArvadosModel::resource_class_for_uuid attr_value)
-            unless skip_uuid_read_permission_check.include? attr
-              r = r.readable_by(current_user)
-            end
-            if r.where(uuid: attr_value).count == 0
-              errors.add(attr, "'#{attr_value}' not found")
-            end
-          end
-        end
+        check_readable_uuid attr, attr_value
       end
     end
   end
index e4ba7f3de1ef8f20833355efb0dae1a153b05113..0d7334e44e85440d37a530e6316d338f125b92aa 100644 (file)
@@ -43,6 +43,28 @@ class Link < ArvadosModel
 
   protected
 
+  def check_readable_uuid attr, attr_value
+    if attr == 'tail_uuid' &&
+       !attr_value.nil? &&
+       self.link_class == 'permission' &&
+       attr_value[0..4] != Rails.configuration.ClusterID &&
+       ApiClientAuthorization.remote_host(uuid_prefix: attr_value[0..4]) &&
+       ArvadosModel::resource_class_for_uuid(attr_value) == User
+      # Permission link tail is a remote user (the user permissions
+      # are being granted to), so bypass the standard check that a
+      # referenced object uuid is readable by current user.
+      #
+      # We could do a call to the remote cluster to check if the user
+      # in tail_uuid exists.  This would detect copy-and-paste errors,
+      # but add another way for the request to fail, and I don't think
+      # it would improve security.  It doesn't seem to be worth the
+      # complexity tradeoff.
+      true
+    else
+      super
+    end
+  end
+
   def permission_to_attach_to_objects
     # Anonymous users cannot write links
     return false if !current_user
@@ -76,6 +98,11 @@ class Link < ArvadosModel
 
     head_obj = ArvadosModel.find_by_uuid(head_uuid)
 
+    if head_obj.nil?
+      errors.add(:head_uuid, "does not exist")
+      return false
+    end
+
     # No permission links can be pointed to past collection versions
     if head_obj.is_a?(Collection) && head_obj.current_version_uuid != head_uuid
       errors.add(:head_uuid, "cannot point to a past version of a collection")
index 00f3cc291352493b11258aa0f9750fc883a263ff..c7d21bdc4da721d51f40c0cb235a15a8e3c3db96 100644 (file)
@@ -58,6 +58,14 @@ class LinkTest < ActiveSupport::TestCase
                                   users(:active).uuid.sub(/-\w+$/, "-#{'z' * 15}"))
   end
 
+  test "link granting permission to remote user is valid" do
+    refute new_active_link_valid?(tail_uuid:
+                                  users(:active).uuid.sub(/^\w+-/, "foooo-"))
+    Rails.configuration.RemoteClusters = Rails.configuration.RemoteClusters.merge({foooo: ActiveSupport::InheritableOptions.new({Host: "bar.com"})})
+    assert new_active_link_valid?(tail_uuid:
+                                  users(:active).uuid.sub(/^\w+-/, "foooo-"))
+  end
+
   test "link granting non-project permission to unreadable user is invalid" do
     refute new_active_link_valid?(tail_uuid: users(:admin).uuid,
                                   head_uuid: collections(:bar_file).uuid)
index a6445506e5fd3b3c1500caf6d3d8cb98d63b5f08..b154f6e99848a3623b167726412ce5b48a59c715 100644 (file)
@@ -31,6 +31,7 @@ func (s *mainSuite) TestHTTPServer(c *check.C) {
                c.Fatal(err)
        }
        _, p, err := net.SplitHostPort(ln.Addr().String())
+       c.Check(err, check.IsNil)
        ln.Close()
        config := "Clusters:\n zzzzz:\n  ManagementToken: abcdefg\n  Services: {Keepbalance: {InternalURLs: {'http://localhost:" + p + "/': {}}}}\n"
 
diff --git a/services/keep/tools/traffic_test.py b/services/keep/tools/traffic_test.py
deleted file mode 100755 (executable)
index cd50a52..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-#! /usr/bin/env python
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# traffic_test.py
-#
-# Launch a test Keep and API server and PUT and GET a bunch of blocks.
-# Can be used to simulate client traffic in Keep to evaluate memory usage,
-# error logging, performance, etc.
-#
-# This script is warty and is relatively environment-specific, but the
-# example run described below should execute cleanly.
-#
-# Usage:
-#   traffic_test.py start
-#       Starts the test servers.
-#   traffic_test.py put file1 file2 file3 ....
-#       Runs arv-put on each file.
-#   traffic_test.py get hash1 hash2 hash3 ....
-#       Loops forever issuing GET requests for specified blocks.
-#   traffic_test.py stop
-#       Stops the test servers.
-#
-# Example:
-#
-#   $ ./traffic_test.py start
-#   $ ./traffic_test.py put GS00253-DNA_A02_200_37.tsv.bz2 \
-#         GS00253-DNA_B01_200_37.tsv.bz2 \
-#         GS00253-DNA_B02_200_37.tsv.bz2
-#   $ ./traffic_test.py get $(find /tmp/tmp* -type f -printf "%f ")
-#     [loops forever]
-#     ^C
-#   $ ./traffic_test.py stop
-#
-# Multiple "get" runs may be run concurrently to evaluate Keep's handling
-# of additional concurrent clients.
-
-PYSDK_DIR    = "../../../sdk/python"
-PYTEST_DIR   = PYSDK_DIR + "/tests"
-ARV_PUT_PATH = PYSDK_DIR + "/bin/arv-put"
-ARV_GET_PATH = PYSDK_DIR + "/bin/arv-get"
-SECONDS_BETWEEN_GETS = 1
-
-import argparse
-import httplib2
-import os
-import random
-import subprocess
-import sys
-import time
-
-# for run_test_server.py
-sys.path.insert(0, PYSDK_DIR)
-sys.path.insert(0, PYTEST_DIR)
-import arvados
-import run_test_server
-
-def arv_cmd(*args):
-    p = subprocess.Popen([sys.executable] + list(args),
-                         stdout=subprocess.PIPE)
-    (arvout, arverr) = p.communicate()
-    if p.returncode != 0:
-        print "error {} from {} {}: {}".format(
-            p.returncode, sys.executable, args, arverr)
-        sys.exit(p.returncode)
-    return arvout
-
-def start():
-    run_test_server.run()
-    run_test_server.run_keep()
-
-def put(files):
-    os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3000"
-    run_test_server.authorize_with('active')
-    for v in ["ARVADOS_API_HOST",
-              "ARVADOS_API_HOST_INSECURE",
-              "ARVADOS_API_TOKEN"]:
-        os.environ[v] = arvados.config.settings()[v]
-
-    if not os.environ.has_key('PYTHONPATH'):
-        os.environ['PYTHONPATH'] = ''
-    os.environ['PYTHONPATH'] = "{}:{}:{}".format(
-        PYSDK_DIR, PYTEST_DIR, os.environ['PYTHONPATH'])
-
-    for c in files:
-        manifest_uuid = arv_cmd(ARV_PUT_PATH, c)
-
-def get(blocks):
-    os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3000"
-
-    run_test_server.authorize_with('active')
-    for v in ["ARVADOS_API_HOST",
-              "ARVADOS_API_HOST_INSECURE",
-              "ARVADOS_API_TOKEN"]:
-        os.environ[v] = arvados.config.settings()[v]
-
-    nqueries = 0
-    while True:
-        b = random.choice(blocks)
-        print "GET /" + b
-        body = arv_cmd(ARV_GET_PATH, b)
-        print "got {} bytes".format(len(body))
-        time.sleep(SECONDS_BETWEEN_GETS)
-        nqueries = nqueries + 1
-
-def stop():
-    run_test_server.stop_keep()
-    run_test_server.stop()
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser()
-    parser.add_argument('action',
-                        type=str,
-                        nargs='+',
-                        help='''"start", "put", "get", "stop"''')
-    args = parser.parse_args()
-
-    if args.action[0] == 'start':
-        start()
-    elif args.action[0] == 'put':
-        put(args.action[1:])
-    elif args.action[0] == 'get':
-        get(args.action[1:])
-    elif args.action[0] == 'stop':
-        stop()
-    else:
-        print('Unrecognized action "{}"'.format(args.action))
-        print('actions are "start", "put", "get", "stop"')
index 97045a660394fe6533fee5d6a444ce07915757ea..d9886c07f317904ea141343d2f26554ecae10b3a 100644 (file)
@@ -166,7 +166,9 @@ func (s *StubbedS3AWSSuite) TestIAMRoleCredentials(c *check.C) {
                metrics: newVolumeMetricsVecs(prometheus.NewRegistry()),
        }
        err := v.check(s.metadata.URL + "/latest")
+       c.Check(err, check.IsNil)
        creds, err := v.bucket.svc.Client.Config.Credentials.Retrieve(context.Background())
+       c.Check(err, check.IsNil)
        c.Check(creds.AccessKeyID, check.Equals, "ASIAIOSFODNN7EXAMPLE")
        c.Check(creds.SecretAccessKey, check.Equals, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
 
@@ -185,6 +187,7 @@ func (s *StubbedS3AWSSuite) TestIAMRoleCredentials(c *check.C) {
                metrics: newVolumeMetricsVecs(prometheus.NewRegistry()),
        }
        err = deadv.check(s.metadata.URL + "/latest")
+       c.Check(err, check.IsNil)
        _, err = deadv.bucket.svc.Client.Config.Credentials.Retrieve(context.Background())
        c.Check(err, check.ErrorMatches, `(?s).*EC2RoleRequestError: no EC2 instance role found.*`)
        c.Check(err, check.ErrorMatches, `(?s).*404.*`)