Merge branch '9684-workflows'
authorTom Clegg <tom@curoverse.com>
Fri, 19 Aug 2016 00:27:37 +0000 (20:27 -0400)
committerTom Clegg <tom@curoverse.com>
Fri, 19 Aug 2016 00:27:37 +0000 (20:27 -0400)
refs #9684

30 files changed:
apps/workbench/app/models/container_work_unit.rb
apps/workbench/app/models/proxy_work_unit.rb
apps/workbench/app/models/work_unit.rb
apps/workbench/app/views/work_units/_show_log.html.erb
apps/workbench/test/integration/websockets_test.rb
build/run-build-packages.sh
crunch_scripts/cwl-runner
sdk/cwl/arvados_cwl/__init__.py
sdk/cwl/arvados_cwl/arvcontainer.py
sdk/cwl/arvados_cwl/arvjob.py
sdk/cwl/arvados_cwl/arvtool.py
sdk/cwl/arvados_cwl/fsaccess.py
sdk/cwl/arvados_cwl/pathmapper.py
sdk/cwl/setup.py
sdk/cwl/tests/arvados-tests.sh [new file with mode: 0755]
sdk/cwl/tests/arvados-tests.yml [new file with mode: 0644]
sdk/cwl/tests/dir-job.yml [new file with mode: 0644]
sdk/cwl/tests/keep-dir-test-input.cwl [new file with mode: 0644]
sdk/cwl/tests/runner.sh [new file with mode: 0755]
sdk/cwl/tests/test_container.py
sdk/cwl/tests/test_job.py
sdk/cwl/tests/testdir/a [new file with mode: 0644]
sdk/cwl/tests/testdir/b [new file with mode: 0644]
sdk/cwl/tests/testdir/c/d [new file with mode: 0644]
services/api/app/models/arvados_model.rb
services/api/app/models/collection.rb
services/api/test/integration/collections_api_test.rb
services/api/test/unit/log_test.rb
services/crunch-dispatch-slurm/crunch-dispatch-slurm.go
services/crunch-dispatch-slurm/usage.go

index f9159231a09f4d7ad197464e1a1403a8e8a3d0ef..4f4c915e066ce3d8c2ef4427336e5a71b0b87b2e 100644 (file)
@@ -124,9 +124,13 @@ class ContainerWorkUnit < ProxyWorkUnit
     get_combined(:output_path)
   end
 
+  def log_object_uuids
+    [get_combined(:uuid), get(:uuid)].uniq
+  end
+
   def live_log_lines(limit=2000)
     event_types = ["stdout", "stderr", "arv-mount", "crunch-run"]
-    log_lines = Log.where(event_type: event_types, object_uuid: uuid).order("id DESC").limit(limit)
+    log_lines = Log.where(event_type: event_types, object_uuid: log_object_uuids).order("id DESC").limit(limit)
     log_lines.results.reverse.
       flat_map { |log| log.properties[:text].split("\n") rescue [] }
   end
index 53b49085660e3d0a5b4c55dc63626f2818aa4fe3..feab5d8eb4a22e599bc328978d2ad5e0dcae4781 100644 (file)
@@ -328,6 +328,10 @@ class ProxyWorkUnit < WorkUnit
     resp
   end
 
+  def log_object_uuids
+    [uuid]
+  end
+
   protected
 
   def get key, obj=@proxied
index 7b52365cfb9085baead007e69797451efbc38ae6..0d194b88a5f8c70a09a4f6044c0a6f90ab388b07 100644 (file)
@@ -184,6 +184,10 @@ class WorkUnit
     # container_uuid of a container_request
   end
 
+  def log_object_uuids
+    # object uuids for live log
+  end
+
   def live_log_lines(limit)
     # fetch log entries from logs table for @proxied
   end
index 566da5ba86d1735ec8099d4f93ca6b9305e880a9..6a0916fe9853b2ecd6ca10bbc3fa4d94923ebbb8 100644 (file)
@@ -13,7 +13,7 @@
 <h4>Recent logs</h4>
 <div id="event_log_div"
      class="arv-log-event-listener arv-log-event-handler-append-logs arv-job-log-window"
-     data-object-uuid="<%= wu.uuid %>"
+     data-object-uuids="<%= wu.log_object_uuids.join(' ') %>"
   ><%= wu.live_log_lines(Rails.configuration.running_job_log_records_to_fetch).join("\n") %>
 </div>
 
index bc8b5cd0f7e7da4babb11a7d81300eef9b21af4c..e9f5a799c0d5444ed453381aa063002fd0e74b9c 100644 (file)
@@ -31,12 +31,13 @@ class WebsocketTest < ActionDispatch::IntegrationTest
    ["pipeline_instances", api_fixture("pipeline_instances")['pipeline_with_newer_template']['uuid']],
    ["jobs", api_fixture("jobs")['running']['uuid']],
    ["containers", api_fixture("containers")['running']['uuid']],
-   ["container_requests", api_fixture("container_requests")['running']['uuid']],
+   ["container_requests", api_fixture("container_requests")['running']['uuid'], api_fixture("containers")['running']['uuid']],
   ].each do |c|
     test "test live logging scrolling #{c[0]}" do
 
       controller = c[0]
       uuid = c[1]
+      log_uuid = c[2] || c[1]
 
       visit(page_with_token("admin", "/#{controller}/#{uuid}"))
       click_link("Log")
@@ -51,7 +52,7 @@ class WebsocketTest < ActionDispatch::IntegrationTest
 
       Thread.current[:arvados_api_token] = @@API_AUTHS["admin"]['api_token']
       api.api("logs", "", {log: {
-                  object_uuid: uuid,
+                  object_uuid: log_uuid,
                   event_type: "stderr",
                   properties: {"text" => text}}})
       assert_text '1000 hello'
@@ -61,7 +62,7 @@ class WebsocketTest < ActionDispatch::IntegrationTest
       old_top = page.evaluate_script("$('#event_log_div').scrollTop()")
 
       api.api("logs", "", {log: {
-                  object_uuid: uuid,
+                  object_uuid: log_uuid,
                   event_type: "stderr",
                   properties: {"text" => "1001 hello\n"}}})
       assert_text '1001 hello'
@@ -75,7 +76,7 @@ class WebsocketTest < ActionDispatch::IntegrationTest
       assert_equal 30, page.evaluate_script("$('#event_log_div').scrollTop()")
 
       api.api("logs", "", {log: {
-                  object_uuid: uuid,
+                  object_uuid: log_uuid,
                   event_type: "stderr",
                   properties: {"text" => "1002 hello\n"}}})
       assert_text '1002 hello'
index e1c0ac3f1631a292c43d23c32a0d29303605f3df..ae2de696cff3b365c0d6f936fd8a549d72769672 100755 (executable)
@@ -459,7 +459,7 @@ fpm_build $WORKSPACE/sdk/cwl "${PYTHON2_PKG_PREFIX}-arvados-cwl-runner" 'Curover
 # So we build this thing separately.
 #
 # Ward, 2016-03-17
-fpm_build schema_salad "" "" python 1.14.20160708181155
+fpm_build schema_salad "" "" python 1.16.20160810195039
 
 # And schema_salad now depends on ruamel-yaml, which apparently has a braindead setup.py that requires special arguments to build (otherwise, it aborts with 'error: you have to install with "pip install ."'). Sigh.
 # Ward, 2016-05-26
@@ -467,7 +467,7 @@ fpm_build schema_salad "" "" python 1.14.20160708181155
 fpm_build ruamel.yaml "" "" python 0.11.11 --python-setup-py-arguments "--single-version-externally-managed"
 
 # And for cwltool we have the same problem as for schema_salad. Ward, 2016-03-17
-fpm_build cwltool "" "" python 1.0.20160726135535
+fpm_build cwltool "" "" python 1.0.20160811184335
 
 # FPM eats the trailing .0 in the python-rdflib-jsonld package when built with 'rdflib-jsonld>=0.3.0'. Force the version. Ward, 2016-03-25
 fpm_build rdflib-jsonld "" "" python 0.3.0
index 2a1873a84e6925f9c1edf6057dc2b01a392d25ff..fe4e8009aca1af0b04fb483983a09fd59de25390 100755 (executable)
@@ -18,8 +18,9 @@ import logging
 import os
 import json
 import argparse
+import re
 from arvados.api import OrderedJsonModel
-from cwltool.process import adjustFileObjs
+from cwltool.process import adjustFileObjs, adjustDirObjs
 from cwltool.load_tool import load_tool
 
 # Print package versions
@@ -30,8 +31,10 @@ api = arvados.api("v1")
 try:
     job_order_object = arvados.current_job()['script_parameters']
 
+    pdh_path = re.compile(r'^[0-9a-f]{32}\+\d+(/.+)?$')
+
     def keeppath(v):
-        if arvados.util.keep_locator_pattern.match(v):
+        if pdh_path.match(v):
             return "keep:%s" % v
         else:
             return v
@@ -49,6 +52,7 @@ try:
             }
 
     adjustFileObjs(job_order_object, keeppathObj)
+    adjustDirObjs(job_order_object, keeppathObj)
 
     runner = arvados_cwl.ArvCwlRunner(api_client=arvados.api('v1', model=OrderedJsonModel()))
 
index 99db9f1026863d4f1bf4def8fae92223ca7afdfd..27af075f3652b4180e23906ed2fc914812036534 100644 (file)
@@ -9,6 +9,7 @@ import os
 import sys
 import threading
 import hashlib
+from functools import partial
 import pkg_resources  # part of setuptools
 
 from cwltool.errors import WorkflowException
@@ -26,6 +27,7 @@ from .fsaccess import CollectionFsAccess
 
 from cwltool.process import shortname, UnsupportedRequirement
 from cwltool.pathmapper import adjustFileObjs
+from cwltool.draft2tool import compute_checksums
 from arvados.api import OrderedJsonModel
 
 logger = logging.getLogger('arvados.cwl-runner')
@@ -103,9 +105,21 @@ class ArvCwlRunner(object):
     def add_uploaded(self, src, pair):
         self.uploaded[src] = pair
 
+    def check_writable(self, obj):
+        if isinstance(obj, dict):
+            if obj.get("writable"):
+                raise UnsupportedRequirement("InitialWorkDir feature 'writable: true' not supported")
+            for v in obj.itervalues():
+                self.check_writable(v)
+        if isinstance(obj, list):
+            for v in obj:
+                self.check_writable(v)
+
     def arvExecutor(self, tool, job_order, **kwargs):
         self.debug = kwargs.get("debug")
 
+        tool.visit(self.check_writable)
+
         if kwargs.get("quiet"):
             logger.setLevel(logging.WARN)
             logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
@@ -113,7 +127,8 @@ class ArvCwlRunner(object):
         useruuid = self.api.users().current().execute()["uuid"]
         self.project_uuid = kwargs.get("project_uuid") if kwargs.get("project_uuid") else useruuid
         self.pipeline = None
-        self.fs_access = CollectionFsAccess(kwargs["basedir"], api_client=self.api)
+        make_fs_access = kwargs.get("make_fs_access") or partial(CollectionFsAccess, api_client=self.api)
+        self.fs_access = make_fs_access(kwargs["basedir"])
 
         if kwargs.get("create_template"):
             tmpl = RunnerTemplate(self, tool, job_order, kwargs.get("enable_reuse"))
@@ -124,7 +139,7 @@ class ArvCwlRunner(object):
         self.debug = kwargs.get("debug")
         self.ignore_docker_for_reuse = kwargs.get("ignore_docker_for_reuse")
 
-        kwargs["fs_access"] = self.fs_access
+        kwargs["make_fs_access"] = make_fs_access
         kwargs["enable_reuse"] = kwargs.get("enable_reuse")
         kwargs["use_container"] = True
         kwargs["tmpdir_prefix"] = "tmp"
@@ -135,6 +150,7 @@ class ArvCwlRunner(object):
             kwargs["outdir"] = "/var/spool/cwl"
             kwargs["docker_outdir"] = "/var/spool/cwl"
             kwargs["tmpdir"] = "/tmp"
+            kwargs["docker_tmpdir"] = "/tmp"
         elif self.work_api == "jobs":
             kwargs["outdir"] = "$(task.outdir)"
             kwargs["docker_outdir"] = "$(task.outdir)"
@@ -228,17 +244,7 @@ class ArvCwlRunner(object):
             raise WorkflowException("Workflow did not return a result.")
 
         if kwargs.get("compute_checksum"):
-            def compute_checksums(fileobj):
-                if "checksum" not in fileobj:
-                    checksum = hashlib.sha1()
-                    with self.fs_access.open(fileobj["location"], "rb") as f:
-                        contents = f.read(1024*1024)
-                        while contents != "":
-                            checksum.update(contents)
-                            contents = f.read(1024*1024)
-                    fileobj["checksum"] = "sha1$%s" % checksum.hexdigest()
-
-            adjustFileObjs(self.final_output, compute_checksums)
+            adjustFileObjs(self.final_output, partial(compute_checksums, self.fs_access))
 
         return self.final_output
 
@@ -341,4 +347,5 @@ def main(args, stdout, stderr, api_client=None):
                              executor=runner.arvExecutor,
                              makeTool=runner.arvMakeTool,
                              versionfunc=versionstring,
-                             job_order_object=job_order_object)
+                             job_order_object=job_order_object,
+                             make_fs_access=partial(CollectionFsAccess, api_client=api_client))
index 73ba8d52089049fad65db3a0ded1da9a63472759..413435db2392e38aa22ba0d5222fef333080dbde 100644 (file)
@@ -62,7 +62,7 @@ class ArvadosContainer(object):
         if self.generatefiles["listing"]:
             raise UnsupportedRequirement("Generate files not supported")
 
-        container_request["environment"] = {"TMPDIR": "/tmp"}
+        container_request["environment"] = {"TMPDIR": self.tmpdir, "HOME": self.outdir}
         if self.environment:
             container_request["environment"].update(self.environment)
 
@@ -100,7 +100,7 @@ class ArvadosContainer(object):
 
             self.arvrunner.processes[response["container_uuid"]] = self
 
-            logger.info("Container %s (%s) request state is %s", self.name, response["container_uuid"], response["state"])
+            logger.info("Container %s (%s) request state is %s", self.name, response["uuid"], response["state"])
 
             if response["state"] == "Final":
                 self.done(response)
index 00355973bd6c6e1416fa3e96480c96771bdc3227..a688469634bf31a20b6cf8cff5ede456277a21c4 100644 (file)
@@ -51,7 +51,7 @@ class ArvadosJob(object):
                 if p.type == "CreateFile":
                     script_parameters["task.vwd"][p.target] = "$(task.keep)/%s/%s" % (vwd.portable_data_hash(), p.target)
 
-        script_parameters["task.env"] = {"TMPDIR": "$(task.tmpdir)"}
+        script_parameters["task.env"] = {"TMPDIR": self.tmpdir, "HOME": self.outdir}
         if self.environment:
             script_parameters["task.env"].update(self.environment)
 
@@ -227,7 +227,7 @@ class RunnerJob(Runner):
         logger.info("Submitted job %s", response["uuid"])
 
         if kwargs.get("submit"):
-            self.pipeline = self.arvrunner.api.pipeline_instances().create(
+            self.arvrunner.pipeline = self.arvrunner.api.pipeline_instances().create(
                 body={
                     "owner_uuid": self.arvrunner.project_uuid,
                     "name": shortname(self.tool.tool["id"]),
index 7107ba0a301ff9a80cf03386f78817ec021b2fd9..987ce8967aef2934d6d0f17bf3316e9eb85d70cf 100644 (file)
@@ -37,4 +37,6 @@ class ArvadosCommandTool(CommandLineTool):
         elif self.work_api == "jobs":
             kwargs["outdir"] = "$(task.outdir)"
             kwargs["docker_outdir"] = "$(task.outdir)"
+            kwargs["tmpdir"] = "$(task.tmpdir)"
+            kwargs["docker_tmpdir"] = "$(task.tmpdir)"
         return super(ArvadosCommandTool, self).job(joborder, output_callback, **kwargs)
index d2d38b00c35a89f2a1a1f179a0468b821e48670a..0970e72d71d0ce2cef056e05f94bd02ea1324549 100644 (file)
@@ -1,14 +1,14 @@
 import fnmatch
 import os
 
-import cwltool.process
+import cwltool.stdfsaccess
 from cwltool.pathmapper import abspath
 
 import arvados.util
 import arvados.collection
 import arvados.arvfile
 
-class CollectionFsAccess(cwltool.process.StdFsAccess):
+class CollectionFsAccess(cwltool.stdfsaccess.StdFsAccess):
     """Implement the cwltool FsAccess interface for Arvados Collections."""
 
     def __init__(self, basedir, api_client=None):
@@ -91,11 +91,11 @@ class CollectionFsAccess(cwltool.process.StdFsAccess):
 
     def listdir(self, fn):  # type: (unicode) -> List[unicode]
         collection, rest = self.get_collection(fn)
-        if rest:
-            dir = collection.find(rest)
-        else:
-            dir = collection
         if collection:
+            if rest:
+                dir = collection.find(rest)
+            else:
+                dir = collection
             return [abspath(l, fn) for l in dir.keys()]
         else:
             return super(CollectionFsAccess, self).listdir(fn)
@@ -104,3 +104,12 @@ class CollectionFsAccess(cwltool.process.StdFsAccess):
         if paths and paths[-1].startswith("keep:") and arvados.util.keep_locator_pattern.match(paths[-1][5:]):
             return paths[-1]
         return os.path.join(path, *paths)
+
+    def realpath(self, path):
+        if path.startswith("$(task.tmpdir)") or path.startswith("$(task.outdir)"):
+            return path
+        collection, rest = self.get_collection(path)
+        if collection:
+            return path
+        else:
+            return os.path.realpath(path)
index 24c319cce5620dd9110f76932d45cb20967040bc..e17264bcdd90d48f5972260bd49d6c76459e1cca 100644 (file)
@@ -52,9 +52,8 @@ class ArvPathMapper(PathMapper):
         elif srcobj["class"] == "Directory":
             if isinstance(src, basestring) and ArvPathMapper.pdh_dirpath.match(src):
                 self._pathmap[src] = MapperEnt(src, self.collection_pattern % src[5:], "Directory")
-            else:
-                for l in srcobj["listing"]:
-                    self.visit(l, uploadfiles)
+            for l in srcobj["listing"]:
+                self.visit(l, uploadfiles)
 
     def addentry(self, obj, c, path, subdirs):
         if obj["location"] in self._pathmap:
index 9fcbdd7bb419a33106bd6ad743900e26e07fcd7b..e22a74faccaab569169f42c95079acc61d53c3c9 100644 (file)
@@ -32,7 +32,7 @@ setup(name='arvados-cwl-runner',
       # Make sure to update arvados/build/run-build-packages.sh as well
       # when updating the cwltool version pin.
       install_requires=[
-          'cwltool==1.0.20160726135535',
+          'cwltool==1.0.20160811184335',
           'arvados-python-client>=0.1.20160714204738',
       ],
       data_files=[
diff --git a/sdk/cwl/tests/arvados-tests.sh b/sdk/cwl/tests/arvados-tests.sh
new file mode 100755 (executable)
index 0000000..8646704
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+if ! arv-get d7514270f356df848477718d58308cc4+94 > /dev/null ; then
+    arv-put --portable-data-hash testdir
+fi
+exec cwltest --test arvados-tests.yml --tool $PWD/runner.sh
diff --git a/sdk/cwl/tests/arvados-tests.yml b/sdk/cwl/tests/arvados-tests.yml
new file mode 100644 (file)
index 0000000..1187962
--- /dev/null
@@ -0,0 +1,10 @@
+- job: dir-job.yml
+  output:
+    "outlist": {
+        "size": 20,
+        "location": "output.txt",
+        "class": "File",
+        "checksum": "sha1$13cda8661796ae241da3a18668fb552161a72592"
+    }
+  tool: keep-dir-test-input.cwl
+  doc: Test directory in keep
diff --git a/sdk/cwl/tests/dir-job.yml b/sdk/cwl/tests/dir-job.yml
new file mode 100644 (file)
index 0000000..91204d7
--- /dev/null
@@ -0,0 +1,3 @@
+indir:
+  class: Directory
+  location: keep:d7514270f356df848477718d58308cc4+94
\ No newline at end of file
diff --git a/sdk/cwl/tests/keep-dir-test-input.cwl b/sdk/cwl/tests/keep-dir-test-input.cwl
new file mode 100644 (file)
index 0000000..93362b5
--- /dev/null
@@ -0,0 +1,21 @@
+class: CommandLineTool
+cwlVersion: v1.0
+requirements:
+  - class: ShellCommandRequirement
+inputs:
+  indir:
+    type: Directory
+    inputBinding:
+      prefix: cd
+      position: -1
+outputs:
+  outlist:
+    type: File
+    outputBinding:
+      glob: output.txt
+arguments: [
+  {shellQuote: false, valueFrom: "&&"},
+  "find", ".",
+  {shellQuote: false, valueFrom: "|"},
+  "sort"]
+stdout: output.txt
\ No newline at end of file
diff --git a/sdk/cwl/tests/runner.sh b/sdk/cwl/tests/runner.sh
new file mode 100755 (executable)
index 0000000..22ede5c
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec arvados-cwl-runner --disable-reuse --compute-checksum "$@"
index a65c02e735b49eae24f0d21b156e26acb57b12cb..e06003769b643d0c1d127cdc12cffd5af119ddd3 100644 (file)
@@ -3,6 +3,7 @@ import logging
 import mock
 import unittest
 import os
+import functools
 import cwltool.process
 
 if not os.getenv('ARVADOS_DEBUG'):
@@ -32,13 +33,17 @@ class TestContainer(unittest.TestCase):
             "baseCommand": "ls",
             "arguments": [{"valueFrom": "$(runtime.outdir)"}]
         }
-        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names, basedir="")
+        make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess, api_client=runner.api)
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
+                                                 basedir="", make_fs_access=make_fs_access)
         arvtool.formatgraph = None
-        for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run"):
+        for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run",
+                             make_fs_access=make_fs_access, tmpdir="/tmp"):
             j.run()
             runner.api.container_requests().create.assert_called_with(
                 body={
                     'environment': {
+                        'HOME': '/var/spool/cwl',
                         'TMPDIR': '/tmp'
                     },
                     'name': 'test_run',
@@ -81,14 +86,18 @@ class TestContainer(unittest.TestCase):
             }],
             "baseCommand": "ls"
         }
-        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names)
+        make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess, api_client=runner.api)
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
+                                                 avsc_names=avsc_names, make_fs_access=make_fs_access)
         arvtool.formatgraph = None
-        for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements"):
+        for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements",
+                             make_fs_access=make_fs_access, tmpdir="/tmp"):
             j.run()
 
         runner.api.container_requests().create.assert_called_with(
             body={
                 'environment': {
+                    'HOME': '/var/spool/cwl',
                     'TMPDIR': '/tmp'
                 },
                 'name': 'test_resource_requirements',
index 0d3a494f00a24c333b989cfbbc47579948ba51b9..21b72d17db40853c89ab397a8b00ba893f1a4f01 100644 (file)
@@ -3,6 +3,7 @@ import logging
 import mock
 import unittest
 import os
+import functools
 import cwltool.process
 
 if not os.getenv('ARVADOS_DEBUG'):
@@ -26,9 +27,10 @@ class TestJob(unittest.TestCase):
             "baseCommand": "ls",
             "arguments": [{"valueFrom": "$(runtime.outdir)"}]
         }
-        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names, basedir="")
+        make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess, api_client=runner.api)
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names, basedir="", make_fs_access=make_fs_access)
         arvtool.formatgraph = None
-        for j in arvtool.job({}, mock.MagicMock(), basedir=""):
+        for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
             j.run()
             runner.api.jobs().create.assert_called_with(
                 body={
@@ -36,7 +38,7 @@ class TestJob(unittest.TestCase):
                     'runtime_constraints': {},
                     'script_parameters': {
                         'tasks': [{
-                            'task.env': {'TMPDIR': '$(task.tmpdir)'},
+                            'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
                             'command': ['ls', '$(task.outdir)']
                         }],
                     },
@@ -77,9 +79,10 @@ class TestJob(unittest.TestCase):
             }],
             "baseCommand": "ls"
         }
-        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names)
+        make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess, api_client=runner.api)
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names, make_fs_access=make_fs_access)
         arvtool.formatgraph = None
-        for j in arvtool.job({}, mock.MagicMock(), basedir=""):
+        for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
             j.run()
         runner.api.jobs().create.assert_called_with(
             body={
@@ -87,7 +90,7 @@ class TestJob(unittest.TestCase):
                 'runtime_constraints': {},
                 'script_parameters': {
                     'tasks': [{
-                        'task.env': {'TMPDIR': '$(task.tmpdir)'},
+                        'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
                         'command': ['ls']
                     }]
             },
diff --git a/sdk/cwl/tests/testdir/a b/sdk/cwl/tests/testdir/a
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sdk/cwl/tests/testdir/b b/sdk/cwl/tests/testdir/b
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sdk/cwl/tests/testdir/c/d b/sdk/cwl/tests/testdir/c/d
new file mode 100644 (file)
index 0000000..e69de29
index 037903c7bc26058e1147f7e3fd59efafcb09165b..2908e40e335ee87fc4bf78c3280ae823e6d9a759 100644 (file)
@@ -104,10 +104,27 @@ class ArvadosModel < ActiveRecord::Base
     api_column_map
   end
 
+  def self.ignored_select_attributes
+    ["href", "kind", "etag"]
+  end
+
   def self.columns_for_attributes(select_attributes)
+    if select_attributes.empty?
+      raise ArgumentError.new("Attribute selection list cannot be empty")
+    end
+    api_column_map = attributes_required_columns
+    invalid_attrs = []
+    select_attributes.each do |s|
+      next if ignored_select_attributes.include? s
+      if not s.is_a? String or not api_column_map.include? s
+        invalid_attrs << s
+      end
+    end
+    if not invalid_attrs.empty?
+      raise ArgumentError.new("Invalid attribute(s): #{invalid_attrs.inspect}")
+    end
     # Given an array of attribute names to select, return an array of column
     # names that must be fetched from the database to satisfy the request.
-    api_column_map = attributes_required_columns
     select_attributes.flat_map { |attr| api_column_map[attr] }.uniq
   end
 
index 4a612924b617f9b9b1dc5c4d7507f8113fdb5553..d0001fdc32bd5826ff607d3a6a193cc74724071a 100644 (file)
@@ -46,6 +46,10 @@ class Collection < ArvadosModel
                 )
   end
 
+  def self.ignored_select_attributes
+    super + ["updated_at", "file_names"]
+  end
+
   FILE_TOKEN = /^[[:digit:]]+:[[:digit:]]+:/
   def check_signatures
     return false if self.manifest_text.nil?
@@ -356,6 +360,12 @@ class Collection < ArvadosModel
     super - ["manifest_text"]
   end
 
+  def logged_attributes
+    attrs = attributes.dup
+    attrs.delete('manifest_text')
+    attrs
+  end
+
   protected
   def portable_manifest_text
     self.class.munge_manifest_locators(manifest_text) do |match|
index 4251047cea6b74ece4d8e4b1473d554e59daeb7e..0bedc0726a08549711f3455f509778b1f9901de3 100644 (file)
@@ -57,6 +57,34 @@ class CollectionsApiTest < ActionDispatch::IntegrationTest
     assert_equal "arvados#collectionList", json_response['kind']
   end
 
+  test "get index with select= (valid attribute)" do
+    get "/arvados/v1/collections", {
+          :format => :json,
+          :select => ['portable_data_hash'].to_json
+        }, auth(:active)
+    assert_response :success
+    assert json_response['items'][0].keys.include?('portable_data_hash')
+    assert not(json_response['items'][0].keys.include?('uuid'))
+  end
+
+  test "get index with select= (invalid attribute) responds 422" do
+    get "/arvados/v1/collections", {
+          :format => :json,
+          :select => ['bogus'].to_json
+        }, auth(:active)
+    assert_response 422
+    assert_match /Invalid attribute.*bogus/, json_response['errors'].join(' ')
+  end
+
+  test "get index with select= (invalid attribute type) responds 422" do
+    get "/arvados/v1/collections", {
+          :format => :json,
+          :select => [['bogus']].to_json
+        }, auth(:active)
+    assert_response 422
+    assert_match /Invalid attribute.*bogus/, json_response['errors'].join(' ')
+  end
+
   test "controller 404 response is json" do
     get "/arvados/v1/thingsthatdonotexist", {:format => :xml}, auth(:active)
     assert_response 404
index 22808c5ed6f8718d1f0b4cfb117562822e556c1f..6bca80c7780ebc7f847aa24827ea12614b03a54b 100644 (file)
@@ -54,12 +54,12 @@ class LogTest < ActiveSupport::TestCase
     yield props if block_given?
   end
 
-  def assert_auth_logged_with_clean_properties(auth, event_type)
-    assert_logged(auth, event_type) do |props|
+  def assert_logged_with_clean_properties(obj, event_type, excluded_attr)
+    assert_logged(obj, event_type) do |props|
       ['old_attributes', 'new_attributes'].map { |k| props[k] }.compact
         .each do |attributes|
-        refute_includes(attributes, 'api_token',
-                        "auth log properties include sensitive API token")
+        refute_includes(attributes, excluded_attr,
+                        "log properties includes #{excluded_attr}")
       end
       yield props if block_given?
     end
@@ -224,12 +224,12 @@ class LogTest < ActiveSupport::TestCase
     auth.user = users(:spectator)
     auth.api_client = api_clients(:untrusted)
     auth.save!
-    assert_auth_logged_with_clean_properties(auth, :create)
+    assert_logged_with_clean_properties(auth, :create, 'api_token')
     auth.expires_at = Time.now
     auth.save!
-    assert_auth_logged_with_clean_properties(auth, :update)
+    assert_logged_with_clean_properties(auth, :update, 'api_token')
     auth.destroy
-    assert_auth_logged_with_clean_properties(auth, :destroy)
+    assert_logged_with_clean_properties(auth, :destroy, 'api_token')
   end
 
   test "use ownership and permission links to determine which logs a user can see" do
@@ -269,4 +269,16 @@ class LogTest < ActiveSupport::TestCase
       refute_includes result_ids, logs(notwant).id
     end
   end
+
+  test "manifest_text not included in collection logs" do
+    act_as_system_user do
+      coll = Collection.create(manifest_text: ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo\n")
+      assert_logged_with_clean_properties(coll, :create, 'manifest_text')
+      coll.name = "testing"
+      coll.save!
+      assert_logged_with_clean_properties(coll, :update, 'manifest_text')
+      coll.destroy
+      assert_logged_with_clean_properties(coll, :destroy, 'manifest_text')
+    end
+  end
 end
index b11963c7040ac7e1ad0d5ce42e983c54f1bb5624..1d9015d5051f0a678a834ba56781377d39a4b96c 100644 (file)
@@ -21,6 +21,8 @@ import (
 
 // Config used by crunch-dispatch-slurm
 type Config struct {
+       arvados.Client
+
        SbatchArguments []string
        PollPeriod      arvados.Duration
 
@@ -71,6 +73,22 @@ func doMain() error {
                config.PollPeriod = arvados.Duration(10 * time.Second)
        }
 
+       if config.Client.APIHost != "" || config.Client.AuthToken != "" {
+               // Copy real configs into env vars so [a]
+               // MakeArvadosClient() uses them, and [b] they get
+               // propagated to crunch-run via SLURM.
+               os.Setenv("ARVADOS_API_HOST", config.Client.APIHost)
+               os.Setenv("ARVADOS_API_TOKEN", config.Client.AuthToken)
+               os.Setenv("ARVADOS_API_INSECURE", "")
+               if config.Client.Insecure {
+                       os.Setenv("ARVADOS_API_INSECURE", "1")
+               }
+               os.Setenv("ARVADOS_KEEP_SERVICES", "")
+               os.Setenv("ARVADOS_EXTERNAL_CLIENT", "")
+       } else {
+               log.Printf("warning: Client credentials missing from config, so falling back on environment variables (deprecated).")
+       }
+
        arv, err := arvadosclient.MakeArvadosClient()
        if err != nil {
                log.Printf("Error making Arvados client: %v", err)
index 683ff763fbc0fbeafac20e49cc29a962b5a2e0ef..e8a1f1899e405db1a53f1ba41b074935d444b8e5 100644 (file)
@@ -8,6 +8,11 @@ import (
 
 var exampleConfigFile = []byte(`
     {
+       "Client": {
+           "APIHost": "zzzzz.arvadosapi.com",
+           "AuthToken": "xyzzy",
+           "Insecure": false
+       },
        "CrunchRunCommand": ["crunch-run"],
        "PollPeriod": "10s",
        "SbatchArguments": ["--partition=foo", "--exclude=node13"]