1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
6 import arvados_cwl.context
7 import arvados_cwl.util
15 import cwltool.process
16 import cwltool.secrets
17 import cwltool.load_tool
18 from cwltool.update import INTERNAL_VERSION
19 from schema_salad.ref_resolver import Loader
20 from schema_salad.sourceline import cmap
22 from parameterized import parameterized
24 from unittest import mock
26 from .matcher import JsonDiffMatcher, StripYAMLComments
27 from .mock_discovery import get_rootDesc
29 if not os.getenv('ARVADOS_DEBUG'):
30 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
31 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
33 class CollectionMock(object):
34 def __init__(self, vwdmock, *args, **kwargs):
35 self.vwdmock = vwdmock
38 def open(self, *args, **kwargs):
40 return self.vwdmock.open(*args, **kwargs)
42 def copy(self, *args, **kwargs):
44 self.vwdmock.copy(*args, **kwargs)
46 def save_new(self, *args, **kwargs):
52 def portable_data_hash(self):
54 return arvados.config.EMPTY_BLOCK_LOCATOR
56 return "99999999999999999999999999999996+99"
59 class TestContainer(unittest.TestCase):
62 cwltool.process._names = set()
65 root_logger = logging.getLogger('')
67 # Remove existing RuntimeStatusLoggingHandlers if they exist
68 handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)]
69 root_logger.handlers = handlers
71 def helper(self, runner, enable_reuse=True):
72 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
74 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
75 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
76 fs_access = mock.MagicMock()
77 fs_access.exists.return_value = True
79 loadingContext = arvados_cwl.context.ArvLoadingContext(
80 {"avsc_names": avsc_names,
82 "make_fs_access": make_fs_access,
83 "construct_tool_object": runner.arv_make_tool,
84 "fetcher_constructor": functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=fs_access),
86 "metadata": cmap({"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"}),
87 "default_docker_image": "arvados/jobs:"+arvados_cwl.__version__,
89 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
90 {"work_api": "containers",
92 "name": "test_run_"+str(enable_reuse),
93 "make_fs_access": make_fs_access,
96 "enable_reuse": enable_reuse,
98 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
99 "workflow_eval_lock": threading.Condition(threading.RLock())
102 if isinstance(runner, mock.MagicMock):
103 def make_tool(toolpath_object, loadingContext):
104 return arvados_cwl.ArvadosCommandTool(runner, toolpath_object, loadingContext)
105 runner.arv_make_tool.side_effect = make_tool
107 return loadingContext, runtimeContext
109 # Helper function to set up the ArvCwlExecutor to use the containers api
110 # and test that the RuntimeStatusLoggingHandler is set up correctly
111 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
112 api = mock.MagicMock()
113 api._rootDesc = copy.deepcopy(get_rootDesc())
115 # Make sure ArvCwlExecutor thinks it's running inside a container so it
116 # adds the logging handler that will call runtime_status_update() mock
117 self.assertFalse(gcc_mock.called)
118 runner = arvados_cwl.ArvCwlExecutor(api)
119 self.assertEqual(runner.work_api, 'containers')
120 root_logger = logging.getLogger('')
121 handlerClasses = [h.__class__ for h in root_logger.handlers]
122 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
125 # The test passes no builder.resources
126 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
127 @parameterized.expand([
131 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
132 def test_run(self, enable_reuse, keepdocker):
133 runner = mock.MagicMock()
134 runner.ignore_docker_for_reuse = False
135 runner.intermediate_output_ttl = 0
136 runner.secret_store = cwltool.secrets.SecretStore()
137 runner.api._rootDesc = {"revision": "20210628"}
138 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
140 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
141 runner.api.collections().get().execute.return_value = {
142 "portable_data_hash": "99999999999999999999999999999993+99"}
148 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
150 "class": "CommandLineTool",
154 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
156 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
157 arvtool.formatgraph = None
159 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
160 j.run(runtimeContext)
161 runner.api.container_requests().create.assert_called_with(
162 body=JsonDiffMatcher({
164 'HOME': '/var/spool/cwl',
167 'name': 'test_run_'+str(enable_reuse),
168 'runtime_constraints': {
172 'use_existing': enable_reuse,
175 '/tmp': {'kind': 'tmp',
176 "capacity": 1073741824
178 '/var/spool/cwl': {'kind': 'tmp',
179 "capacity": 1073741824 }
181 'state': 'Committed',
182 'output_name': 'Output from step test_run_'+str(enable_reuse),
183 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
184 'output_path': '/var/spool/cwl',
186 'container_image': '99999999999999999999999999999993+99',
187 'command': ['ls', '/var/spool/cwl'],
188 'cwd': '/var/spool/cwl',
189 'scheduling_parameters': {},
190 'properties': {'cwl_input': {}},
192 'output_storage_classes': ["default"]
195 # The test passes some fields in builder.resources
196 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
197 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
198 def test_resource_requirements(self, keepdocker):
199 arvados_cwl.add_arv_hints()
200 runner = mock.MagicMock()
201 runner.ignore_docker_for_reuse = False
202 runner.intermediate_output_ttl = 3600
203 runner.secret_store = cwltool.secrets.SecretStore()
204 runner.api._rootDesc = {"revision": "20210628"}
205 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
207 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
208 runner.api.collections().get().execute.return_value = {
209 "portable_data_hash": "99999999999999999999999999999993+99"}
215 "class": "ResourceRequirement",
221 "class": "http://arvados.org/cwl#RuntimeConstraints",
224 "class": "http://arvados.org/cwl#APIRequirement",
226 "class": "http://arvados.org/cwl#PartitionRequirement",
229 "class": "http://arvados.org/cwl#IntermediateOutput",
232 "class": "WorkReuse",
237 "class": "CommandLineTool",
241 loadingContext, runtimeContext = self.helper(runner)
242 runtimeContext.name = "test_resource_requirements"
244 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
245 arvtool.formatgraph = None
246 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
247 j.run(runtimeContext)
249 call_args, call_kwargs = runner.api.container_requests().create.call_args
251 call_body_expected = {
253 'HOME': '/var/spool/cwl',
256 'name': 'test_resource_requirements',
257 'runtime_constraints': {
260 'keep_cache_ram': 536870912,
263 'use_existing': False,
266 '/tmp': {'kind': 'tmp',
267 "capacity": 4194304000 },
268 '/var/spool/cwl': {'kind': 'tmp',
269 "capacity": 5242880000 }
271 'state': 'Committed',
272 'output_name': 'Output from step test_resource_requirements',
273 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
274 'output_path': '/var/spool/cwl',
276 'container_image': '99999999999999999999999999999993+99',
278 'cwd': '/var/spool/cwl',
279 'scheduling_parameters': {
280 'partitions': ['blurb']
282 'properties': {'cwl_input': {}},
284 'output_storage_classes': ["default"]
287 call_body = call_kwargs.get('body', None)
288 self.assertNotEqual(None, call_body)
289 for key in call_body:
290 self.assertEqual(call_body_expected.get(key), call_body.get(key))
293 # The test passes some fields in builder.resources
294 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
295 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
296 @mock.patch("arvados.collection.Collection")
297 def test_initial_work_dir(self, collection_mock, keepdocker):
298 runner = mock.MagicMock()
299 runner.ignore_docker_for_reuse = False
300 runner.intermediate_output_ttl = 0
301 runner.secret_store = cwltool.secrets.SecretStore()
302 runner.api._rootDesc = {"revision": "20210628"}
303 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
305 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
306 runner.api.collections().get().execute.return_value = {
307 "portable_data_hash": "99999999999999999999999999999993+99"}
309 sourcemock = mock.MagicMock()
310 def get_collection_mock(p):
312 return (sourcemock, p.split("/", 1)[1])
314 return (sourcemock, "")
315 runner.fs_access.get_collection.side_effect = get_collection_mock
317 vwdmock = mock.MagicMock()
318 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
324 "class": "InitialWorkDirRequirement",
328 "location": "keep:99999999999999999999999999999995+99/bar"
331 "class": "Directory",
333 "location": "keep:99999999999999999999999999999995+99"
337 "basename": "filename",
338 "location": "keep:99999999999999999999999999999995+99/baz/filename"
341 "class": "Directory",
342 "basename": "subdir",
343 "location": "keep:99999999999999999999999999999995+99/subdir"
347 "class": "CommandLineTool",
348 "cwlVersion": "v1.2",
352 loadingContext, runtimeContext = self.helper(runner)
353 runtimeContext.name = "test_initial_work_dir"
355 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
357 arvtool.formatgraph = None
358 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
359 j.run(runtimeContext)
361 call_args, call_kwargs = runner.api.container_requests().create.call_args
363 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
364 vwdmock.copy.assert_has_calls([mock.call('.', 'foo2', source_collection=sourcemock)])
365 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
366 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
368 call_body_expected = {
370 'HOME': '/var/spool/cwl',
373 'name': 'test_initial_work_dir',
374 'runtime_constraints': {
378 'use_existing': True,
381 '/tmp': {'kind': 'tmp',
382 "capacity": 1073741824 },
383 '/var/spool/cwl': {'kind': 'tmp',
384 "capacity": 1073741824 },
385 '/var/spool/cwl/foo': {
386 'kind': 'collection',
388 'portable_data_hash': '99999999999999999999999999999996+99'
390 '/var/spool/cwl/foo2': {
391 'kind': 'collection',
393 'portable_data_hash': '99999999999999999999999999999996+99'
395 '/var/spool/cwl/filename': {
396 'kind': 'collection',
398 'portable_data_hash': '99999999999999999999999999999996+99'
400 '/var/spool/cwl/subdir': {
401 'kind': 'collection',
403 'portable_data_hash': '99999999999999999999999999999996+99'
406 'state': 'Committed',
407 'output_name': 'Output from step test_initial_work_dir',
408 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
409 'output_path': '/var/spool/cwl',
411 'container_image': '99999999999999999999999999999993+99',
413 'cwd': '/var/spool/cwl',
414 'scheduling_parameters': {
416 'properties': {'cwl_input': {}},
418 'output_storage_classes': ["default"]
421 call_body = call_kwargs.get('body', None)
422 self.assertNotEqual(None, call_body)
423 for key in call_body:
424 self.assertEqual(call_body_expected.get(key), call_body.get(key))
427 # Test redirecting stdin/stdout/stderr
428 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
429 def test_redirects(self, keepdocker):
430 runner = mock.MagicMock()
431 runner.ignore_docker_for_reuse = False
432 runner.intermediate_output_ttl = 0
433 runner.secret_store = cwltool.secrets.SecretStore()
434 runner.api._rootDesc = {"revision": "20210628"}
435 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
437 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
438 runner.api.collections().get().execute.return_value = {
439 "portable_data_hash": "99999999999999999999999999999993+99"}
441 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
447 "stdout": "stdout.txt",
448 "stderr": "stderr.txt",
449 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
450 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
452 "class": "CommandLineTool",
456 loadingContext, runtimeContext = self.helper(runner)
457 runtimeContext.name = "test_run_redirect"
459 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
460 arvtool.formatgraph = None
461 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
462 j.run(runtimeContext)
463 runner.api.container_requests().create.assert_called_with(
464 body=JsonDiffMatcher({
466 'HOME': '/var/spool/cwl',
469 'name': 'test_run_redirect',
470 'runtime_constraints': {
474 'use_existing': True,
477 '/tmp': {'kind': 'tmp',
478 "capacity": 1073741824 },
479 '/var/spool/cwl': {'kind': 'tmp',
480 "capacity": 1073741824 },
483 "path": "/var/spool/cwl/stderr.txt"
486 "kind": "collection",
488 "portable_data_hash": "99999999999999999999999999999996+99"
492 "path": "/var/spool/cwl/stdout.txt"
495 'state': 'Committed',
496 "output_name": "Output from step test_run_redirect",
497 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
498 'output_path': '/var/spool/cwl',
500 'container_image': '99999999999999999999999999999993+99',
501 'command': ['ls', '/var/spool/cwl'],
502 'cwd': '/var/spool/cwl',
503 'scheduling_parameters': {},
504 'properties': {'cwl_input': {}},
506 'output_storage_classes': ["default"]
509 @mock.patch("arvados.collection.Collection")
510 def test_done(self, col):
511 api = mock.MagicMock()
513 runner = mock.MagicMock()
515 runner.num_retries = 0
516 runner.ignore_docker_for_reuse = False
517 runner.intermediate_output_ttl = 0
518 runner.secret_store = cwltool.secrets.SecretStore()
520 runner.api.container_requests().get().execute.return_value = {"container_uuid":"zzzzz-xvhdp-zzzzzzzzzzzzzzz"}
522 runner.api.containers().get().execute.return_value = {"state":"Complete",
526 # Need to noop-out the close method otherwise it gets
527 # discarded when closed and we can't call getvalue() to check
529 class NoopCloseStringIO(io.StringIO):
533 usage_report = NoopCloseStringIO()
534 def colreader_action(name, mode):
535 nonlocal usage_report
536 if name == "node.json":
537 return io.StringIO("""{
538 "ProviderType": "c5.large",
541 "IncludedScratch": 8000000000000,
544 "Preemptible": false,
547 "HardwareCapability": "",
551 if name == 'crunchstat.txt':
552 return open("tests/container_request_9tee4-xvhdp-kk0ja1cl8b2kr1y-arv-mount.txt", "rt")
553 if name == 'arv-mount.txt':
554 return open("tests/container_request_9tee4-xvhdp-kk0ja1cl8b2kr1y-crunchstat.txt", "rt")
555 if name == 'usage_report.html':
559 col().open.side_effect = colreader_action
560 col().__iter__.return_value = ['node.json', 'crunchstat.txt', 'arv-mount.txt']
562 loadingContext, runtimeContext = self.helper(runner)
564 arvjob = arvados_cwl.ArvadosContainer(runner,
573 arvjob.output_callback = mock.MagicMock()
574 arvjob.collect_outputs = mock.MagicMock()
575 arvjob.successCodes = [0]
576 arvjob.outdir = "/var/spool/cwl"
577 arvjob.output_ttl = 3600
578 arvjob.uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzz1"
580 arvjob.collect_outputs.return_value = {"out": "stuff"}
584 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
585 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
586 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
587 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
588 "modified_at": "2017-05-26T12:01:22Z",
593 self.assertFalse(api.collections().create.called)
594 self.assertFalse(runner.runtime_status_error.called)
596 # Assert that something was written to the usage report
597 self.assertTrue(len(usage_report.getvalue()) > 0)
599 arvjob.collect_outputs.assert_called_with("keep:abc+123", 0)
600 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
601 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
603 runner.api.container_requests().update.assert_called_with(uuid="zzzzz-xvhdp-zzzzzzzzzzzzzz1",
604 body={'container_request': {'properties': {'cwl_output': {'out': 'stuff'}}}})
607 # Test to make sure we dont call runtime_status_update if we already did
608 # some where higher up in the call stack
609 @mock.patch("arvados_cwl.util.get_current_container")
610 def test_recursive_runtime_status_update(self, gcc_mock):
611 self.setup_and_test_container_executor_and_logging(gcc_mock)
612 root_logger = logging.getLogger('')
614 # get_current_container is invoked when we call runtime_status_update
615 # so try and log again!
616 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
618 root_logger.error("First Error")
620 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
623 # Test to make sure that an exception raised from
624 # get_current_container doesn't cause the logger to raise an
626 @mock.patch("arvados_cwl.util.get_current_container")
627 def test_runtime_status_get_current_container_exception(self, gcc_mock):
628 self.setup_and_test_container_executor_and_logging(gcc_mock)
629 root_logger = logging.getLogger('')
631 # get_current_container is invoked when we call
632 # runtime_status_update, it is going to also raise an
634 gcc_mock.side_effect = Exception("Second Error")
636 root_logger.error("First Error")
638 self.fail("Exception in logger should not propagate")
639 self.assertTrue(gcc_mock.called)
641 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
642 @mock.patch("arvados_cwl.util.get_current_container")
643 @mock.patch("arvados.collection.CollectionReader")
644 @mock.patch("arvados.collection.Collection")
645 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
646 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
648 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
649 self.assertTrue(gcc_mock.called)
651 runner.num_retries = 0
652 runner.ignore_docker_for_reuse = False
653 runner.intermediate_output_ttl = 0
654 runner.secret_store = cwltool.secrets.SecretStore()
655 runner.label = mock.MagicMock()
656 runner.label.return_value = '[container testjob]'
658 runner.api.containers().get().execute.return_value = {
665 col().open.return_value = []
667 loadingContext, runtimeContext = self.helper(runner)
669 arvjob = arvados_cwl.ArvadosContainer(runner,
678 arvjob.output_callback = mock.MagicMock()
679 arvjob.collect_outputs = mock.MagicMock()
680 arvjob.successCodes = [0]
681 arvjob.outdir = "/var/spool/cwl"
682 arvjob.output_ttl = 3600
683 arvjob.collect_outputs.return_value = {"out": "stuff"}
687 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
688 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
689 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
690 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
691 "modified_at": "2017-05-26T12:01:22Z",
695 rts_mock.assert_has_calls([
697 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
698 ' ** log is empty **'
701 'arvados.cwl-runner: [container testjob] unable to generate resource usage report'
703 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
705 # The test passes no builder.resources
706 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
707 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
708 def test_mounts(self, keepdocker):
709 runner = mock.MagicMock()
710 runner.ignore_docker_for_reuse = False
711 runner.intermediate_output_ttl = 0
712 runner.secret_store = cwltool.secrets.SecretStore()
713 runner.api._rootDesc = {"revision": "20210628"}
714 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
716 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
717 runner.api.collections().get().execute.return_value = {
718 "portable_data_hash": "99999999999999999999999999999994+99",
719 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
721 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
730 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
732 "class": "CommandLineTool",
736 loadingContext, runtimeContext = self.helper(runner)
737 runtimeContext.name = "test_run_mounts"
739 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
740 arvtool.formatgraph = None
743 "class": "Directory",
744 "location": "keep:99999999999999999999999999999994+44",
745 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
749 "location": "keep:99999999999999999999999999999994+44/file1",
753 "location": "keep:99999999999999999999999999999994+44/file2",
758 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
759 j.run(runtimeContext)
760 runner.api.container_requests().create.assert_called_with(
761 body=JsonDiffMatcher({
763 'HOME': '/var/spool/cwl',
766 'name': 'test_run_mounts',
767 'runtime_constraints': {
771 'use_existing': True,
774 "/keep/99999999999999999999999999999994+44": {
775 "kind": "collection",
776 "portable_data_hash": "99999999999999999999999999999994+44",
777 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
779 '/tmp': {'kind': 'tmp',
780 "capacity": 1073741824 },
781 '/var/spool/cwl': {'kind': 'tmp',
782 "capacity": 1073741824 }
784 'state': 'Committed',
785 'output_name': 'Output from step test_run_mounts',
786 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
787 'output_path': '/var/spool/cwl',
789 'container_image': '99999999999999999999999999999994+99',
790 'command': ['ls', '/var/spool/cwl'],
791 'cwd': '/var/spool/cwl',
792 'scheduling_parameters': {},
793 'properties': {'cwl_input': {
795 "basename": "99999999999999999999999999999994+44",
796 "class": "Directory",
798 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
803 "dirname": "/keep/99999999999999999999999999999994+44",
804 "location": "keep:99999999999999999999999999999994+44/file1",
807 "path": "/keep/99999999999999999999999999999994+44/file1",
813 "dirname": "/keep/99999999999999999999999999999994+44",
814 "location": "keep:99999999999999999999999999999994+44/file2",
817 "path": "/keep/99999999999999999999999999999994+44/file2",
821 "location": "keep:99999999999999999999999999999994+44",
822 "path": "/keep/99999999999999999999999999999994+44"
826 'output_storage_classes': ["default"]
829 # The test passes no builder.resources
830 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
831 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
832 def test_secrets(self, keepdocker):
833 arvados_cwl.add_arv_hints()
834 runner = mock.MagicMock()
835 runner.ignore_docker_for_reuse = False
836 runner.intermediate_output_ttl = 0
837 runner.secret_store = cwltool.secrets.SecretStore()
838 runner.api._rootDesc = {"revision": "20210628"}
839 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
841 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
842 runner.api.collections().get().execute.return_value = {
843 "portable_data_hash": "99999999999999999999999999999993+99"}
845 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
847 tool = cmap({"arguments": ["md5sum", "example.conf"],
848 "class": "CommandLineTool",
849 "cwlVersion": "v1.2",
852 "class": "http://commonwl.org/cwltool#Secrets",
861 "id": "#secret_job.cwl/pw",
869 "class": "InitialWorkDirRequirement",
872 "entry": "username: user\npassword: $(inputs.pw)\n",
873 "entryname": "example.conf"
879 loadingContext, runtimeContext = self.helper(runner)
880 runtimeContext.name = "test_secrets"
882 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
883 arvtool.formatgraph = None
885 job_order = {"pw": "blorp"}
886 runner.secret_store.store(["pw"], job_order)
888 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
889 j.run(runtimeContext)
890 runner.api.container_requests().create.assert_called_with(
891 body=JsonDiffMatcher({
893 'HOME': '/var/spool/cwl',
896 'name': 'test_secrets',
897 'runtime_constraints': {
901 'use_existing': True,
904 '/tmp': {'kind': 'tmp',
905 "capacity": 1073741824
907 '/var/spool/cwl': {'kind': 'tmp',
908 "capacity": 1073741824 }
910 'state': 'Committed',
911 'output_name': 'Output from step test_secrets',
912 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
913 'output_path': '/var/spool/cwl',
915 'container_image': '99999999999999999999999999999993+99',
916 'command': ['md5sum', 'example.conf'],
917 'cwd': '/var/spool/cwl',
918 'scheduling_parameters': {},
919 'properties': {'cwl_input': job_order},
921 "/var/spool/cwl/example.conf": {
922 "content": "username: user\npassword: blorp\n",
926 'output_storage_classes': ["default"]
929 # The test passes no builder.resources
930 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
931 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
932 def test_timelimit(self, keepdocker):
933 runner = mock.MagicMock()
934 runner.ignore_docker_for_reuse = False
935 runner.intermediate_output_ttl = 0
936 runner.secret_store = cwltool.secrets.SecretStore()
937 runner.api._rootDesc = {"revision": "20210628"}
938 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
940 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
941 runner.api.collections().get().execute.return_value = {
942 "portable_data_hash": "99999999999999999999999999999993+99"}
948 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
950 "cwlVersion": "v1.2",
951 "class": "CommandLineTool",
954 "class": "ToolTimeLimit",
960 loadingContext, runtimeContext = self.helper(runner)
961 runtimeContext.name = "test_timelimit"
963 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
964 arvtool.formatgraph = None
966 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
967 j.run(runtimeContext)
969 _, kwargs = runner.api.container_requests().create.call_args
970 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
973 # The test passes no builder.resources
974 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
975 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
976 def test_setting_storage_class(self, keepdocker):
977 runner = mock.MagicMock()
978 runner.ignore_docker_for_reuse = False
979 runner.intermediate_output_ttl = 0
980 runner.secret_store = cwltool.secrets.SecretStore()
981 runner.api._rootDesc = {"revision": "20210628"}
982 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
984 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
985 runner.api.collections().get().execute.return_value = {
986 "portable_data_hash": "99999999999999999999999999999993+99"}
992 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
994 "cwlVersion": "v1.2",
995 "class": "CommandLineTool",
998 "class": "http://arvados.org/cwl#OutputStorageClass",
999 "finalStorageClass": ["baz_sc", "qux_sc"],
1000 "intermediateStorageClass": ["foo_sc", "bar_sc"]
1005 loadingContext, runtimeContext = self.helper(runner, True)
1007 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1008 arvtool.formatgraph = None
1010 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1011 j.run(runtimeContext)
1012 runner.api.container_requests().create.assert_called_with(
1013 body=JsonDiffMatcher({
1015 'HOME': '/var/spool/cwl',
1018 'name': 'test_run_True',
1019 'runtime_constraints': {
1023 'use_existing': True,
1026 '/tmp': {'kind': 'tmp',
1027 "capacity": 1073741824
1029 '/var/spool/cwl': {'kind': 'tmp',
1030 "capacity": 1073741824 }
1032 'state': 'Committed',
1033 'output_name': 'Output from step test_run_True',
1034 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1035 'output_path': '/var/spool/cwl',
1037 'container_image': '99999999999999999999999999999993+99',
1038 'command': ['ls', '/var/spool/cwl'],
1039 'cwd': '/var/spool/cwl',
1040 'scheduling_parameters': {},
1041 'properties': {'cwl_input': {}},
1042 'secret_mounts': {},
1043 'output_storage_classes': ["foo_sc", "bar_sc"]
1047 # The test passes no builder.resources
1048 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1049 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1050 def test_setting_process_properties(self, keepdocker):
1051 runner = mock.MagicMock()
1052 runner.ignore_docker_for_reuse = False
1053 runner.intermediate_output_ttl = 0
1054 runner.secret_store = cwltool.secrets.SecretStore()
1055 runner.api._rootDesc = {"revision": "20210628"}
1056 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1058 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1059 runner.api.collections().get().execute.return_value = {
1060 "portable_data_hash": "99999999999999999999999999999993+99"}
1064 {"id": "x", "type": "string"}],
1066 "baseCommand": "ls",
1067 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1069 "class": "CommandLineTool",
1070 "cwlVersion": "v1.2",
1073 "class": "http://arvados.org/cwl#ProcessProperties",
1074 "processProperties": [
1075 {"propertyName": "foo",
1076 "propertyValue": "bar"},
1077 {"propertyName": "baz",
1078 "propertyValue": "$(inputs.x)"},
1079 {"propertyName": "quux",
1090 loadingContext, runtimeContext = self.helper(runner, True)
1092 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1093 arvtool.formatgraph = None
1095 for j in arvtool.job({"x": "blorp"}, mock.MagicMock(), runtimeContext):
1096 j.run(runtimeContext)
1097 runner.api.container_requests().create.assert_called_with(
1098 body=JsonDiffMatcher({
1100 'HOME': '/var/spool/cwl',
1103 'name': 'test_run_True',
1104 'runtime_constraints': {
1108 'use_existing': True,
1111 '/tmp': {'kind': 'tmp',
1112 "capacity": 1073741824
1114 '/var/spool/cwl': {'kind': 'tmp',
1115 "capacity": 1073741824 }
1117 'state': 'Committed',
1118 'output_name': 'Output from step test_run_True',
1119 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1120 'output_path': '/var/spool/cwl',
1122 'container_image': '99999999999999999999999999999993+99',
1123 'command': ['ls', '/var/spool/cwl'],
1124 'cwd': '/var/spool/cwl',
1125 'scheduling_parameters': {},
1128 "cwl_input": {"x": "blorp"},
1135 'secret_mounts': {},
1136 'output_storage_classes': ["default"]
1140 # The test passes no builder.resources
1141 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1142 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1143 def test_cuda_requirement(self, keepdocker):
1144 arvados_cwl.add_arv_hints()
1146 runner = mock.MagicMock()
1147 runner.ignore_docker_for_reuse = False
1148 runner.intermediate_output_ttl = 0
1149 runner.secret_store = cwltool.secrets.SecretStore()
1150 runner.api._rootDesc = {"revision": "20210628"}
1151 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1153 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1154 runner.api.collections().get().execute.return_value = {
1155 "portable_data_hash": "99999999999999999999999999999993+99"}
1158 "class": "http://commonwl.org/cwltool#CUDARequirement",
1159 "cudaVersionMin": "11.0",
1160 "cudaComputeCapability": "9.0",
1162 "class": "http://commonwl.org/cwltool#CUDARequirement",
1163 "cudaVersionMin": "11.0",
1164 "cudaComputeCapability": "9.0",
1165 "cudaDeviceCountMin": 2
1167 "class": "http://commonwl.org/cwltool#CUDARequirement",
1168 "cudaVersionMin": "11.0",
1169 "cudaComputeCapability": ["4.0", "5.0"],
1170 "cudaDeviceCountMin": 2
1175 'driver_version': "11.0",
1176 'hardware_capability': "9.0"
1179 'driver_version': "11.0",
1180 'hardware_capability': "9.0"
1183 'driver_version': "11.0",
1184 'hardware_capability': "4.0"
1187 for test_case in range(0, len(test_cwl_req)):
1192 "baseCommand": "nvidia-smi",
1195 "cwlVersion": "v1.2",
1196 "class": "CommandLineTool",
1197 "requirements": [test_cwl_req[test_case]]
1200 loadingContext, runtimeContext = self.helper(runner, True)
1202 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1203 arvtool.formatgraph = None
1205 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1206 j.run(runtimeContext)
1207 runner.api.container_requests().create.assert_called_with(
1208 body=JsonDiffMatcher({
1210 'HOME': '/var/spool/cwl',
1213 'name': 'test_run_True' + ("" if test_case == 0 else "_"+str(test_case+1)),
1214 'runtime_constraints': {
1217 'cuda': test_arv_req[test_case]
1219 'use_existing': True,
1222 '/tmp': {'kind': 'tmp',
1223 "capacity": 1073741824
1225 '/var/spool/cwl': {'kind': 'tmp',
1226 "capacity": 1073741824 }
1228 'state': 'Committed',
1229 'output_name': 'Output from step test_run_True' + ("" if test_case == 0 else "_"+str(test_case+1)),
1230 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1231 'output_path': '/var/spool/cwl',
1233 'container_image': '99999999999999999999999999999993+99',
1234 'command': ['nvidia-smi'],
1235 'cwd': '/var/spool/cwl',
1236 'scheduling_parameters': {},
1237 'properties': {'cwl_input': {}},
1238 'secret_mounts': {},
1239 'output_storage_classes': ["default"]
1243 # The test passes no builder.resources
1244 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1245 @mock.patch("arvados_cwl.arvdocker.determine_image_id")
1246 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1247 def test_match_local_docker(self, keepdocker, determine_image_id):
1248 arvados_cwl.add_arv_hints()
1250 runner = mock.MagicMock()
1251 runner.ignore_docker_for_reuse = False
1252 runner.intermediate_output_ttl = 0
1253 runner.secret_store = cwltool.secrets.SecretStore()
1254 runner.api._rootDesc = {"revision": "20210628"}
1255 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1257 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz4", {"dockerhash": "456"}),
1258 ("zzzzz-4zz18-zzzzzzzzzzzzzz3", {"dockerhash": "123"})]
1259 determine_image_id.side_effect = lambda x: "123"
1261 ex = mock.MagicMock()
1262 lookup = {"zzzzz-4zz18-zzzzzzzzzzzzzz4": {"portable_data_hash": "99999999999999999999999999999994+99"},
1263 "zzzzz-4zz18-zzzzzzzzzzzzzz3": {"portable_data_hash": "99999999999999999999999999999993+99"}}
1264 ex.execute.return_value = lookup[uuid]
1266 runner.api.collections().get.side_effect = execute
1271 "baseCommand": "echo",
1274 "cwlVersion": "v1.0",
1275 "class": "org.w3id.cwl.cwl.CommandLineTool"
1278 loadingContext, runtimeContext = self.helper(runner, True)
1280 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
1281 arvtool.formatgraph = None
1283 container_request = {
1285 'HOME': '/var/spool/cwl',
1288 'name': 'test_run_True',
1289 'runtime_constraints': {
1293 'use_existing': True,
1296 '/tmp': {'kind': 'tmp',
1297 "capacity": 1073741824
1299 '/var/spool/cwl': {'kind': 'tmp',
1300 "capacity": 1073741824 }
1302 'state': 'Committed',
1303 'output_name': 'Output from step test_run_True',
1304 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1305 'output_path': '/var/spool/cwl',
1307 'container_image': '99999999999999999999999999999994+99',
1308 'command': ['echo'],
1309 'cwd': '/var/spool/cwl',
1310 'scheduling_parameters': {},
1311 'properties': {'cwl_input': {}},
1312 'secret_mounts': {},
1313 'output_storage_classes': ["default"]
1316 runtimeContext.match_local_docker = False
1317 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1318 j.run(runtimeContext)
1319 runner.api.container_requests().create.assert_called_with(
1320 body=JsonDiffMatcher(container_request))
1322 runtimeContext.cached_docker_lookups.clear()
1323 runtimeContext.match_local_docker = True
1324 container_request['container_image'] = '99999999999999999999999999999993+99'
1325 container_request['name'] = 'test_run_True_2'
1326 container_request['output_name'] = 'Output from step test_run_True_2'
1327 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1328 j.run(runtimeContext)
1329 runner.api.container_requests().create.assert_called_with(
1330 body=JsonDiffMatcher(container_request))
1333 # The test passes no builder.resources
1334 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1335 @parameterized.expand([
1336 ("None, None", None, None, None),
1337 ("None, True", None, True, True),
1338 ("None, False", None, False, False),
1339 ("False, None", False, None, False),
1340 ("False, True", False, True, False),
1341 ("False, False", False, False, False),
1342 ("True, None", True, None, True),
1343 ("True, True", True, True, True),
1344 ("True, False", True, False, False),
1346 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1347 def test_run_preemptible_hint(self, _, enable_preemptible, preemptible_hint,
1348 preemptible_setting, keepdocker):
1349 arvados_cwl.add_arv_hints()
1351 runner = mock.MagicMock()
1352 runner.ignore_docker_for_reuse = False
1353 runner.intermediate_output_ttl = 0
1354 runner.secret_store = cwltool.secrets.SecretStore()
1355 runner.api._rootDesc = {"revision": "20210628"}
1356 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1358 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1359 runner.api.collections().get().execute.return_value = {
1360 "portable_data_hash": "99999999999999999999999999999993+99"}
1362 if preemptible_hint is not None:
1364 "class": "http://arvados.org/cwl#UsePreemptible",
1365 "usePreemptible": preemptible_hint
1373 "baseCommand": "ls",
1374 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1376 "class": "CommandLineTool",
1377 "cwlVersion": "v1.2",
1381 loadingContext, runtimeContext = self.helper(runner)
1383 runtimeContext.name = 'test_run_enable_preemptible_'+str(enable_preemptible)+str(preemptible_hint)
1384 runtimeContext.enable_preemptible = enable_preemptible
1386 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1387 arvtool.formatgraph = None
1389 # Test the interactions between --enable/disable-preemptible
1390 # and UsePreemptible hint
1393 if preemptible_setting is not None:
1394 sched['preemptible'] = preemptible_setting
1396 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1397 j.run(runtimeContext)
1398 runner.api.container_requests().create.assert_called_with(
1399 body=JsonDiffMatcher({
1401 'HOME': '/var/spool/cwl',
1404 'name': runtimeContext.name,
1405 'runtime_constraints': {
1409 'use_existing': True,
1412 '/tmp': {'kind': 'tmp',
1413 "capacity": 1073741824
1415 '/var/spool/cwl': {'kind': 'tmp',
1416 "capacity": 1073741824 }
1418 'state': 'Committed',
1419 'output_name': 'Output from step '+runtimeContext.name,
1420 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1421 'output_path': '/var/spool/cwl',
1423 'container_image': '99999999999999999999999999999993+99',
1424 'command': ['ls', '/var/spool/cwl'],
1425 'cwd': '/var/spool/cwl',
1426 'scheduling_parameters': sched,
1427 'properties': {'cwl_input': {}},
1428 'secret_mounts': {},
1429 'output_storage_classes': ["default"]
1433 # The test passes no builder.resources
1434 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1435 @parameterized.expand([
1436 ("None, None", None, None, False),
1437 ("None, True", None, True, True),
1438 ("None, False", None, False, False),
1439 ("False, None", False, None, False),
1440 ("False, True", False, True, False), # command line overrides hint
1441 ("False, False", False, False, False),
1442 ("True, None", True, None, True),
1443 ("True, True", True, True, True),
1444 ("True, False", True, False, False), # hint overrides command line
1446 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1447 def test_spot_retry(self, _, enable_resubmit_non_preemptible,
1448 preemption_behavior_hint,
1449 expect_resubmit_behavior,
1451 arvados_cwl.add_arv_hints()
1453 runner = mock.MagicMock()
1454 runner.ignore_docker_for_reuse = False
1455 runner.intermediate_output_ttl = 0
1456 runner.secret_store = cwltool.secrets.SecretStore()
1457 runner.api._rootDesc = {"revision": "20210628"}
1458 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1460 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1461 runner.api.collections().get().execute.return_value = {
1462 "portable_data_hash": "99999999999999999999999999999993+99"}
1466 "class": "http://arvados.org/cwl#UsePreemptible",
1467 "usePreemptible": True
1470 if preemption_behavior_hint is not None:
1472 "class": "http://arvados.org/cwl#PreemptionBehavior",
1473 "resubmitNonPreemptible": preemption_behavior_hint
1479 "baseCommand": "ls",
1480 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1482 "class": "CommandLineTool",
1483 "cwlVersion": "v1.2",
1487 loadingContext, runtimeContext = self.helper(runner)
1489 runtimeContext.name = 'test_spot_retry_'+str(enable_resubmit_non_preemptible)+str(preemption_behavior_hint)
1490 runtimeContext.enable_resubmit_non_preemptible = enable_resubmit_non_preemptible
1492 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1493 arvtool.formatgraph = None
1495 # Test the interactions between --enable/disable-preemptible
1496 # and UsePreemptible hint
1498 expect_container_request = {
1500 'HOME': '/var/spool/cwl',
1503 'name': runtimeContext.name,
1504 'runtime_constraints': {
1508 'use_existing': True,
1511 '/tmp': {'kind': 'tmp',
1512 "capacity": 1073741824
1514 '/var/spool/cwl': {'kind': 'tmp',
1515 "capacity": 1073741824 }
1517 'state': 'Committed',
1518 'output_name': 'Output from step '+runtimeContext.name,
1519 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1520 'output_path': '/var/spool/cwl',
1522 'container_image': '99999999999999999999999999999993+99',
1523 'command': ['ls', '/var/spool/cwl'],
1524 'cwd': '/var/spool/cwl',
1525 'scheduling_parameters': {'preemptible': True},
1526 'properties': {'cwl_input': {}},
1527 'secret_mounts': {},
1528 'output_storage_classes': ["default"],
1531 expect_resubmit_container_request = expect_container_request.copy()
1532 expect_resubmit_container_request['scheduling_parameters'] = {'preemptible': False}
1534 runner.api.container_requests().create().execute.return_value = {"uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
1535 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"}
1537 if expect_resubmit_behavior:
1538 expect_container_request['container_count_max'] = 1
1540 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1541 j.run(runtimeContext)
1542 runner.api.container_requests().create.assert_called_with(
1543 body=JsonDiffMatcher(expect_container_request))
1544 runner.api.containers().get().execute.return_value = {
1545 "state":"Cancelled",
1546 "output": "abc+123",
1550 "preemptionNotice": "bye bye"
1553 runner.api.container_requests().create().execute.return_value = {"uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzz2",
1554 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzz2"}
1559 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
1560 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
1561 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
1562 "modified_at": "2017-05-26T12:01:22Z",
1566 if expect_resubmit_behavior:
1567 runner.api.container_requests().update.assert_any_call(
1568 uuid="zzzzz-xvhdp-zzzzzzzzzzzzzzz", body={"properties": {"arv:failed_container_resubmitted": "zzzzz-xvhdp-zzzzzzzzzzzzzz2"}})
1569 runner.api.container_requests().create.assert_called_with(
1570 body=JsonDiffMatcher(expect_resubmit_container_request))
1572 @parameterized.expand([
1576 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1577 def test_output_properties(self, rev, keepdocker):
1578 arvados_cwl.add_arv_hints()
1579 runner = mock.MagicMock()
1580 runner.ignore_docker_for_reuse = False
1581 runner.intermediate_output_ttl = 0
1582 runner.secret_store = cwltool.secrets.SecretStore()
1583 runner.api._rootDesc = {"revision": rev}
1584 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1586 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1587 runner.api.collections().get().execute.return_value = {
1588 "portable_data_hash": "99999999999999999999999999999993+99"}
1596 "baseCommand": "ls",
1597 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1599 "cwlVersion": "v1.2",
1600 "class": "CommandLineTool",
1603 "class": "http://arvados.org/cwl#OutputCollectionProperties",
1604 "outputProperties": {
1606 "baz": "$(inputs.inp)"
1612 loadingContext, runtimeContext = self.helper(runner)
1613 runtimeContext.name = "test_timelimit"
1615 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1616 arvtool.formatgraph = None
1618 for j in arvtool.job({"inp": "quux"}, mock.MagicMock(), runtimeContext):
1619 j.run(runtimeContext)
1621 _, kwargs = runner.api.container_requests().create.call_args
1622 if rev == "20220510":
1623 self.assertEqual({"foo": "bar", "baz": "quux"}, kwargs['body'].get('output_properties'))
1625 self.assertEqual(None, kwargs['body'].get('output_properties'))
1627 @parameterized.expand([
1631 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1632 def test_output_glob(self, rev, keepdocker):
1633 arvados_cwl.add_arv_hints()
1634 runner = mock.MagicMock()
1635 runner.ignore_docker_for_reuse = False
1636 runner.intermediate_output_ttl = 0
1637 runner.secret_store = cwltool.secrets.SecretStore()
1638 runner.api._rootDesc = {"revision": rev}
1639 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1641 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1642 runner.api.collections().get().execute.return_value = {
1643 "portable_data_hash": "99999999999999999999999999999993+99"}
1662 "glob": ["*.dat", "*.bat"]
1684 "glob": "$(inputs.inp)"
1693 "secondaryFiles": [".goo", "^.hoo"]
1697 "baseCommand": "ls",
1698 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1700 "cwlVersion": "v1.2",
1701 "class": "CommandLineTool",
1705 loadingContext, runtimeContext = self.helper(runner)
1706 runtimeContext.name = "test_timelimit"
1708 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1709 arvtool.formatgraph = None
1711 for j in arvtool.job({"inp": "quux"}, mock.MagicMock(), runtimeContext):
1712 j.run(runtimeContext)
1714 _, kwargs = runner.api.container_requests().create.call_args
1715 if rev == "20240502":
1716 self.assertEqual(['*.txt', '*.txt/**',
1717 '*.dat', '*.dat/**',
1718 '*.bat', '*.bat/**',
1719 '*.cat', '*.cat/**',
1721 '*.foo', '*.foo/**',
1722 '*.foo.goo', '*.foo.goo/**',
1723 '*.hoo', '*.hoo/**',
1725 ], kwargs['body'].get('output_glob'))
1727 self.assertEqual(None, kwargs['body'].get('output_glob'))
1730 class TestWorkflow(unittest.TestCase):
1732 cwltool.process._names = set()
1734 def helper(self, runner, enable_reuse=True):
1735 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
1737 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
1738 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
1740 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
1741 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
1742 document_loader.fetch_text = document_loader.fetcher.fetch_text
1743 document_loader.check_exists = document_loader.fetcher.check_exists
1745 loadingContext = arvados_cwl.context.ArvLoadingContext(
1746 {"avsc_names": avsc_names,
1748 "make_fs_access": make_fs_access,
1749 "loader": document_loader,
1750 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
1751 "construct_tool_object": runner.arv_make_tool,
1752 "default_docker_image": "arvados/jobs:"+arvados_cwl.__version__,
1754 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
1755 {"work_api": "containers",
1757 "name": "test_run_wf_"+str(enable_reuse),
1758 "make_fs_access": make_fs_access,
1760 "enable_reuse": enable_reuse,
1763 return loadingContext, runtimeContext
1765 # The test passes no builder.resources
1766 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1767 @mock.patch("arvados.collection.CollectionReader")
1768 @mock.patch("arvados.collection.Collection")
1769 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1770 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
1771 arvados_cwl.add_arv_hints()
1773 api = mock.MagicMock()
1774 api._rootDesc = get_rootDesc()
1775 api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1777 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1778 self.assertEqual(runner.work_api, 'containers')
1780 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1781 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
1782 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1783 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1785 runner.api.containers().current().execute.return_value = {}
1787 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1788 runner.ignore_docker_for_reuse = False
1789 runner.num_retries = 0
1790 runner.secret_store = cwltool.secrets.SecretStore()
1792 loadingContext, runtimeContext = self.helper(runner)
1793 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1795 mockcollectionreader().exists.return_value = True
1797 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
1798 metadata["cwlVersion"] = tool["cwlVersion"]
1800 mockc = mock.MagicMock()
1801 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
1802 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
1804 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1805 arvtool.formatgraph = None
1806 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1808 next(it).run(runtimeContext)
1809 next(it).run(runtimeContext)
1811 with open("tests/wf/scatter2_subwf.cwl") as f:
1812 subwf = StripYAMLComments(f.read()).rstrip()
1814 runner.api.container_requests().create.assert_called_with(
1815 body=JsonDiffMatcher({
1820 "--preserve-entire-environment",
1824 "container_image": "99999999999999999999999999999993+99",
1825 "cwd": "/var/spool/cwl",
1827 "HOME": "/var/spool/cwl",
1831 "/keep/99999999999999999999999999999999+118": {
1832 "kind": "collection",
1833 "portable_data_hash": "99999999999999999999999999999999+118"
1836 "capacity": 1073741824,
1840 "capacity": 1073741824,
1843 "/var/spool/cwl/cwl.input.yml": {
1844 "kind": "collection",
1845 "path": "cwl.input.yml",
1846 "portable_data_hash": "99999999999999999999999999999996+99"
1848 "/var/spool/cwl/workflow.cwl": {
1849 "kind": "collection",
1850 "path": "workflow.cwl",
1851 "portable_data_hash": "99999999999999999999999999999996+99"
1855 "path": "/var/spool/cwl/cwl.output.json"
1858 "name": "scatterstep",
1859 "output_name": "Output from step scatterstep",
1860 "output_path": "/var/spool/cwl",
1863 "properties": {'cwl_input': {
1865 "basename": "token.txt",
1867 "dirname": "/keep/99999999999999999999999999999999+118",
1868 "location": "keep:99999999999999999999999999999999+118/token.txt",
1870 "nameroot": "token",
1871 "path": "/keep/99999999999999999999999999999999+118/token.txt",
1876 "runtime_constraints": {
1880 "scheduling_parameters": {},
1881 "secret_mounts": {},
1882 "state": "Committed",
1883 "use_existing": True,
1884 'output_storage_classes': ["default"]
1886 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
1887 mockc.open().__enter__().write.assert_has_calls([mock.call(
1890 "basename": "token.txt",
1892 "location": "/keep/99999999999999999999999999999999+118/token.txt",
1898 # The test passes no builder.resources
1899 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1900 @mock.patch("arvados.collection.CollectionReader")
1901 @mock.patch("arvados.collection.Collection")
1902 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1903 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
1904 arvados_cwl.add_arv_hints()
1906 api = mock.MagicMock()
1907 api._rootDesc = get_rootDesc()
1908 api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1910 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1911 self.assertEqual(runner.work_api, 'containers')
1913 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1914 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1915 "portable_data_hash": "99999999999999999999999999999993+99"}
1916 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1917 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1919 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1920 runner.ignore_docker_for_reuse = False
1921 runner.num_retries = 0
1922 runner.secret_store = cwltool.secrets.SecretStore()
1924 loadingContext, runtimeContext = self.helper(runner)
1925 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1926 loadingContext.do_update = True
1927 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1929 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1931 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1932 arvtool.formatgraph = None
1933 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1935 next(it).run(runtimeContext)
1936 next(it).run(runtimeContext)
1938 with open("tests/wf/echo-subwf.cwl") as f:
1939 subwf = StripYAMLComments(f.read())
1941 runner.api.container_requests().create.assert_called_with(
1942 body=JsonDiffMatcher({
1944 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1945 'scheduling_parameters': {},
1946 'name': u'echo-subwf',
1947 'secret_mounts': {},
1948 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1949 'properties': {'cwl_input': {}},
1952 '/var/spool/cwl/cwl.input.yml': {
1953 'portable_data_hash': '99999999999999999999999999999996+99',
1954 'kind': 'collection',
1955 'path': 'cwl.input.yml'
1957 '/var/spool/cwl/workflow.cwl': {
1958 'portable_data_hash': '99999999999999999999999999999996+99',
1959 'kind': 'collection',
1960 'path': 'workflow.cwl'
1963 'path': '/var/spool/cwl/cwl.output.json',
1968 'capacity': 1073741824
1969 }, '/var/spool/cwl': {
1971 'capacity': 3221225472
1974 'state': 'Committed',
1975 'output_path': '/var/spool/cwl',
1976 'container_image': '99999999999999999999999999999993+99',
1981 u'--preserve-entire-environment',
1985 'use_existing': True,
1986 'output_name': u'Output from step echo-subwf',
1987 'cwd': '/var/spool/cwl',
1988 'output_storage_classes': ["default"]
1991 def test_default_work_api(self):
1992 arvados_cwl.add_arv_hints()
1994 api = mock.MagicMock()
1995 api._rootDesc = copy.deepcopy(get_rootDesc())
1996 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1997 self.assertEqual(runner.work_api, 'containers')