1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from builtins import str
6 from builtins import object
9 import arvados_cwl.context
10 import arvados_cwl.util
11 from arvados_cwl.arvdocker import arv_docker_clear_cache
19 import cwltool.process
20 import cwltool.secrets
21 from cwltool.update import INTERNAL_VERSION
22 from schema_salad.ref_resolver import Loader
23 from schema_salad.sourceline import cmap
25 from .matcher import JsonDiffMatcher, StripYAMLComments
26 from .mock_discovery import get_rootDesc
28 if not os.getenv('ARVADOS_DEBUG'):
29 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
30 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
32 class CollectionMock(object):
33 def __init__(self, vwdmock, *args, **kwargs):
34 self.vwdmock = vwdmock
37 def open(self, *args, **kwargs):
39 return self.vwdmock.open(*args, **kwargs)
41 def copy(self, *args, **kwargs):
43 self.vwdmock.copy(*args, **kwargs)
45 def save_new(self, *args, **kwargs):
51 def portable_data_hash(self):
53 return arvados.config.EMPTY_BLOCK_LOCATOR
55 return "99999999999999999999999999999996+99"
58 class TestContainer(unittest.TestCase):
61 cwltool.process._names = set()
62 arv_docker_clear_cache()
64 def helper(self, runner, enable_reuse=True):
65 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
67 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
68 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
69 loadingContext = arvados_cwl.context.ArvLoadingContext(
70 {"avsc_names": avsc_names,
72 "make_fs_access": make_fs_access,
74 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"}})
75 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
76 {"work_api": "containers",
78 "name": "test_run_"+str(enable_reuse),
79 "make_fs_access": make_fs_access,
81 "enable_reuse": enable_reuse,
83 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
86 return loadingContext, runtimeContext
88 # Helper function to set up the ArvCwlExecutor to use the containers api
89 # and test that the RuntimeStatusLoggingHandler is set up correctly
90 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
91 api = mock.MagicMock()
92 api._rootDesc = copy.deepcopy(get_rootDesc())
94 # Make sure ArvCwlExecutor thinks it's running inside a container so it
95 # adds the logging handler that will call runtime_status_update() mock
96 self.assertFalse(gcc_mock.called)
97 runner = arvados_cwl.ArvCwlExecutor(api)
98 self.assertEqual(runner.work_api, 'containers')
99 root_logger = logging.getLogger('')
100 handlerClasses = [h.__class__ for h in root_logger.handlers]
101 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
104 # The test passes no builder.resources
105 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
106 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
107 def test_run(self, keepdocker):
108 for enable_reuse in (True, False):
109 arv_docker_clear_cache()
111 runner = mock.MagicMock()
112 runner.ignore_docker_for_reuse = False
113 runner.intermediate_output_ttl = 0
114 runner.secret_store = cwltool.secrets.SecretStore()
115 runner.api._rootDesc = {"revision": "20210628"}
117 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
118 runner.api.collections().get().execute.return_value = {
119 "portable_data_hash": "99999999999999999999999999999993+99"}
125 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
127 "class": "org.w3id.cwl.cwl.CommandLineTool"
130 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
132 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
133 arvtool.formatgraph = None
135 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
136 j.run(runtimeContext)
137 runner.api.container_requests().create.assert_called_with(
138 body=JsonDiffMatcher({
140 'HOME': '/var/spool/cwl',
143 'name': 'test_run_'+str(enable_reuse),
144 'runtime_constraints': {
148 'use_existing': enable_reuse,
151 '/tmp': {'kind': 'tmp',
152 "capacity": 1073741824
154 '/var/spool/cwl': {'kind': 'tmp',
155 "capacity": 1073741824 }
157 'state': 'Committed',
158 'output_name': 'Output for step test_run_'+str(enable_reuse),
159 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
160 'output_path': '/var/spool/cwl',
162 'container_image': '99999999999999999999999999999993+99',
163 'command': ['ls', '/var/spool/cwl'],
164 'cwd': '/var/spool/cwl',
165 'scheduling_parameters': {},
168 'output_storage_classes': ["default"]
171 # The test passes some fields in builder.resources
172 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
173 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
174 def test_resource_requirements(self, keepdocker):
175 runner = mock.MagicMock()
176 runner.ignore_docker_for_reuse = False
177 runner.intermediate_output_ttl = 3600
178 runner.secret_store = cwltool.secrets.SecretStore()
179 runner.api._rootDesc = {"revision": "20210628"}
181 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
182 runner.api.collections().get().execute.return_value = {
183 "portable_data_hash": "99999999999999999999999999999993+99"}
189 "class": "ResourceRequirement",
195 "class": "http://arvados.org/cwl#RuntimeConstraints",
198 "class": "http://arvados.org/cwl#APIRequirement",
200 "class": "http://arvados.org/cwl#PartitionRequirement",
203 "class": "http://arvados.org/cwl#IntermediateOutput",
206 "class": "http://arvados.org/cwl#ReuseRequirement",
211 "class": "org.w3id.cwl.cwl.CommandLineTool"
214 loadingContext, runtimeContext = self.helper(runner)
215 runtimeContext.name = "test_resource_requirements"
217 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
218 arvtool.formatgraph = None
219 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
220 j.run(runtimeContext)
222 call_args, call_kwargs = runner.api.container_requests().create.call_args
224 call_body_expected = {
226 'HOME': '/var/spool/cwl',
229 'name': 'test_resource_requirements',
230 'runtime_constraints': {
233 'keep_cache_ram': 536870912,
236 'use_existing': False,
239 '/tmp': {'kind': 'tmp',
240 "capacity": 4194304000 },
241 '/var/spool/cwl': {'kind': 'tmp',
242 "capacity": 5242880000 }
244 'state': 'Committed',
245 'output_name': 'Output for step test_resource_requirements',
246 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
247 'output_path': '/var/spool/cwl',
249 'container_image': '99999999999999999999999999999993+99',
251 'cwd': '/var/spool/cwl',
252 'scheduling_parameters': {
253 'partitions': ['blurb']
257 'output_storage_classes': ["default"]
260 call_body = call_kwargs.get('body', None)
261 self.assertNotEqual(None, call_body)
262 for key in call_body:
263 self.assertEqual(call_body_expected.get(key), call_body.get(key))
266 # The test passes some fields in builder.resources
267 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
268 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
269 @mock.patch("arvados.collection.Collection")
270 def test_initial_work_dir(self, collection_mock, keepdocker):
271 runner = mock.MagicMock()
272 runner.ignore_docker_for_reuse = False
273 runner.intermediate_output_ttl = 0
274 runner.secret_store = cwltool.secrets.SecretStore()
275 runner.api._rootDesc = {"revision": "20210628"}
277 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
278 runner.api.collections().get().execute.return_value = {
279 "portable_data_hash": "99999999999999999999999999999993+99"}
281 sourcemock = mock.MagicMock()
282 def get_collection_mock(p):
284 return (sourcemock, p.split("/", 1)[1])
286 return (sourcemock, "")
287 runner.fs_access.get_collection.side_effect = get_collection_mock
289 vwdmock = mock.MagicMock()
290 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
296 "class": "InitialWorkDirRequirement",
300 "location": "keep:99999999999999999999999999999995+99/bar"
303 "class": "Directory",
305 "location": "keep:99999999999999999999999999999995+99"
309 "basename": "filename",
310 "location": "keep:99999999999999999999999999999995+99/baz/filename"
313 "class": "Directory",
314 "basename": "subdir",
315 "location": "keep:99999999999999999999999999999995+99/subdir"
320 "class": "org.w3id.cwl.cwl.CommandLineTool"
323 loadingContext, runtimeContext = self.helper(runner)
324 runtimeContext.name = "test_initial_work_dir"
326 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
327 arvtool.formatgraph = None
328 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
329 j.run(runtimeContext)
331 call_args, call_kwargs = runner.api.container_requests().create.call_args
333 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
334 vwdmock.copy.assert_has_calls([mock.call('.', 'foo2', source_collection=sourcemock)])
335 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
336 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
338 call_body_expected = {
340 'HOME': '/var/spool/cwl',
343 'name': 'test_initial_work_dir',
344 'runtime_constraints': {
348 'use_existing': True,
351 '/tmp': {'kind': 'tmp',
352 "capacity": 1073741824 },
353 '/var/spool/cwl': {'kind': 'tmp',
354 "capacity": 1073741824 },
355 '/var/spool/cwl/foo': {
356 'kind': 'collection',
358 'portable_data_hash': '99999999999999999999999999999996+99'
360 '/var/spool/cwl/foo2': {
361 'kind': 'collection',
363 'portable_data_hash': '99999999999999999999999999999996+99'
365 '/var/spool/cwl/filename': {
366 'kind': 'collection',
368 'portable_data_hash': '99999999999999999999999999999996+99'
370 '/var/spool/cwl/subdir': {
371 'kind': 'collection',
373 'portable_data_hash': '99999999999999999999999999999996+99'
376 'state': 'Committed',
377 'output_name': 'Output for step test_initial_work_dir',
378 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
379 'output_path': '/var/spool/cwl',
381 'container_image': '99999999999999999999999999999993+99',
383 'cwd': '/var/spool/cwl',
384 'scheduling_parameters': {
388 'output_storage_classes': ["default"]
391 call_body = call_kwargs.get('body', None)
392 self.assertNotEqual(None, call_body)
393 for key in call_body:
394 self.assertEqual(call_body_expected.get(key), call_body.get(key))
397 # Test redirecting stdin/stdout/stderr
398 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
399 def test_redirects(self, keepdocker):
400 runner = mock.MagicMock()
401 runner.ignore_docker_for_reuse = False
402 runner.intermediate_output_ttl = 0
403 runner.secret_store = cwltool.secrets.SecretStore()
404 runner.api._rootDesc = {"revision": "20210628"}
406 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
407 runner.api.collections().get().execute.return_value = {
408 "portable_data_hash": "99999999999999999999999999999993+99"}
410 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
416 "stdout": "stdout.txt",
417 "stderr": "stderr.txt",
418 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
419 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
421 "class": "org.w3id.cwl.cwl.CommandLineTool"
424 loadingContext, runtimeContext = self.helper(runner)
425 runtimeContext.name = "test_run_redirect"
427 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
428 arvtool.formatgraph = None
429 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
430 j.run(runtimeContext)
431 runner.api.container_requests().create.assert_called_with(
432 body=JsonDiffMatcher({
434 'HOME': '/var/spool/cwl',
437 'name': 'test_run_redirect',
438 'runtime_constraints': {
442 'use_existing': True,
445 '/tmp': {'kind': 'tmp',
446 "capacity": 1073741824 },
447 '/var/spool/cwl': {'kind': 'tmp',
448 "capacity": 1073741824 },
451 "path": "/var/spool/cwl/stderr.txt"
454 "kind": "collection",
456 "portable_data_hash": "99999999999999999999999999999996+99"
460 "path": "/var/spool/cwl/stdout.txt"
463 'state': 'Committed',
464 "output_name": "Output for step test_run_redirect",
465 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
466 'output_path': '/var/spool/cwl',
468 'container_image': '99999999999999999999999999999993+99',
469 'command': ['ls', '/var/spool/cwl'],
470 'cwd': '/var/spool/cwl',
471 'scheduling_parameters': {},
474 'output_storage_classes': ["default"]
477 @mock.patch("arvados.collection.Collection")
478 def test_done(self, col):
479 api = mock.MagicMock()
481 runner = mock.MagicMock()
483 runner.num_retries = 0
484 runner.ignore_docker_for_reuse = False
485 runner.intermediate_output_ttl = 0
486 runner.secret_store = cwltool.secrets.SecretStore()
488 runner.api.containers().get().execute.return_value = {"state":"Complete",
492 col().open.return_value = []
494 loadingContext, runtimeContext = self.helper(runner)
496 arvjob = arvados_cwl.ArvadosContainer(runner,
504 arvjob.output_callback = mock.MagicMock()
505 arvjob.collect_outputs = mock.MagicMock()
506 arvjob.successCodes = [0]
507 arvjob.outdir = "/var/spool/cwl"
508 arvjob.output_ttl = 3600
510 arvjob.collect_outputs.return_value = {"out": "stuff"}
514 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
515 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
516 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
517 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
518 "modified_at": "2017-05-26T12:01:22Z"
521 self.assertFalse(api.collections().create.called)
522 self.assertFalse(runner.runtime_status_error.called)
524 arvjob.collect_outputs.assert_called_with("keep:abc+123", 0)
525 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
526 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
528 # Test to make sure we dont call runtime_status_update if we already did
529 # some where higher up in the call stack
530 @mock.patch("arvados_cwl.util.get_current_container")
531 def test_recursive_runtime_status_update(self, gcc_mock):
532 self.setup_and_test_container_executor_and_logging(gcc_mock)
533 root_logger = logging.getLogger('')
535 # get_current_container is invoked when we call runtime_status_update
536 # so try and log again!
537 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
539 root_logger.error("First Error")
541 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
544 # Test to make sure that an exception raised from
545 # get_current_container doesn't cause the logger to raise an
547 @mock.patch("arvados_cwl.util.get_current_container")
548 def test_runtime_status_get_current_container_exception(self, gcc_mock):
549 self.setup_and_test_container_executor_and_logging(gcc_mock)
550 root_logger = logging.getLogger('')
552 # get_current_container is invoked when we call
553 # runtime_status_update, it is going to also raise an
555 gcc_mock.side_effect = Exception("Second Error")
557 root_logger.error("First Error")
559 self.fail("Exception in logger should not propagate")
560 self.assertTrue(gcc_mock.called)
562 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
563 @mock.patch("arvados_cwl.util.get_current_container")
564 @mock.patch("arvados.collection.CollectionReader")
565 @mock.patch("arvados.collection.Collection")
566 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
567 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
569 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
570 self.assertTrue(gcc_mock.called)
572 runner.num_retries = 0
573 runner.ignore_docker_for_reuse = False
574 runner.intermediate_output_ttl = 0
575 runner.secret_store = cwltool.secrets.SecretStore()
576 runner.label = mock.MagicMock()
577 runner.label.return_value = '[container testjob]'
579 runner.api.containers().get().execute.return_value = {
586 col().open.return_value = []
588 loadingContext, runtimeContext = self.helper(runner)
590 arvjob = arvados_cwl.ArvadosContainer(runner,
598 arvjob.output_callback = mock.MagicMock()
599 arvjob.collect_outputs = mock.MagicMock()
600 arvjob.successCodes = [0]
601 arvjob.outdir = "/var/spool/cwl"
602 arvjob.output_ttl = 3600
603 arvjob.collect_outputs.return_value = {"out": "stuff"}
607 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
608 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
609 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
610 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
611 "modified_at": "2017-05-26T12:01:22Z"
614 rts_mock.assert_called_with(
616 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
617 ' ** log is empty **'
619 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
621 # The test passes no builder.resources
622 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
623 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
624 def test_mounts(self, keepdocker):
625 runner = mock.MagicMock()
626 runner.ignore_docker_for_reuse = False
627 runner.intermediate_output_ttl = 0
628 runner.secret_store = cwltool.secrets.SecretStore()
629 runner.api._rootDesc = {"revision": "20210628"}
631 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
632 runner.api.collections().get().execute.return_value = {
633 "portable_data_hash": "99999999999999999999999999999994+99",
634 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
636 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
645 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
647 "class": "org.w3id.cwl.cwl.CommandLineTool"
650 loadingContext, runtimeContext = self.helper(runner)
651 runtimeContext.name = "test_run_mounts"
653 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
654 arvtool.formatgraph = None
657 "class": "Directory",
658 "location": "keep:99999999999999999999999999999994+44",
659 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
663 "location": "keep:99999999999999999999999999999994+44/file1",
667 "location": "keep:99999999999999999999999999999994+44/file2",
672 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
673 j.run(runtimeContext)
674 runner.api.container_requests().create.assert_called_with(
675 body=JsonDiffMatcher({
677 'HOME': '/var/spool/cwl',
680 'name': 'test_run_mounts',
681 'runtime_constraints': {
685 'use_existing': True,
688 "/keep/99999999999999999999999999999994+44": {
689 "kind": "collection",
690 "portable_data_hash": "99999999999999999999999999999994+44",
691 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
693 '/tmp': {'kind': 'tmp',
694 "capacity": 1073741824 },
695 '/var/spool/cwl': {'kind': 'tmp',
696 "capacity": 1073741824 }
698 'state': 'Committed',
699 'output_name': 'Output for step test_run_mounts',
700 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
701 'output_path': '/var/spool/cwl',
703 'container_image': '99999999999999999999999999999994+99',
704 'command': ['ls', '/var/spool/cwl'],
705 'cwd': '/var/spool/cwl',
706 'scheduling_parameters': {},
709 'output_storage_classes': ["default"]
712 # The test passes no builder.resources
713 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
714 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
715 def test_secrets(self, keepdocker):
716 runner = mock.MagicMock()
717 runner.ignore_docker_for_reuse = False
718 runner.intermediate_output_ttl = 0
719 runner.secret_store = cwltool.secrets.SecretStore()
720 runner.api._rootDesc = {"revision": "20210628"}
722 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
723 runner.api.collections().get().execute.return_value = {
724 "portable_data_hash": "99999999999999999999999999999993+99"}
726 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
728 tool = cmap({"arguments": ["md5sum", "example.conf"],
729 "class": "org.w3id.cwl.cwl.CommandLineTool",
732 "class": "http://commonwl.org/cwltool#Secrets",
738 "id": "#secret_job.cwl",
741 "id": "#secret_job.cwl/pw",
749 "class": "InitialWorkDirRequirement",
752 "entry": "username: user\npassword: $(inputs.pw)\n",
753 "entryname": "example.conf"
759 loadingContext, runtimeContext = self.helper(runner)
760 runtimeContext.name = "test_secrets"
762 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
763 arvtool.formatgraph = None
765 job_order = {"pw": "blorp"}
766 runner.secret_store.store(["pw"], job_order)
768 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
769 j.run(runtimeContext)
770 runner.api.container_requests().create.assert_called_with(
771 body=JsonDiffMatcher({
773 'HOME': '/var/spool/cwl',
776 'name': 'test_secrets',
777 'runtime_constraints': {
781 'use_existing': True,
784 '/tmp': {'kind': 'tmp',
785 "capacity": 1073741824
787 '/var/spool/cwl': {'kind': 'tmp',
788 "capacity": 1073741824 }
790 'state': 'Committed',
791 'output_name': 'Output for step test_secrets',
792 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
793 'output_path': '/var/spool/cwl',
795 'container_image': '99999999999999999999999999999993+99',
796 'command': ['md5sum', 'example.conf'],
797 'cwd': '/var/spool/cwl',
798 'scheduling_parameters': {},
801 "/var/spool/cwl/example.conf": {
802 "content": "username: user\npassword: blorp\n",
806 'output_storage_classes': ["default"]
809 # The test passes no builder.resources
810 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
811 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
812 def test_timelimit(self, keepdocker):
813 runner = mock.MagicMock()
814 runner.ignore_docker_for_reuse = False
815 runner.intermediate_output_ttl = 0
816 runner.secret_store = cwltool.secrets.SecretStore()
817 runner.api._rootDesc = {"revision": "20210628"}
819 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
820 runner.api.collections().get().execute.return_value = {
821 "portable_data_hash": "99999999999999999999999999999993+99"}
827 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
829 "class": "org.w3id.cwl.cwl.CommandLineTool",
832 "class": "ToolTimeLimit",
838 loadingContext, runtimeContext = self.helper(runner)
839 runtimeContext.name = "test_timelimit"
841 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
842 arvtool.formatgraph = None
844 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
845 j.run(runtimeContext)
847 _, kwargs = runner.api.container_requests().create.call_args
848 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
851 # The test passes no builder.resources
852 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
853 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
854 def test_setting_storage_class(self, keepdocker):
855 arv_docker_clear_cache()
857 runner = mock.MagicMock()
858 runner.ignore_docker_for_reuse = False
859 runner.intermediate_output_ttl = 0
860 runner.secret_store = cwltool.secrets.SecretStore()
861 runner.api._rootDesc = {"revision": "20210628"}
863 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
864 runner.api.collections().get().execute.return_value = {
865 "portable_data_hash": "99999999999999999999999999999993+99"}
871 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
873 "class": "org.w3id.cwl.cwl.CommandLineTool",
876 "class": "http://arvados.org/cwl#OutputStorageClass",
877 "finalStorageClass": ["baz_sc", "qux_sc"],
878 "intermediateStorageClass": ["foo_sc", "bar_sc"]
883 loadingContext, runtimeContext = self.helper(runner, True)
885 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
886 arvtool.formatgraph = None
888 for j in arvtool.job({}, 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_run_True',
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 for step test_run_True',
912 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
913 'output_path': '/var/spool/cwl',
915 'container_image': '99999999999999999999999999999993+99',
916 'command': ['ls', '/var/spool/cwl'],
917 'cwd': '/var/spool/cwl',
918 'scheduling_parameters': {},
921 'output_storage_classes': ["foo_sc", "bar_sc"]
925 # The test passes no builder.resources
926 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
927 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
928 def test_setting_process_properties(self, keepdocker):
929 arv_docker_clear_cache()
931 runner = mock.MagicMock()
932 runner.ignore_docker_for_reuse = False
933 runner.intermediate_output_ttl = 0
934 runner.secret_store = cwltool.secrets.SecretStore()
935 runner.api._rootDesc = {"revision": "20210628"}
937 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
938 runner.api.collections().get().execute.return_value = {
939 "portable_data_hash": "99999999999999999999999999999993+99"}
943 {"id": "x", "type": "string"}],
946 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
948 "class": "org.w3id.cwl.cwl.CommandLineTool",
951 "class": "http://arvados.org/cwl#ProcessProperties",
952 "processProperties": [
953 {"propertyName": "foo",
954 "propertyValue": "bar"},
955 {"propertyName": "baz",
956 "propertyValue": "$(inputs.x)"},
957 {"propertyName": "quux",
968 loadingContext, runtimeContext = self.helper(runner, True)
970 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
971 arvtool.formatgraph = None
973 for j in arvtool.job({"x": "blorp"}, mock.MagicMock(), runtimeContext):
974 j.run(runtimeContext)
975 runner.api.container_requests().create.assert_called_with(
976 body=JsonDiffMatcher({
978 'HOME': '/var/spool/cwl',
981 'name': 'test_run_True',
982 'runtime_constraints': {
986 'use_existing': True,
989 '/tmp': {'kind': 'tmp',
990 "capacity": 1073741824
992 '/var/spool/cwl': {'kind': 'tmp',
993 "capacity": 1073741824 }
995 'state': 'Committed',
996 'output_name': 'Output for step test_run_True',
997 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
998 'output_path': '/var/spool/cwl',
1000 'container_image': '99999999999999999999999999999993+99',
1001 'command': ['ls', '/var/spool/cwl'],
1002 'cwd': '/var/spool/cwl',
1003 'scheduling_parameters': {},
1012 'secret_mounts': {},
1013 'output_storage_classes': ["default"]
1017 class TestWorkflow(unittest.TestCase):
1019 cwltool.process._names = set()
1020 arv_docker_clear_cache()
1022 def helper(self, runner, enable_reuse=True):
1023 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
1025 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
1026 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
1028 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
1029 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
1030 document_loader.fetch_text = document_loader.fetcher.fetch_text
1031 document_loader.check_exists = document_loader.fetcher.check_exists
1033 loadingContext = arvados_cwl.context.ArvLoadingContext(
1034 {"avsc_names": avsc_names,
1036 "make_fs_access": make_fs_access,
1037 "loader": document_loader,
1038 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
1039 "construct_tool_object": runner.arv_make_tool})
1040 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
1041 {"work_api": "containers",
1043 "name": "test_run_wf_"+str(enable_reuse),
1044 "make_fs_access": make_fs_access,
1046 "enable_reuse": enable_reuse,
1049 return loadingContext, runtimeContext
1051 # The test passes no builder.resources
1052 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1053 @mock.patch("arvados.collection.CollectionReader")
1054 @mock.patch("arvados.collection.Collection")
1055 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1056 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
1057 arvados_cwl.add_arv_hints()
1059 api = mock.MagicMock()
1060 api._rootDesc = get_rootDesc()
1062 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1063 self.assertEqual(runner.work_api, 'containers')
1065 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1066 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
1067 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1068 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1070 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1071 runner.ignore_docker_for_reuse = False
1072 runner.num_retries = 0
1073 runner.secret_store = cwltool.secrets.SecretStore()
1075 loadingContext, runtimeContext = self.helper(runner)
1076 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1078 mockcollectionreader().exists.return_value = True
1080 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
1081 metadata["cwlVersion"] = tool["cwlVersion"]
1083 mockc = mock.MagicMock()
1084 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
1085 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
1087 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1088 arvtool.formatgraph = None
1089 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1091 next(it).run(runtimeContext)
1092 next(it).run(runtimeContext)
1094 with open("tests/wf/scatter2_subwf.cwl") as f:
1095 subwf = StripYAMLComments(f.read()).rstrip()
1097 runner.api.container_requests().create.assert_called_with(
1098 body=JsonDiffMatcher({
1103 "--preserve-entire-environment",
1107 "container_image": "99999999999999999999999999999993+99",
1108 "cwd": "/var/spool/cwl",
1110 "HOME": "/var/spool/cwl",
1114 "/keep/99999999999999999999999999999999+118": {
1115 "kind": "collection",
1116 "portable_data_hash": "99999999999999999999999999999999+118"
1119 "capacity": 1073741824,
1123 "capacity": 1073741824,
1126 "/var/spool/cwl/cwl.input.yml": {
1127 "kind": "collection",
1128 "path": "cwl.input.yml",
1129 "portable_data_hash": "99999999999999999999999999999996+99"
1131 "/var/spool/cwl/workflow.cwl": {
1132 "kind": "collection",
1133 "path": "workflow.cwl",
1134 "portable_data_hash": "99999999999999999999999999999996+99"
1138 "path": "/var/spool/cwl/cwl.output.json"
1141 "name": "scatterstep",
1142 "output_name": "Output for step scatterstep",
1143 "output_path": "/var/spool/cwl",
1147 "runtime_constraints": {
1151 "scheduling_parameters": {},
1152 "secret_mounts": {},
1153 "state": "Committed",
1154 "use_existing": True,
1155 'output_storage_classes': ["default"]
1157 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
1158 mockc.open().__enter__().write.assert_has_calls([mock.call(
1161 "basename": "token.txt",
1163 "location": "/keep/99999999999999999999999999999999+118/token.txt",
1169 # The test passes no builder.resources
1170 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1171 @mock.patch("arvados.collection.CollectionReader")
1172 @mock.patch("arvados.collection.Collection")
1173 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1174 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
1175 arvados_cwl.add_arv_hints()
1177 api = mock.MagicMock()
1178 api._rootDesc = get_rootDesc()
1180 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1181 self.assertEqual(runner.work_api, 'containers')
1183 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1184 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1185 "portable_data_hash": "99999999999999999999999999999993+99"}
1186 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1187 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1189 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1190 runner.ignore_docker_for_reuse = False
1191 runner.num_retries = 0
1192 runner.secret_store = cwltool.secrets.SecretStore()
1194 loadingContext, runtimeContext = self.helper(runner)
1195 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1196 loadingContext.do_update = True
1197 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1199 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1201 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1202 arvtool.formatgraph = None
1203 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1205 next(it).run(runtimeContext)
1206 next(it).run(runtimeContext)
1208 with open("tests/wf/echo-subwf.cwl") as f:
1209 subwf = StripYAMLComments(f.read())
1211 runner.api.container_requests().create.assert_called_with(
1212 body=JsonDiffMatcher({
1214 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1215 'scheduling_parameters': {},
1216 'name': u'echo-subwf',
1217 'secret_mounts': {},
1218 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1222 '/var/spool/cwl/cwl.input.yml': {
1223 'portable_data_hash': '99999999999999999999999999999996+99',
1224 'kind': 'collection',
1225 'path': 'cwl.input.yml'
1227 '/var/spool/cwl/workflow.cwl': {
1228 'portable_data_hash': '99999999999999999999999999999996+99',
1229 'kind': 'collection',
1230 'path': 'workflow.cwl'
1233 'path': '/var/spool/cwl/cwl.output.json',
1238 'capacity': 1073741824
1239 }, '/var/spool/cwl': {
1241 'capacity': 3221225472
1244 'state': 'Committed',
1245 'output_path': '/var/spool/cwl',
1246 'container_image': '99999999999999999999999999999993+99',
1251 u'--preserve-entire-environment',
1255 'use_existing': True,
1256 'output_name': u'Output for step echo-subwf',
1257 'cwd': '/var/spool/cwl',
1258 'output_storage_classes': ["default"]
1261 def test_default_work_api(self):
1262 arvados_cwl.add_arv_hints()
1264 api = mock.MagicMock()
1265 api._rootDesc = copy.deepcopy(get_rootDesc())
1266 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1267 self.assertEqual(runner.work_api, 'containers')