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
8 #from arvados_cwl.arvdocker import arv_docker_clear_cache
16 import cwltool.process
17 import cwltool.secrets
18 import cwltool.load_tool
19 from cwltool.update import INTERNAL_VERSION
20 from schema_salad.ref_resolver import Loader
21 from schema_salad.sourceline import cmap
23 from parameterized import parameterized
25 from unittest import mock
27 from .matcher import JsonDiffMatcher, StripYAMLComments
28 from .mock_discovery import get_rootDesc
30 if not os.getenv('ARVADOS_DEBUG'):
31 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
32 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
34 class CollectionMock(object):
35 def __init__(self, vwdmock, *args, **kwargs):
36 self.vwdmock = vwdmock
39 def open(self, *args, **kwargs):
41 return self.vwdmock.open(*args, **kwargs)
43 def copy(self, *args, **kwargs):
45 self.vwdmock.copy(*args, **kwargs)
47 def save_new(self, *args, **kwargs):
53 def portable_data_hash(self):
55 return arvados.config.EMPTY_BLOCK_LOCATOR
57 return "99999999999999999999999999999996+99"
60 class TestContainer(unittest.TestCase):
63 cwltool.process._names = set()
64 #arv_docker_clear_cache()
67 root_logger = logging.getLogger('')
69 # Remove existing RuntimeStatusLoggingHandlers if they exist
70 handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)]
71 root_logger.handlers = handlers
73 def helper(self, runner, enable_reuse=True):
74 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
76 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
77 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
78 fs_access = mock.MagicMock()
79 fs_access.exists.return_value = True
81 loadingContext = arvados_cwl.context.ArvLoadingContext(
82 {"avsc_names": avsc_names,
84 "make_fs_access": make_fs_access,
85 "construct_tool_object": runner.arv_make_tool,
86 "fetcher_constructor": functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=fs_access),
88 "metadata": cmap({"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"}),
89 "default_docker_image": "arvados/jobs:"+arvados_cwl.__version__,
91 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
92 {"work_api": "containers",
94 "name": "test_run_"+str(enable_reuse),
95 "make_fs_access": make_fs_access,
98 "enable_reuse": enable_reuse,
100 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
101 "workflow_eval_lock": threading.Condition(threading.RLock())
104 if isinstance(runner, mock.MagicMock):
105 def make_tool(toolpath_object, loadingContext):
106 return arvados_cwl.ArvadosCommandTool(runner, toolpath_object, loadingContext)
107 runner.arv_make_tool.side_effect = make_tool
109 return loadingContext, runtimeContext
111 # Helper function to set up the ArvCwlExecutor to use the containers api
112 # and test that the RuntimeStatusLoggingHandler is set up correctly
113 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
114 api = mock.MagicMock()
115 api._rootDesc = copy.deepcopy(get_rootDesc())
117 # Make sure ArvCwlExecutor thinks it's running inside a container so it
118 # adds the logging handler that will call runtime_status_update() mock
119 self.assertFalse(gcc_mock.called)
120 runner = arvados_cwl.ArvCwlExecutor(api)
121 self.assertEqual(runner.work_api, 'containers')
122 root_logger = logging.getLogger('')
123 handlerClasses = [h.__class__ for h in root_logger.handlers]
124 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
127 # The test passes no builder.resources
128 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
129 @parameterized.expand([
133 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
134 def test_run(self, enable_reuse, keepdocker):
135 runner = mock.MagicMock()
136 runner.ignore_docker_for_reuse = False
137 runner.intermediate_output_ttl = 0
138 runner.secret_store = cwltool.secrets.SecretStore()
139 runner.api._rootDesc = {"revision": "20210628"}
140 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
142 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
143 runner.api.collections().get().execute.return_value = {
144 "portable_data_hash": "99999999999999999999999999999993+99"}
150 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
152 "class": "CommandLineTool",
156 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
158 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
159 arvtool.formatgraph = None
161 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
162 j.run(runtimeContext)
163 runner.api.container_requests().create.assert_called_with(
164 body=JsonDiffMatcher({
166 'HOME': '/var/spool/cwl',
169 'name': 'test_run_'+str(enable_reuse),
170 'runtime_constraints': {
174 'use_existing': enable_reuse,
177 '/tmp': {'kind': 'tmp',
178 "capacity": 1073741824
180 '/var/spool/cwl': {'kind': 'tmp',
181 "capacity": 1073741824 }
183 'state': 'Committed',
184 'output_name': 'Output from step test_run_'+str(enable_reuse),
185 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
186 'output_path': '/var/spool/cwl',
188 'container_image': '99999999999999999999999999999993+99',
189 'command': ['ls', '/var/spool/cwl'],
190 'cwd': '/var/spool/cwl',
191 'scheduling_parameters': {},
192 'properties': {'cwl_input': {}},
194 'output_storage_classes': ["default"]
197 # The test passes some fields in builder.resources
198 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
199 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
200 def test_resource_requirements(self, keepdocker):
201 arvados_cwl.add_arv_hints()
202 runner = mock.MagicMock()
203 runner.ignore_docker_for_reuse = False
204 runner.intermediate_output_ttl = 3600
205 runner.secret_store = cwltool.secrets.SecretStore()
206 runner.api._rootDesc = {"revision": "20210628"}
207 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
209 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
210 runner.api.collections().get().execute.return_value = {
211 "portable_data_hash": "99999999999999999999999999999993+99"}
217 "class": "ResourceRequirement",
223 "class": "http://arvados.org/cwl#RuntimeConstraints",
226 "class": "http://arvados.org/cwl#APIRequirement",
228 "class": "http://arvados.org/cwl#PartitionRequirement",
231 "class": "http://arvados.org/cwl#IntermediateOutput",
234 "class": "WorkReuse",
239 "class": "CommandLineTool",
243 loadingContext, runtimeContext = self.helper(runner)
244 runtimeContext.name = "test_resource_requirements"
246 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
247 arvtool.formatgraph = None
248 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
249 j.run(runtimeContext)
251 call_args, call_kwargs = runner.api.container_requests().create.call_args
253 call_body_expected = {
255 'HOME': '/var/spool/cwl',
258 'name': 'test_resource_requirements',
259 'runtime_constraints': {
262 'keep_cache_ram': 536870912,
265 'use_existing': False,
268 '/tmp': {'kind': 'tmp',
269 "capacity": 4194304000 },
270 '/var/spool/cwl': {'kind': 'tmp',
271 "capacity": 5242880000 }
273 'state': 'Committed',
274 'output_name': 'Output from step test_resource_requirements',
275 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
276 'output_path': '/var/spool/cwl',
278 'container_image': '99999999999999999999999999999993+99',
280 'cwd': '/var/spool/cwl',
281 'scheduling_parameters': {
282 'partitions': ['blurb']
284 'properties': {'cwl_input': {}},
286 'output_storage_classes': ["default"]
289 call_body = call_kwargs.get('body', None)
290 self.assertNotEqual(None, call_body)
291 for key in call_body:
292 self.assertEqual(call_body_expected.get(key), call_body.get(key))
295 # The test passes some fields in builder.resources
296 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
297 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
298 @mock.patch("arvados.collection.Collection")
299 def test_initial_work_dir(self, collection_mock, keepdocker):
300 runner = mock.MagicMock()
301 runner.ignore_docker_for_reuse = False
302 runner.intermediate_output_ttl = 0
303 runner.secret_store = cwltool.secrets.SecretStore()
304 runner.api._rootDesc = {"revision": "20210628"}
305 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
307 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
308 runner.api.collections().get().execute.return_value = {
309 "portable_data_hash": "99999999999999999999999999999993+99"}
311 sourcemock = mock.MagicMock()
312 def get_collection_mock(p):
314 return (sourcemock, p.split("/", 1)[1])
316 return (sourcemock, "")
317 runner.fs_access.get_collection.side_effect = get_collection_mock
319 vwdmock = mock.MagicMock()
320 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
326 "class": "InitialWorkDirRequirement",
330 "location": "keep:99999999999999999999999999999995+99/bar"
333 "class": "Directory",
335 "location": "keep:99999999999999999999999999999995+99"
339 "basename": "filename",
340 "location": "keep:99999999999999999999999999999995+99/baz/filename"
343 "class": "Directory",
344 "basename": "subdir",
345 "location": "keep:99999999999999999999999999999995+99/subdir"
349 "class": "CommandLineTool",
350 "cwlVersion": "v1.2",
354 loadingContext, runtimeContext = self.helper(runner)
355 runtimeContext.name = "test_initial_work_dir"
357 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
359 arvtool.formatgraph = None
360 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
361 j.run(runtimeContext)
363 call_args, call_kwargs = runner.api.container_requests().create.call_args
365 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
366 vwdmock.copy.assert_has_calls([mock.call('.', 'foo2', source_collection=sourcemock)])
367 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
368 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
370 call_body_expected = {
372 'HOME': '/var/spool/cwl',
375 'name': 'test_initial_work_dir',
376 'runtime_constraints': {
380 'use_existing': True,
383 '/tmp': {'kind': 'tmp',
384 "capacity": 1073741824 },
385 '/var/spool/cwl': {'kind': 'tmp',
386 "capacity": 1073741824 },
387 '/var/spool/cwl/foo': {
388 'kind': 'collection',
390 'portable_data_hash': '99999999999999999999999999999996+99'
392 '/var/spool/cwl/foo2': {
393 'kind': 'collection',
395 'portable_data_hash': '99999999999999999999999999999996+99'
397 '/var/spool/cwl/filename': {
398 'kind': 'collection',
400 'portable_data_hash': '99999999999999999999999999999996+99'
402 '/var/spool/cwl/subdir': {
403 'kind': 'collection',
405 'portable_data_hash': '99999999999999999999999999999996+99'
408 'state': 'Committed',
409 'output_name': 'Output from step test_initial_work_dir',
410 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
411 'output_path': '/var/spool/cwl',
413 'container_image': '99999999999999999999999999999993+99',
415 'cwd': '/var/spool/cwl',
416 'scheduling_parameters': {
418 'properties': {'cwl_input': {}},
420 'output_storage_classes': ["default"]
423 call_body = call_kwargs.get('body', None)
424 self.assertNotEqual(None, call_body)
425 for key in call_body:
426 self.assertEqual(call_body_expected.get(key), call_body.get(key))
429 # Test redirecting stdin/stdout/stderr
430 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
431 def test_redirects(self, keepdocker):
432 runner = mock.MagicMock()
433 runner.ignore_docker_for_reuse = False
434 runner.intermediate_output_ttl = 0
435 runner.secret_store = cwltool.secrets.SecretStore()
436 runner.api._rootDesc = {"revision": "20210628"}
437 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
439 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
440 runner.api.collections().get().execute.return_value = {
441 "portable_data_hash": "99999999999999999999999999999993+99"}
443 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
449 "stdout": "stdout.txt",
450 "stderr": "stderr.txt",
451 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
452 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
454 "class": "CommandLineTool",
458 loadingContext, runtimeContext = self.helper(runner)
459 runtimeContext.name = "test_run_redirect"
461 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
462 arvtool.formatgraph = None
463 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
464 j.run(runtimeContext)
465 runner.api.container_requests().create.assert_called_with(
466 body=JsonDiffMatcher({
468 'HOME': '/var/spool/cwl',
471 'name': 'test_run_redirect',
472 'runtime_constraints': {
476 'use_existing': True,
479 '/tmp': {'kind': 'tmp',
480 "capacity": 1073741824 },
481 '/var/spool/cwl': {'kind': 'tmp',
482 "capacity": 1073741824 },
485 "path": "/var/spool/cwl/stderr.txt"
488 "kind": "collection",
490 "portable_data_hash": "99999999999999999999999999999996+99"
494 "path": "/var/spool/cwl/stdout.txt"
497 'state': 'Committed',
498 "output_name": "Output from step test_run_redirect",
499 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
500 'output_path': '/var/spool/cwl',
502 'container_image': '99999999999999999999999999999993+99',
503 'command': ['ls', '/var/spool/cwl'],
504 'cwd': '/var/spool/cwl',
505 'scheduling_parameters': {},
506 'properties': {'cwl_input': {}},
508 'output_storage_classes': ["default"]
511 @mock.patch("arvados.collection.Collection")
512 def test_done(self, col):
513 api = mock.MagicMock()
515 runner = mock.MagicMock()
517 runner.num_retries = 0
518 runner.ignore_docker_for_reuse = False
519 runner.intermediate_output_ttl = 0
520 runner.secret_store = cwltool.secrets.SecretStore()
522 runner.api.container_requests().get().execute.return_value = {"container_uuid":"zzzzz-xvhdp-zzzzzzzzzzzzzzz"}
524 runner.api.containers().get().execute.return_value = {"state":"Complete",
528 # Need to noop-out the close method otherwise it gets
529 # discarded when closed and we can't call getvalue() to check
531 class NoopCloseStringIO(io.StringIO):
535 usage_report = NoopCloseStringIO()
536 def colreader_action(name, mode):
537 nonlocal usage_report
538 if name == "node.json":
539 return io.StringIO("""{
540 "ProviderType": "c5.large",
543 "IncludedScratch": 8000000000000,
546 "Preemptible": false,
549 "HardwareCapability": "",
553 if name == 'crunchstat.txt':
554 return open("tests/container_request_9tee4-xvhdp-kk0ja1cl8b2kr1y-arv-mount.txt", "rt")
555 if name == 'arv-mount.txt':
556 return open("tests/container_request_9tee4-xvhdp-kk0ja1cl8b2kr1y-crunchstat.txt", "rt")
557 if name == 'usage_report.html':
561 col().open.side_effect = colreader_action
562 col().__iter__.return_value = ['node.json', 'crunchstat.txt', 'arv-mount.txt']
564 loadingContext, runtimeContext = self.helper(runner)
566 arvjob = arvados_cwl.ArvadosContainer(runner,
575 arvjob.output_callback = mock.MagicMock()
576 arvjob.collect_outputs = mock.MagicMock()
577 arvjob.successCodes = [0]
578 arvjob.outdir = "/var/spool/cwl"
579 arvjob.output_ttl = 3600
580 arvjob.uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzz1"
582 arvjob.collect_outputs.return_value = {"out": "stuff"}
586 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
587 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
588 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
589 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
590 "modified_at": "2017-05-26T12:01:22Z",
595 self.assertFalse(api.collections().create.called)
596 self.assertFalse(runner.runtime_status_error.called)
598 # Assert that something was written to the usage report
599 self.assertTrue(len(usage_report.getvalue()) > 0)
601 arvjob.collect_outputs.assert_called_with("keep:abc+123", 0)
602 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
603 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
605 runner.api.container_requests().update.assert_called_with(uuid="zzzzz-xvhdp-zzzzzzzzzzzzzz1",
606 body={'container_request': {'properties': {'cwl_output': {'out': 'stuff'}}}})
609 # Test to make sure we dont call runtime_status_update if we already did
610 # some where higher up in the call stack
611 @mock.patch("arvados_cwl.util.get_current_container")
612 def test_recursive_runtime_status_update(self, gcc_mock):
613 self.setup_and_test_container_executor_and_logging(gcc_mock)
614 root_logger = logging.getLogger('')
616 # get_current_container is invoked when we call runtime_status_update
617 # so try and log again!
618 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
620 root_logger.error("First Error")
622 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
625 # Test to make sure that an exception raised from
626 # get_current_container doesn't cause the logger to raise an
628 @mock.patch("arvados_cwl.util.get_current_container")
629 def test_runtime_status_get_current_container_exception(self, gcc_mock):
630 self.setup_and_test_container_executor_and_logging(gcc_mock)
631 root_logger = logging.getLogger('')
633 # get_current_container is invoked when we call
634 # runtime_status_update, it is going to also raise an
636 gcc_mock.side_effect = Exception("Second Error")
638 root_logger.error("First Error")
640 self.fail("Exception in logger should not propagate")
641 self.assertTrue(gcc_mock.called)
643 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
644 @mock.patch("arvados_cwl.util.get_current_container")
645 @mock.patch("arvados.collection.CollectionReader")
646 @mock.patch("arvados.collection.Collection")
647 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
648 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
650 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
651 self.assertTrue(gcc_mock.called)
653 runner.num_retries = 0
654 runner.ignore_docker_for_reuse = False
655 runner.intermediate_output_ttl = 0
656 runner.secret_store = cwltool.secrets.SecretStore()
657 runner.label = mock.MagicMock()
658 runner.label.return_value = '[container testjob]'
660 runner.api.containers().get().execute.return_value = {
667 col().open.return_value = []
669 loadingContext, runtimeContext = self.helper(runner)
671 arvjob = arvados_cwl.ArvadosContainer(runner,
680 arvjob.output_callback = mock.MagicMock()
681 arvjob.collect_outputs = mock.MagicMock()
682 arvjob.successCodes = [0]
683 arvjob.outdir = "/var/spool/cwl"
684 arvjob.output_ttl = 3600
685 arvjob.collect_outputs.return_value = {"out": "stuff"}
689 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
690 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
691 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
692 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
693 "modified_at": "2017-05-26T12:01:22Z",
697 rts_mock.assert_has_calls([
699 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
700 ' ** log is empty **'
703 'arvados.cwl-runner: [container testjob] unable to generate resource usage report'
705 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
707 # The test passes no builder.resources
708 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
709 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
710 def test_mounts(self, keepdocker):
711 runner = mock.MagicMock()
712 runner.ignore_docker_for_reuse = False
713 runner.intermediate_output_ttl = 0
714 runner.secret_store = cwltool.secrets.SecretStore()
715 runner.api._rootDesc = {"revision": "20210628"}
716 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
718 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
719 runner.api.collections().get().execute.return_value = {
720 "portable_data_hash": "99999999999999999999999999999994+99",
721 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
723 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
732 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
734 "class": "CommandLineTool",
738 loadingContext, runtimeContext = self.helper(runner)
739 runtimeContext.name = "test_run_mounts"
741 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
742 arvtool.formatgraph = None
745 "class": "Directory",
746 "location": "keep:99999999999999999999999999999994+44",
747 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
751 "location": "keep:99999999999999999999999999999994+44/file1",
755 "location": "keep:99999999999999999999999999999994+44/file2",
760 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
761 j.run(runtimeContext)
762 runner.api.container_requests().create.assert_called_with(
763 body=JsonDiffMatcher({
765 'HOME': '/var/spool/cwl',
768 'name': 'test_run_mounts',
769 'runtime_constraints': {
773 'use_existing': True,
776 "/keep/99999999999999999999999999999994+44": {
777 "kind": "collection",
778 "portable_data_hash": "99999999999999999999999999999994+44",
779 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
781 '/tmp': {'kind': 'tmp',
782 "capacity": 1073741824 },
783 '/var/spool/cwl': {'kind': 'tmp',
784 "capacity": 1073741824 }
786 'state': 'Committed',
787 'output_name': 'Output from step test_run_mounts',
788 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
789 'output_path': '/var/spool/cwl',
791 'container_image': '99999999999999999999999999999994+99',
792 'command': ['ls', '/var/spool/cwl'],
793 'cwd': '/var/spool/cwl',
794 'scheduling_parameters': {},
795 'properties': {'cwl_input': {
797 "basename": "99999999999999999999999999999994+44",
798 "class": "Directory",
800 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
805 "dirname": "/keep/99999999999999999999999999999994+44",
806 "location": "keep:99999999999999999999999999999994+44/file1",
809 "path": "/keep/99999999999999999999999999999994+44/file1",
815 "dirname": "/keep/99999999999999999999999999999994+44",
816 "location": "keep:99999999999999999999999999999994+44/file2",
819 "path": "/keep/99999999999999999999999999999994+44/file2",
823 "location": "keep:99999999999999999999999999999994+44",
824 "path": "/keep/99999999999999999999999999999994+44"
828 'output_storage_classes': ["default"]
831 # The test passes no builder.resources
832 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
833 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
834 def test_secrets(self, keepdocker):
835 arvados_cwl.add_arv_hints()
836 runner = mock.MagicMock()
837 runner.ignore_docker_for_reuse = False
838 runner.intermediate_output_ttl = 0
839 runner.secret_store = cwltool.secrets.SecretStore()
840 runner.api._rootDesc = {"revision": "20210628"}
841 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
843 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
844 runner.api.collections().get().execute.return_value = {
845 "portable_data_hash": "99999999999999999999999999999993+99"}
847 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
849 tool = cmap({"arguments": ["md5sum", "example.conf"],
850 "class": "CommandLineTool",
851 "cwlVersion": "v1.2",
854 "class": "http://commonwl.org/cwltool#Secrets",
863 "id": "#secret_job.cwl/pw",
871 "class": "InitialWorkDirRequirement",
874 "entry": "username: user\npassword: $(inputs.pw)\n",
875 "entryname": "example.conf"
881 loadingContext, runtimeContext = self.helper(runner)
882 runtimeContext.name = "test_secrets"
884 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
885 arvtool.formatgraph = None
887 job_order = {"pw": "blorp"}
888 runner.secret_store.store(["pw"], job_order)
890 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
891 j.run(runtimeContext)
892 runner.api.container_requests().create.assert_called_with(
893 body=JsonDiffMatcher({
895 'HOME': '/var/spool/cwl',
898 'name': 'test_secrets',
899 'runtime_constraints': {
903 'use_existing': True,
906 '/tmp': {'kind': 'tmp',
907 "capacity": 1073741824
909 '/var/spool/cwl': {'kind': 'tmp',
910 "capacity": 1073741824 }
912 'state': 'Committed',
913 'output_name': 'Output from step test_secrets',
914 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
915 'output_path': '/var/spool/cwl',
917 'container_image': '99999999999999999999999999999993+99',
918 'command': ['md5sum', 'example.conf'],
919 'cwd': '/var/spool/cwl',
920 'scheduling_parameters': {},
921 'properties': {'cwl_input': job_order},
923 "/var/spool/cwl/example.conf": {
924 "content": "username: user\npassword: blorp\n",
928 'output_storage_classes': ["default"]
931 # The test passes no builder.resources
932 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
933 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
934 def test_timelimit(self, keepdocker):
935 runner = mock.MagicMock()
936 runner.ignore_docker_for_reuse = False
937 runner.intermediate_output_ttl = 0
938 runner.secret_store = cwltool.secrets.SecretStore()
939 runner.api._rootDesc = {"revision": "20210628"}
940 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
942 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
943 runner.api.collections().get().execute.return_value = {
944 "portable_data_hash": "99999999999999999999999999999993+99"}
950 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
952 "cwlVersion": "v1.2",
953 "class": "CommandLineTool",
956 "class": "ToolTimeLimit",
962 loadingContext, runtimeContext = self.helper(runner)
963 runtimeContext.name = "test_timelimit"
965 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
966 arvtool.formatgraph = None
968 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
969 j.run(runtimeContext)
971 _, kwargs = runner.api.container_requests().create.call_args
972 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
975 # The test passes no builder.resources
976 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
977 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
978 def test_setting_storage_class(self, keepdocker):
979 #arv_docker_clear_cache()
981 runner = mock.MagicMock()
982 runner.ignore_docker_for_reuse = False
983 runner.intermediate_output_ttl = 0
984 runner.secret_store = cwltool.secrets.SecretStore()
985 runner.api._rootDesc = {"revision": "20210628"}
986 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
988 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
989 runner.api.collections().get().execute.return_value = {
990 "portable_data_hash": "99999999999999999999999999999993+99"}
996 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
998 "cwlVersion": "v1.2",
999 "class": "CommandLineTool",
1002 "class": "http://arvados.org/cwl#OutputStorageClass",
1003 "finalStorageClass": ["baz_sc", "qux_sc"],
1004 "intermediateStorageClass": ["foo_sc", "bar_sc"]
1009 loadingContext, runtimeContext = self.helper(runner, True)
1011 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1012 arvtool.formatgraph = None
1014 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1015 j.run(runtimeContext)
1016 runner.api.container_requests().create.assert_called_with(
1017 body=JsonDiffMatcher({
1019 'HOME': '/var/spool/cwl',
1022 'name': 'test_run_True',
1023 'runtime_constraints': {
1027 'use_existing': True,
1030 '/tmp': {'kind': 'tmp',
1031 "capacity": 1073741824
1033 '/var/spool/cwl': {'kind': 'tmp',
1034 "capacity": 1073741824 }
1036 'state': 'Committed',
1037 'output_name': 'Output from step test_run_True',
1038 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1039 'output_path': '/var/spool/cwl',
1041 'container_image': '99999999999999999999999999999993+99',
1042 'command': ['ls', '/var/spool/cwl'],
1043 'cwd': '/var/spool/cwl',
1044 'scheduling_parameters': {},
1045 'properties': {'cwl_input': {}},
1046 'secret_mounts': {},
1047 'output_storage_classes': ["foo_sc", "bar_sc"]
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.commands.keepdocker.list_images_in_arv")
1054 def test_setting_process_properties(self, keepdocker):
1055 #arv_docker_clear_cache()
1057 runner = mock.MagicMock()
1058 runner.ignore_docker_for_reuse = False
1059 runner.intermediate_output_ttl = 0
1060 runner.secret_store = cwltool.secrets.SecretStore()
1061 runner.api._rootDesc = {"revision": "20210628"}
1062 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1064 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1065 runner.api.collections().get().execute.return_value = {
1066 "portable_data_hash": "99999999999999999999999999999993+99"}
1070 {"id": "x", "type": "string"}],
1072 "baseCommand": "ls",
1073 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1075 "class": "CommandLineTool",
1076 "cwlVersion": "v1.2",
1079 "class": "http://arvados.org/cwl#ProcessProperties",
1080 "processProperties": [
1081 {"propertyName": "foo",
1082 "propertyValue": "bar"},
1083 {"propertyName": "baz",
1084 "propertyValue": "$(inputs.x)"},
1085 {"propertyName": "quux",
1096 loadingContext, runtimeContext = self.helper(runner, True)
1098 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1099 arvtool.formatgraph = None
1101 for j in arvtool.job({"x": "blorp"}, mock.MagicMock(), runtimeContext):
1102 j.run(runtimeContext)
1103 runner.api.container_requests().create.assert_called_with(
1104 body=JsonDiffMatcher({
1106 'HOME': '/var/spool/cwl',
1109 'name': 'test_run_True',
1110 'runtime_constraints': {
1114 'use_existing': True,
1117 '/tmp': {'kind': 'tmp',
1118 "capacity": 1073741824
1120 '/var/spool/cwl': {'kind': 'tmp',
1121 "capacity": 1073741824 }
1123 'state': 'Committed',
1124 'output_name': 'Output from step test_run_True',
1125 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1126 'output_path': '/var/spool/cwl',
1128 'container_image': '99999999999999999999999999999993+99',
1129 'command': ['ls', '/var/spool/cwl'],
1130 'cwd': '/var/spool/cwl',
1131 'scheduling_parameters': {},
1134 "cwl_input": {"x": "blorp"},
1141 'secret_mounts': {},
1142 'output_storage_classes': ["default"]
1146 # The test passes no builder.resources
1147 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1148 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1149 def test_cuda_requirement(self, keepdocker):
1150 arvados_cwl.add_arv_hints()
1151 #arv_docker_clear_cache()
1153 runner = mock.MagicMock()
1154 runner.ignore_docker_for_reuse = False
1155 runner.intermediate_output_ttl = 0
1156 runner.secret_store = cwltool.secrets.SecretStore()
1157 runner.api._rootDesc = {"revision": "20210628"}
1158 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1160 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1161 runner.api.collections().get().execute.return_value = {
1162 "portable_data_hash": "99999999999999999999999999999993+99"}
1165 "class": "http://commonwl.org/cwltool#CUDARequirement",
1166 "cudaVersionMin": "11.0",
1167 "cudaComputeCapability": "9.0",
1169 "class": "http://commonwl.org/cwltool#CUDARequirement",
1170 "cudaVersionMin": "11.0",
1171 "cudaComputeCapability": "9.0",
1172 "cudaDeviceCountMin": 2
1174 "class": "http://commonwl.org/cwltool#CUDARequirement",
1175 "cudaVersionMin": "11.0",
1176 "cudaComputeCapability": ["4.0", "5.0"],
1177 "cudaDeviceCountMin": 2
1182 'driver_version': "11.0",
1183 'hardware_capability': "9.0"
1186 'driver_version': "11.0",
1187 'hardware_capability': "9.0"
1190 'driver_version': "11.0",
1191 'hardware_capability': "4.0"
1194 for test_case in range(0, len(test_cwl_req)):
1199 "baseCommand": "nvidia-smi",
1202 "cwlVersion": "v1.2",
1203 "class": "CommandLineTool",
1204 "requirements": [test_cwl_req[test_case]]
1207 loadingContext, runtimeContext = self.helper(runner, True)
1209 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1210 arvtool.formatgraph = None
1212 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1213 j.run(runtimeContext)
1214 runner.api.container_requests().create.assert_called_with(
1215 body=JsonDiffMatcher({
1217 'HOME': '/var/spool/cwl',
1220 'name': 'test_run_True' + ("" if test_case == 0 else "_"+str(test_case+1)),
1221 'runtime_constraints': {
1224 'cuda': test_arv_req[test_case]
1226 'use_existing': True,
1229 '/tmp': {'kind': 'tmp',
1230 "capacity": 1073741824
1232 '/var/spool/cwl': {'kind': 'tmp',
1233 "capacity": 1073741824 }
1235 'state': 'Committed',
1236 'output_name': 'Output from step test_run_True' + ("" if test_case == 0 else "_"+str(test_case+1)),
1237 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1238 'output_path': '/var/spool/cwl',
1240 'container_image': '99999999999999999999999999999993+99',
1241 'command': ['nvidia-smi'],
1242 'cwd': '/var/spool/cwl',
1243 'scheduling_parameters': {},
1244 'properties': {'cwl_input': {}},
1245 'secret_mounts': {},
1246 'output_storage_classes': ["default"]
1250 # The test passes no builder.resources
1251 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1252 @mock.patch("arvados_cwl.arvdocker.determine_image_id")
1253 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1254 def test_match_local_docker(self, keepdocker, determine_image_id):
1255 arvados_cwl.add_arv_hints()
1257 runner = mock.MagicMock()
1258 runner.ignore_docker_for_reuse = False
1259 runner.intermediate_output_ttl = 0
1260 runner.secret_store = cwltool.secrets.SecretStore()
1261 runner.api._rootDesc = {"revision": "20210628"}
1262 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1264 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz4", {"dockerhash": "456"}),
1265 ("zzzzz-4zz18-zzzzzzzzzzzzzz3", {"dockerhash": "123"})]
1266 determine_image_id.side_effect = lambda x: "123"
1268 ex = mock.MagicMock()
1269 lookup = {"zzzzz-4zz18-zzzzzzzzzzzzzz4": {"portable_data_hash": "99999999999999999999999999999994+99"},
1270 "zzzzz-4zz18-zzzzzzzzzzzzzz3": {"portable_data_hash": "99999999999999999999999999999993+99"}}
1271 ex.execute.return_value = lookup[uuid]
1273 runner.api.collections().get.side_effect = execute
1278 "baseCommand": "echo",
1281 "cwlVersion": "v1.0",
1282 "class": "org.w3id.cwl.cwl.CommandLineTool"
1285 loadingContext, runtimeContext = self.helper(runner, True)
1287 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
1288 arvtool.formatgraph = None
1290 container_request = {
1292 'HOME': '/var/spool/cwl',
1295 'name': 'test_run_True',
1296 'runtime_constraints': {
1300 'use_existing': True,
1303 '/tmp': {'kind': 'tmp',
1304 "capacity": 1073741824
1306 '/var/spool/cwl': {'kind': 'tmp',
1307 "capacity": 1073741824 }
1309 'state': 'Committed',
1310 'output_name': 'Output from step test_run_True',
1311 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1312 'output_path': '/var/spool/cwl',
1314 'container_image': '99999999999999999999999999999994+99',
1315 'command': ['echo'],
1316 'cwd': '/var/spool/cwl',
1317 'scheduling_parameters': {},
1318 'properties': {'cwl_input': {}},
1319 'secret_mounts': {},
1320 'output_storage_classes': ["default"]
1323 runtimeContext.match_local_docker = False
1324 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1325 j.run(runtimeContext)
1326 runner.api.container_requests().create.assert_called_with(
1327 body=JsonDiffMatcher(container_request))
1329 runtimeContext.cached_docker_lookups.clear()
1330 runtimeContext.match_local_docker = True
1331 container_request['container_image'] = '99999999999999999999999999999993+99'
1332 container_request['name'] = 'test_run_True_2'
1333 container_request['output_name'] = 'Output from step test_run_True_2'
1334 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1335 j.run(runtimeContext)
1336 runner.api.container_requests().create.assert_called_with(
1337 body=JsonDiffMatcher(container_request))
1340 # The test passes no builder.resources
1341 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1342 @parameterized.expand([
1343 ("None, None", None, None, None),
1344 ("None, True", None, True, True),
1345 ("None, False", None, False, False),
1346 ("False, None", False, None, False),
1347 ("False, True", False, True, False),
1348 ("False, False", False, False, False),
1349 ("True, None", True, None, True),
1350 ("True, True", True, True, True),
1351 ("True, False", True, False, False),
1353 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1354 def test_run_preemptible_hint(self, _, enable_preemptible, preemptible_hint,
1355 preemptible_setting, keepdocker):
1356 arvados_cwl.add_arv_hints()
1358 runner = mock.MagicMock()
1359 runner.ignore_docker_for_reuse = False
1360 runner.intermediate_output_ttl = 0
1361 runner.secret_store = cwltool.secrets.SecretStore()
1362 runner.api._rootDesc = {"revision": "20210628"}
1363 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1365 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1366 runner.api.collections().get().execute.return_value = {
1367 "portable_data_hash": "99999999999999999999999999999993+99"}
1369 if preemptible_hint is not None:
1371 "class": "http://arvados.org/cwl#UsePreemptible",
1372 "usePreemptible": preemptible_hint
1380 "baseCommand": "ls",
1381 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1383 "class": "CommandLineTool",
1384 "cwlVersion": "v1.2",
1388 loadingContext, runtimeContext = self.helper(runner)
1390 runtimeContext.name = 'test_run_enable_preemptible_'+str(enable_preemptible)+str(preemptible_hint)
1391 runtimeContext.enable_preemptible = enable_preemptible
1393 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1394 arvtool.formatgraph = None
1396 # Test the interactions between --enable/disable-preemptible
1397 # and UsePreemptible hint
1400 if preemptible_setting is not None:
1401 sched['preemptible'] = preemptible_setting
1403 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1404 j.run(runtimeContext)
1405 runner.api.container_requests().create.assert_called_with(
1406 body=JsonDiffMatcher({
1408 'HOME': '/var/spool/cwl',
1411 'name': runtimeContext.name,
1412 'runtime_constraints': {
1416 'use_existing': True,
1419 '/tmp': {'kind': 'tmp',
1420 "capacity": 1073741824
1422 '/var/spool/cwl': {'kind': 'tmp',
1423 "capacity": 1073741824 }
1425 'state': 'Committed',
1426 'output_name': 'Output from step '+runtimeContext.name,
1427 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1428 'output_path': '/var/spool/cwl',
1430 'container_image': '99999999999999999999999999999993+99',
1431 'command': ['ls', '/var/spool/cwl'],
1432 'cwd': '/var/spool/cwl',
1433 'scheduling_parameters': sched,
1434 'properties': {'cwl_input': {}},
1435 'secret_mounts': {},
1436 'output_storage_classes': ["default"]
1440 # The test passes no builder.resources
1441 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1442 @parameterized.expand([
1443 ("None, None", None, None, False),
1444 ("None, True", None, True, True),
1445 ("None, False", None, False, False),
1446 ("False, None", False, None, False),
1447 ("False, True", False, True, False), # command line overrides hint
1448 ("False, False", False, False, False),
1449 ("True, None", True, None, True),
1450 ("True, True", True, True, True),
1451 ("True, False", True, False, False), # hint overrides command line
1453 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
1454 def test_spot_retry(self, _, enable_resubmit_non_preemptible,
1455 preemption_behavior_hint,
1456 expect_resubmit_behavior,
1458 arvados_cwl.add_arv_hints()
1459 #arv_docker_clear_cache()
1461 runner = mock.MagicMock()
1462 runner.ignore_docker_for_reuse = False
1463 runner.intermediate_output_ttl = 0
1464 runner.secret_store = cwltool.secrets.SecretStore()
1465 runner.api._rootDesc = {"revision": "20210628"}
1466 runner.api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1468 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
1469 runner.api.collections().get().execute.return_value = {
1470 "portable_data_hash": "99999999999999999999999999999993+99"}
1474 "class": "http://arvados.org/cwl#UsePreemptible",
1475 "usePreemptible": True
1478 if preemption_behavior_hint is not None:
1480 "class": "http://arvados.org/cwl#PreemptionBehavior",
1481 "resubmitNonPreemptible": preemption_behavior_hint
1487 "baseCommand": "ls",
1488 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
1490 "class": "CommandLineTool",
1491 "cwlVersion": "v1.2",
1495 loadingContext, runtimeContext = self.helper(runner)
1497 runtimeContext.name = 'test_spot_retry_'+str(enable_resubmit_non_preemptible)+str(preemption_behavior_hint)
1498 runtimeContext.enable_resubmit_non_preemptible = enable_resubmit_non_preemptible
1500 arvtool = cwltool.load_tool.load_tool(tool, loadingContext)
1501 arvtool.formatgraph = None
1503 # Test the interactions between --enable/disable-preemptible
1504 # and UsePreemptible hint
1506 expect_container_request = {
1508 'HOME': '/var/spool/cwl',
1511 'name': runtimeContext.name,
1512 'runtime_constraints': {
1516 'use_existing': True,
1519 '/tmp': {'kind': 'tmp',
1520 "capacity": 1073741824
1522 '/var/spool/cwl': {'kind': 'tmp',
1523 "capacity": 1073741824 }
1525 'state': 'Committed',
1526 'output_name': 'Output from step '+runtimeContext.name,
1527 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
1528 'output_path': '/var/spool/cwl',
1530 'container_image': '99999999999999999999999999999993+99',
1531 'command': ['ls', '/var/spool/cwl'],
1532 'cwd': '/var/spool/cwl',
1533 'scheduling_parameters': {'preemptible': True},
1534 'properties': {'cwl_input': {}},
1535 'secret_mounts': {},
1536 'output_storage_classes': ["default"],
1539 expect_resubmit_container_request = expect_container_request.copy()
1540 expect_resubmit_container_request['scheduling_parameters'] = {'preemptible': False}
1542 if expect_resubmit_behavior:
1543 expect_container_request['container_count_max'] = 1
1545 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
1546 j.run(runtimeContext)
1547 runner.api.container_requests().create.assert_called_with(
1548 body=JsonDiffMatcher(expect_container_request))
1549 runner.api.containers().get().execute.return_value = {
1550 "state":"Cancelled",
1551 "output": "abc+123",
1555 "preemptionNotice": "bye bye"
1561 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
1562 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
1563 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
1564 "modified_at": "2017-05-26T12:01:22Z",
1568 if expect_resubmit_behavior:
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()
1733 #arv_docker_clear_cache()
1735 def helper(self, runner, enable_reuse=True):
1736 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
1738 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
1739 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
1741 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
1742 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
1743 document_loader.fetch_text = document_loader.fetcher.fetch_text
1744 document_loader.check_exists = document_loader.fetcher.check_exists
1746 loadingContext = arvados_cwl.context.ArvLoadingContext(
1747 {"avsc_names": avsc_names,
1749 "make_fs_access": make_fs_access,
1750 "loader": document_loader,
1751 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
1752 "construct_tool_object": runner.arv_make_tool,
1753 "default_docker_image": "arvados/jobs:"+arvados_cwl.__version__,
1755 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
1756 {"work_api": "containers",
1758 "name": "test_run_wf_"+str(enable_reuse),
1759 "make_fs_access": make_fs_access,
1761 "enable_reuse": enable_reuse,
1764 return loadingContext, runtimeContext
1766 # The test passes no builder.resources
1767 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1768 @mock.patch("arvados.collection.CollectionReader")
1769 @mock.patch("arvados.collection.Collection")
1770 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1771 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
1772 arvados_cwl.add_arv_hints()
1774 api = mock.MagicMock()
1775 api._rootDesc = get_rootDesc()
1776 api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1778 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1779 self.assertEqual(runner.work_api, 'containers')
1781 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1782 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
1783 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1784 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1786 runner.api.containers().current().execute.return_value = {}
1788 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1789 runner.ignore_docker_for_reuse = False
1790 runner.num_retries = 0
1791 runner.secret_store = cwltool.secrets.SecretStore()
1793 loadingContext, runtimeContext = self.helper(runner)
1794 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1796 mockcollectionreader().exists.return_value = True
1798 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
1799 metadata["cwlVersion"] = tool["cwlVersion"]
1801 mockc = mock.MagicMock()
1802 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
1803 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
1805 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1806 arvtool.formatgraph = None
1807 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1809 next(it).run(runtimeContext)
1810 next(it).run(runtimeContext)
1812 with open("tests/wf/scatter2_subwf.cwl") as f:
1813 subwf = StripYAMLComments(f.read()).rstrip()
1815 runner.api.container_requests().create.assert_called_with(
1816 body=JsonDiffMatcher({
1821 "--preserve-entire-environment",
1825 "container_image": "99999999999999999999999999999993+99",
1826 "cwd": "/var/spool/cwl",
1828 "HOME": "/var/spool/cwl",
1832 "/keep/99999999999999999999999999999999+118": {
1833 "kind": "collection",
1834 "portable_data_hash": "99999999999999999999999999999999+118"
1837 "capacity": 1073741824,
1841 "capacity": 1073741824,
1844 "/var/spool/cwl/cwl.input.yml": {
1845 "kind": "collection",
1846 "path": "cwl.input.yml",
1847 "portable_data_hash": "99999999999999999999999999999996+99"
1849 "/var/spool/cwl/workflow.cwl": {
1850 "kind": "collection",
1851 "path": "workflow.cwl",
1852 "portable_data_hash": "99999999999999999999999999999996+99"
1856 "path": "/var/spool/cwl/cwl.output.json"
1859 "name": "scatterstep",
1860 "output_name": "Output from step scatterstep",
1861 "output_path": "/var/spool/cwl",
1864 "properties": {'cwl_input': {
1866 "basename": "token.txt",
1868 "dirname": "/keep/99999999999999999999999999999999+118",
1869 "location": "keep:99999999999999999999999999999999+118/token.txt",
1871 "nameroot": "token",
1872 "path": "/keep/99999999999999999999999999999999+118/token.txt",
1877 "runtime_constraints": {
1881 "scheduling_parameters": {},
1882 "secret_mounts": {},
1883 "state": "Committed",
1884 "use_existing": True,
1885 'output_storage_classes': ["default"]
1887 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
1888 mockc.open().__enter__().write.assert_has_calls([mock.call(
1891 "basename": "token.txt",
1893 "location": "/keep/99999999999999999999999999999999+118/token.txt",
1899 # The test passes no builder.resources
1900 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
1901 @mock.patch("arvados.collection.CollectionReader")
1902 @mock.patch("arvados.collection.Collection")
1903 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1904 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
1905 arvados_cwl.add_arv_hints()
1907 api = mock.MagicMock()
1908 api._rootDesc = get_rootDesc()
1909 api.config.return_value = {"Containers": {"DefaultKeepCacheRAM": 256<<20}}
1911 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1912 self.assertEqual(runner.work_api, 'containers')
1914 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1915 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1916 "portable_data_hash": "99999999999999999999999999999993+99"}
1917 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1918 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1920 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1921 runner.ignore_docker_for_reuse = False
1922 runner.num_retries = 0
1923 runner.secret_store = cwltool.secrets.SecretStore()
1925 loadingContext, runtimeContext = self.helper(runner)
1926 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1927 loadingContext.do_update = True
1928 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1930 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1932 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1933 arvtool.formatgraph = None
1934 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1936 next(it).run(runtimeContext)
1937 next(it).run(runtimeContext)
1939 with open("tests/wf/echo-subwf.cwl") as f:
1940 subwf = StripYAMLComments(f.read())
1942 runner.api.container_requests().create.assert_called_with(
1943 body=JsonDiffMatcher({
1945 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1946 'scheduling_parameters': {},
1947 'name': u'echo-subwf',
1948 'secret_mounts': {},
1949 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1950 'properties': {'cwl_input': {}},
1953 '/var/spool/cwl/cwl.input.yml': {
1954 'portable_data_hash': '99999999999999999999999999999996+99',
1955 'kind': 'collection',
1956 'path': 'cwl.input.yml'
1958 '/var/spool/cwl/workflow.cwl': {
1959 'portable_data_hash': '99999999999999999999999999999996+99',
1960 'kind': 'collection',
1961 'path': 'workflow.cwl'
1964 'path': '/var/spool/cwl/cwl.output.json',
1969 'capacity': 1073741824
1970 }, '/var/spool/cwl': {
1972 'capacity': 3221225472
1975 'state': 'Committed',
1976 'output_path': '/var/spool/cwl',
1977 'container_image': '99999999999999999999999999999993+99',
1982 u'--preserve-entire-environment',
1986 'use_existing': True,
1987 'output_name': u'Output from step echo-subwf',
1988 'cwd': '/var/spool/cwl',
1989 'output_storage_classes': ["default"]
1992 def test_default_work_api(self):
1993 arvados_cwl.add_arv_hints()
1995 api = mock.MagicMock()
1996 api._rootDesc = copy.deepcopy(get_rootDesc())
1997 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1998 self.assertEqual(runner.work_api, 'containers')