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 schema_salad.ref_resolver import Loader
22 from schema_salad.sourceline import cmap
24 from .matcher import JsonDiffMatcher, StripYAMLComments
25 from .mock_discovery import get_rootDesc
27 if not os.getenv('ARVADOS_DEBUG'):
28 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
29 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
31 class CollectionMock(object):
32 def __init__(self, vwdmock, *args, **kwargs):
33 self.vwdmock = vwdmock
36 def open(self, *args, **kwargs):
38 return self.vwdmock.open(*args, **kwargs)
40 def copy(self, *args, **kwargs):
42 self.vwdmock.copy(*args, **kwargs)
44 def save_new(self, *args, **kwargs):
50 def portable_data_hash(self):
52 return arvados.config.EMPTY_BLOCK_LOCATOR
54 return "99999999999999999999999999999996+99"
57 class TestContainer(unittest.TestCase):
60 cwltool.process._names = set()
62 def helper(self, runner, enable_reuse=True):
63 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
65 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
66 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
67 loadingContext = arvados_cwl.context.ArvLoadingContext(
68 {"avsc_names": avsc_names,
70 "make_fs_access": make_fs_access,
72 "metadata": {"cwlVersion": "v1.1", "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"}})
73 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
74 {"work_api": "containers",
76 "name": "test_run_"+str(enable_reuse),
77 "make_fs_access": make_fs_access,
79 "enable_reuse": enable_reuse,
81 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
84 return loadingContext, runtimeContext
86 # Helper function to set up the ArvCwlExecutor to use the containers api
87 # and test that the RuntimeStatusLoggingHandler is set up correctly
88 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
89 api = mock.MagicMock()
90 api._rootDesc = copy.deepcopy(get_rootDesc())
92 # Make sure ArvCwlExecutor thinks it's running inside a container so it
93 # adds the logging handler that will call runtime_status_update() mock
94 self.assertFalse(gcc_mock.called)
95 runner = arvados_cwl.ArvCwlExecutor(api)
96 self.assertEqual(runner.work_api, 'containers')
97 root_logger = logging.getLogger('')
98 handlerClasses = [h.__class__ for h in root_logger.handlers]
99 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
102 # The test passes no builder.resources
103 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
104 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
105 def test_run(self, keepdocker):
106 for enable_reuse in (True, False):
107 arv_docker_clear_cache()
109 runner = mock.MagicMock()
110 runner.ignore_docker_for_reuse = False
111 runner.intermediate_output_ttl = 0
112 runner.secret_store = cwltool.secrets.SecretStore()
114 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
115 runner.api.collections().get().execute.return_value = {
116 "portable_data_hash": "99999999999999999999999999999993+99"}
122 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
124 "class": "CommandLineTool"
127 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
129 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
130 arvtool.formatgraph = None
132 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
133 j.run(runtimeContext)
134 runner.api.container_requests().create.assert_called_with(
135 body=JsonDiffMatcher({
137 'HOME': '/var/spool/cwl',
140 'name': 'test_run_'+str(enable_reuse),
141 'runtime_constraints': {
145 'use_existing': enable_reuse,
148 '/tmp': {'kind': 'tmp',
149 "capacity": 1073741824
151 '/var/spool/cwl': {'kind': 'tmp',
152 "capacity": 1073741824 }
154 'state': 'Committed',
155 'output_name': 'Output for step test_run_'+str(enable_reuse),
156 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
157 'output_path': '/var/spool/cwl',
159 'container_image': '99999999999999999999999999999993+99',
160 'command': ['ls', '/var/spool/cwl'],
161 'cwd': '/var/spool/cwl',
162 'scheduling_parameters': {},
167 # The test passes some fields in builder.resources
168 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
169 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
170 def test_resource_requirements(self, keepdocker):
171 arv_docker_clear_cache()
172 runner = mock.MagicMock()
173 runner.ignore_docker_for_reuse = False
174 runner.intermediate_output_ttl = 3600
175 runner.secret_store = cwltool.secrets.SecretStore()
177 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
178 runner.api.collections().get().execute.return_value = {
179 "portable_data_hash": "99999999999999999999999999999993+99"}
185 "class": "ResourceRequirement",
191 "class": "http://arvados.org/cwl#RuntimeConstraints",
194 "class": "http://arvados.org/cwl#APIRequirement",
196 "class": "http://arvados.org/cwl#PartitionRequirement",
199 "class": "http://arvados.org/cwl#IntermediateOutput",
202 "class": "http://arvados.org/cwl#ReuseRequirement",
207 "class": "CommandLineTool"
210 loadingContext, runtimeContext = self.helper(runner)
211 runtimeContext.name = "test_resource_requirements"
213 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
214 arvtool.formatgraph = None
215 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
216 j.run(runtimeContext)
218 call_args, call_kwargs = runner.api.container_requests().create.call_args
220 call_body_expected = {
222 'HOME': '/var/spool/cwl',
225 'name': 'test_resource_requirements',
226 'runtime_constraints': {
229 'keep_cache_ram': 536870912,
232 'use_existing': False,
235 '/tmp': {'kind': 'tmp',
236 "capacity": 4194304000 },
237 '/var/spool/cwl': {'kind': 'tmp',
238 "capacity": 5242880000 }
240 'state': 'Committed',
241 'output_name': 'Output for step test_resource_requirements',
242 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
243 'output_path': '/var/spool/cwl',
245 'container_image': '99999999999999999999999999999993+99',
247 'cwd': '/var/spool/cwl',
248 'scheduling_parameters': {
249 'partitions': ['blurb']
255 call_body = call_kwargs.get('body', None)
256 self.assertNotEqual(None, call_body)
257 for key in call_body:
258 self.assertEqual(call_body_expected.get(key), call_body.get(key))
261 # The test passes some fields in builder.resources
262 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
263 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
264 @mock.patch("arvados.collection.Collection")
265 def test_initial_work_dir(self, collection_mock, keepdocker):
266 arv_docker_clear_cache()
267 runner = mock.MagicMock()
268 runner.ignore_docker_for_reuse = False
269 runner.intermediate_output_ttl = 0
270 runner.secret_store = cwltool.secrets.SecretStore()
272 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
273 runner.api.collections().get().execute.return_value = {
274 "portable_data_hash": "99999999999999999999999999999993+99"}
276 sourcemock = mock.MagicMock()
277 def get_collection_mock(p):
279 return (sourcemock, p.split("/", 1)[1])
281 return (sourcemock, "")
282 runner.fs_access.get_collection.side_effect = get_collection_mock
284 vwdmock = mock.MagicMock()
285 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
291 "class": "InitialWorkDirRequirement",
295 "location": "keep:99999999999999999999999999999995+99/bar"
298 "class": "Directory",
300 "location": "keep:99999999999999999999999999999995+99"
304 "basename": "filename",
305 "location": "keep:99999999999999999999999999999995+99/baz/filename"
308 "class": "Directory",
309 "basename": "subdir",
310 "location": "keep:99999999999999999999999999999995+99/subdir"
315 "class": "CommandLineTool"
318 loadingContext, runtimeContext = self.helper(runner)
319 runtimeContext.name = "test_initial_work_dir"
321 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
322 arvtool.formatgraph = None
323 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
324 j.run(runtimeContext)
326 call_args, call_kwargs = runner.api.container_requests().create.call_args
328 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
329 vwdmock.copy.assert_has_calls([mock.call('.', 'foo2', source_collection=sourcemock)])
330 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
331 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
333 call_body_expected = {
335 'HOME': '/var/spool/cwl',
338 'name': 'test_initial_work_dir',
339 'runtime_constraints': {
343 'use_existing': True,
346 '/tmp': {'kind': 'tmp',
347 "capacity": 1073741824 },
348 '/var/spool/cwl': {'kind': 'tmp',
349 "capacity": 1073741824 },
350 '/var/spool/cwl/foo': {
351 'kind': 'collection',
353 'portable_data_hash': '99999999999999999999999999999996+99'
355 '/var/spool/cwl/foo2': {
356 'kind': 'collection',
358 'portable_data_hash': '99999999999999999999999999999996+99'
360 '/var/spool/cwl/filename': {
361 'kind': 'collection',
363 'portable_data_hash': '99999999999999999999999999999996+99'
365 '/var/spool/cwl/subdir': {
366 'kind': 'collection',
368 'portable_data_hash': '99999999999999999999999999999996+99'
371 'state': 'Committed',
372 'output_name': 'Output for step test_initial_work_dir',
373 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
374 'output_path': '/var/spool/cwl',
376 'container_image': '99999999999999999999999999999993+99',
378 'cwd': '/var/spool/cwl',
379 'scheduling_parameters': {
385 call_body = call_kwargs.get('body', None)
386 self.assertNotEqual(None, call_body)
387 for key in call_body:
388 self.assertEqual(call_body_expected.get(key), call_body.get(key))
391 # Test redirecting stdin/stdout/stderr
392 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
393 def test_redirects(self, keepdocker):
394 arv_docker_clear_cache()
396 runner = mock.MagicMock()
397 runner.ignore_docker_for_reuse = False
398 runner.intermediate_output_ttl = 0
399 runner.secret_store = cwltool.secrets.SecretStore()
401 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
402 runner.api.collections().get().execute.return_value = {
403 "portable_data_hash": "99999999999999999999999999999993+99"}
405 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
411 "stdout": "stdout.txt",
412 "stderr": "stderr.txt",
413 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
414 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
416 "class": "CommandLineTool"
419 loadingContext, runtimeContext = self.helper(runner)
420 runtimeContext.name = "test_run_redirect"
422 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
423 arvtool.formatgraph = None
424 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
425 j.run(runtimeContext)
426 runner.api.container_requests().create.assert_called_with(
427 body=JsonDiffMatcher({
429 'HOME': '/var/spool/cwl',
432 'name': 'test_run_redirect',
433 'runtime_constraints': {
437 'use_existing': True,
440 '/tmp': {'kind': 'tmp',
441 "capacity": 1073741824 },
442 '/var/spool/cwl': {'kind': 'tmp',
443 "capacity": 1073741824 },
446 "path": "/var/spool/cwl/stderr.txt"
449 "kind": "collection",
451 "portable_data_hash": "99999999999999999999999999999996+99"
455 "path": "/var/spool/cwl/stdout.txt"
458 'state': 'Committed',
459 "output_name": "Output for step test_run_redirect",
460 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
461 'output_path': '/var/spool/cwl',
463 'container_image': '99999999999999999999999999999993+99',
464 'command': ['ls', '/var/spool/cwl'],
465 'cwd': '/var/spool/cwl',
466 'scheduling_parameters': {},
471 @mock.patch("arvados.collection.Collection")
472 def test_done(self, col):
473 api = mock.MagicMock()
475 runner = mock.MagicMock()
477 runner.num_retries = 0
478 runner.ignore_docker_for_reuse = False
479 runner.intermediate_output_ttl = 0
480 runner.secret_store = cwltool.secrets.SecretStore()
482 runner.api.containers().get().execute.return_value = {"state":"Complete",
486 col().open.return_value = []
488 loadingContext, runtimeContext = self.helper(runner)
490 arvjob = arvados_cwl.ArvadosContainer(runner,
498 arvjob.output_callback = mock.MagicMock()
499 arvjob.collect_outputs = mock.MagicMock()
500 arvjob.successCodes = [0]
501 arvjob.outdir = "/var/spool/cwl"
502 arvjob.output_ttl = 3600
504 arvjob.collect_outputs.return_value = {"out": "stuff"}
508 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
509 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
510 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
511 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
512 "modified_at": "2017-05-26T12:01:22Z"
515 self.assertFalse(api.collections().create.called)
516 self.assertFalse(runner.runtime_status_error.called)
518 arvjob.collect_outputs.assert_called_with("keep:abc+123", 0)
519 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
520 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
522 # Test to make sure we dont call runtime_status_update if we already did
523 # some where higher up in the call stack
524 @mock.patch("arvados_cwl.util.get_current_container")
525 def test_recursive_runtime_status_update(self, gcc_mock):
526 self.setup_and_test_container_executor_and_logging(gcc_mock)
527 root_logger = logging.getLogger('')
529 # get_current_container is invoked when we call runtime_status_update
530 # so try and log again!
531 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
533 root_logger.error("First Error")
535 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
538 # Test to make sure that an exception raised from
539 # get_current_container doesn't cause the logger to raise an
541 @mock.patch("arvados_cwl.util.get_current_container")
542 def test_runtime_status_get_current_container_exception(self, gcc_mock):
543 self.setup_and_test_container_executor_and_logging(gcc_mock)
544 root_logger = logging.getLogger('')
546 # get_current_container is invoked when we call
547 # runtime_status_update, it is going to also raise an
549 gcc_mock.side_effect = Exception("Second Error")
551 root_logger.error("First Error")
553 self.fail("Exception in logger should not propagate")
554 self.assertTrue(gcc_mock.called)
556 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
557 @mock.patch("arvados_cwl.util.get_current_container")
558 @mock.patch("arvados.collection.CollectionReader")
559 @mock.patch("arvados.collection.Collection")
560 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
561 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
563 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
564 self.assertTrue(gcc_mock.called)
566 runner.num_retries = 0
567 runner.ignore_docker_for_reuse = False
568 runner.intermediate_output_ttl = 0
569 runner.secret_store = cwltool.secrets.SecretStore()
570 runner.label = mock.MagicMock()
571 runner.label.return_value = '[container testjob]'
573 runner.api.containers().get().execute.return_value = {
580 col().open.return_value = []
582 loadingContext, runtimeContext = self.helper(runner)
584 arvjob = arvados_cwl.ArvadosContainer(runner,
592 arvjob.output_callback = mock.MagicMock()
593 arvjob.collect_outputs = mock.MagicMock()
594 arvjob.successCodes = [0]
595 arvjob.outdir = "/var/spool/cwl"
596 arvjob.output_ttl = 3600
597 arvjob.collect_outputs.return_value = {"out": "stuff"}
601 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
602 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
603 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
604 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
605 "modified_at": "2017-05-26T12:01:22Z"
608 rts_mock.assert_called_with(
610 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
611 ' ** log is empty **'
613 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
615 # The test passes no builder.resources
616 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
617 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
618 def test_mounts(self, keepdocker):
619 arv_docker_clear_cache()
621 runner = mock.MagicMock()
622 runner.ignore_docker_for_reuse = False
623 runner.intermediate_output_ttl = 0
624 runner.secret_store = cwltool.secrets.SecretStore()
626 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
627 runner.api.collections().get().execute.return_value = {
628 "portable_data_hash": "99999999999999999999999999999994+99",
629 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
631 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
640 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
642 "class": "CommandLineTool"
645 loadingContext, runtimeContext = self.helper(runner)
646 runtimeContext.name = "test_run_mounts"
648 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
649 arvtool.formatgraph = None
652 "class": "Directory",
653 "location": "keep:99999999999999999999999999999994+44",
654 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
658 "location": "keep:99999999999999999999999999999994+44/file1",
662 "location": "keep:99999999999999999999999999999994+44/file2",
667 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
668 j.run(runtimeContext)
669 runner.api.container_requests().create.assert_called_with(
670 body=JsonDiffMatcher({
672 'HOME': '/var/spool/cwl',
675 'name': 'test_run_mounts',
676 'runtime_constraints': {
680 'use_existing': True,
683 "/keep/99999999999999999999999999999994+44": {
684 "kind": "collection",
685 "portable_data_hash": "99999999999999999999999999999994+44",
686 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
688 '/tmp': {'kind': 'tmp',
689 "capacity": 1073741824 },
690 '/var/spool/cwl': {'kind': 'tmp',
691 "capacity": 1073741824 }
693 'state': 'Committed',
694 'output_name': 'Output for step test_run_mounts',
695 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
696 'output_path': '/var/spool/cwl',
698 'container_image': '99999999999999999999999999999994+99',
699 'command': ['ls', '/var/spool/cwl'],
700 'cwd': '/var/spool/cwl',
701 'scheduling_parameters': {},
706 # The test passes no builder.resources
707 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
708 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
709 def test_secrets(self, keepdocker):
710 arv_docker_clear_cache()
712 runner = mock.MagicMock()
713 runner.ignore_docker_for_reuse = False
714 runner.intermediate_output_ttl = 0
715 runner.secret_store = cwltool.secrets.SecretStore()
717 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
718 runner.api.collections().get().execute.return_value = {
719 "portable_data_hash": "99999999999999999999999999999993+99"}
721 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
723 tool = cmap({"arguments": ["md5sum", "example.conf"],
724 "class": "CommandLineTool",
727 "class": "http://commonwl.org/cwltool#Secrets",
733 "id": "#secret_job.cwl",
736 "id": "#secret_job.cwl/pw",
744 "class": "InitialWorkDirRequirement",
747 "entry": "username: user\npassword: $(inputs.pw)\n",
748 "entryname": "example.conf"
754 loadingContext, runtimeContext = self.helper(runner)
755 runtimeContext.name = "test_secrets"
757 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
758 arvtool.formatgraph = None
760 job_order = {"pw": "blorp"}
761 runner.secret_store.store(["pw"], job_order)
763 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
764 j.run(runtimeContext)
765 runner.api.container_requests().create.assert_called_with(
766 body=JsonDiffMatcher({
768 'HOME': '/var/spool/cwl',
771 'name': 'test_secrets',
772 'runtime_constraints': {
776 'use_existing': True,
779 '/tmp': {'kind': 'tmp',
780 "capacity": 1073741824
782 '/var/spool/cwl': {'kind': 'tmp',
783 "capacity": 1073741824 }
785 'state': 'Committed',
786 'output_name': 'Output for step test_secrets',
787 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
788 'output_path': '/var/spool/cwl',
790 'container_image': '99999999999999999999999999999993+99',
791 'command': ['md5sum', 'example.conf'],
792 'cwd': '/var/spool/cwl',
793 'scheduling_parameters': {},
796 "/var/spool/cwl/example.conf": {
797 "content": "username: user\npassword: blorp\n",
803 # The test passes no builder.resources
804 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
805 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
806 def test_timelimit(self, keepdocker):
807 arv_docker_clear_cache()
809 runner = mock.MagicMock()
810 runner.ignore_docker_for_reuse = False
811 runner.intermediate_output_ttl = 0
812 runner.secret_store = cwltool.secrets.SecretStore()
814 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
815 runner.api.collections().get().execute.return_value = {
816 "portable_data_hash": "99999999999999999999999999999993+99"}
822 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
824 "class": "CommandLineTool",
827 "class": "ToolTimeLimit",
833 loadingContext, runtimeContext = self.helper(runner)
834 runtimeContext.name = "test_timelimit"
836 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
837 arvtool.formatgraph = None
839 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
840 j.run(runtimeContext)
842 _, kwargs = runner.api.container_requests().create.call_args
843 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
846 class TestWorkflow(unittest.TestCase):
848 cwltool.process._names = set()
850 def helper(self, runner, enable_reuse=True):
851 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
853 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
854 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
856 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
857 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
858 document_loader.fetch_text = document_loader.fetcher.fetch_text
859 document_loader.check_exists = document_loader.fetcher.check_exists
861 loadingContext = arvados_cwl.context.ArvLoadingContext(
862 {"avsc_names": avsc_names,
864 "make_fs_access": make_fs_access,
865 "loader": document_loader,
866 "metadata": {"cwlVersion": "v1.1", "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
867 "construct_tool_object": runner.arv_make_tool})
868 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
869 {"work_api": "containers",
871 "name": "test_run_wf_"+str(enable_reuse),
872 "make_fs_access": make_fs_access,
874 "enable_reuse": enable_reuse,
877 return loadingContext, runtimeContext
879 # The test passes no builder.resources
880 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
881 @mock.patch("arvados.collection.CollectionReader")
882 @mock.patch("arvados.collection.Collection")
883 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
884 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
885 arv_docker_clear_cache()
886 arvados_cwl.add_arv_hints()
888 api = mock.MagicMock()
889 api._rootDesc = get_rootDesc()
891 runner = arvados_cwl.executor.ArvCwlExecutor(api)
892 self.assertEqual(runner.work_api, 'containers')
894 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
895 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
896 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
897 "portable_data_hash": "99999999999999999999999999999993+99"}]}
899 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
900 runner.ignore_docker_for_reuse = False
901 runner.num_retries = 0
902 runner.secret_store = cwltool.secrets.SecretStore()
904 loadingContext, runtimeContext = self.helper(runner)
905 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
907 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
908 metadata["cwlVersion"] = tool["cwlVersion"]
910 mockc = mock.MagicMock()
911 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
912 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
914 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
915 arvtool.formatgraph = None
916 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
918 next(it).run(runtimeContext)
919 next(it).run(runtimeContext)
921 with open("tests/wf/scatter2_subwf.cwl") as f:
922 subwf = StripYAMLComments(f.read()).rstrip()
924 runner.api.container_requests().create.assert_called_with(
925 body=JsonDiffMatcher({
930 "--preserve-entire-environment",
934 "container_image": "99999999999999999999999999999993+99",
935 "cwd": "/var/spool/cwl",
937 "HOME": "/var/spool/cwl",
941 "/keep/99999999999999999999999999999999+118": {
942 "kind": "collection",
943 "portable_data_hash": "99999999999999999999999999999999+118"
946 "capacity": 1073741824,
950 "capacity": 1073741824,
953 "/var/spool/cwl/cwl.input.yml": {
954 "kind": "collection",
955 "path": "cwl.input.yml",
956 "portable_data_hash": "99999999999999999999999999999996+99"
958 "/var/spool/cwl/workflow.cwl": {
959 "kind": "collection",
960 "path": "workflow.cwl",
961 "portable_data_hash": "99999999999999999999999999999996+99"
965 "path": "/var/spool/cwl/cwl.output.json"
968 "name": "scatterstep",
969 "output_name": "Output for step scatterstep",
970 "output_path": "/var/spool/cwl",
974 "runtime_constraints": {
978 "scheduling_parameters": {},
980 "state": "Committed",
983 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
984 mockc.open().__enter__().write.assert_has_calls([mock.call(
987 "basename": "token.txt",
989 "location": "/keep/99999999999999999999999999999999+118/token.txt",
995 # The test passes no builder.resources
996 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
997 @mock.patch("arvados.collection.CollectionReader")
998 @mock.patch("arvados.collection.Collection")
999 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1000 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
1001 arv_docker_clear_cache()
1002 arvados_cwl.add_arv_hints()
1004 api = mock.MagicMock()
1005 api._rootDesc = get_rootDesc()
1007 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1008 self.assertEqual(runner.work_api, 'containers')
1010 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1011 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1012 "portable_data_hash": "99999999999999999999999999999993+99"}
1013 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1014 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1016 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1017 runner.ignore_docker_for_reuse = False
1018 runner.num_retries = 0
1019 runner.secret_store = cwltool.secrets.SecretStore()
1021 loadingContext, runtimeContext = self.helper(runner)
1022 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1023 loadingContext.do_update = True
1024 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1026 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1028 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1029 arvtool.formatgraph = None
1030 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1032 next(it).run(runtimeContext)
1033 next(it).run(runtimeContext)
1035 with open("tests/wf/echo-subwf.cwl") as f:
1036 subwf = StripYAMLComments(f.read())
1038 runner.api.container_requests().create.assert_called_with(
1039 body=JsonDiffMatcher({
1041 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1042 'scheduling_parameters': {},
1043 'name': u'echo-subwf',
1044 'secret_mounts': {},
1045 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1049 '/var/spool/cwl/cwl.input.yml': {
1050 'portable_data_hash': '99999999999999999999999999999996+99',
1051 'kind': 'collection',
1052 'path': 'cwl.input.yml'
1054 '/var/spool/cwl/workflow.cwl': {
1055 'portable_data_hash': '99999999999999999999999999999996+99',
1056 'kind': 'collection',
1057 'path': 'workflow.cwl'
1060 'path': '/var/spool/cwl/cwl.output.json',
1065 'capacity': 1073741824
1066 }, '/var/spool/cwl': {
1068 'capacity': 3221225472
1071 'state': 'Committed',
1072 'output_path': '/var/spool/cwl',
1073 'container_image': '99999999999999999999999999999993+99',
1078 u'--preserve-entire-environment',
1079 u'workflow.cwl#main',
1082 'use_existing': True,
1083 'output_name': u'Output for step echo-subwf',
1084 'cwd': '/var/spool/cwl'
1087 def test_default_work_api(self):
1088 arvados_cwl.add_arv_hints()
1090 api = mock.MagicMock()
1091 api._rootDesc = copy.deepcopy(get_rootDesc())
1092 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1093 self.assertEqual(runner.work_api, 'containers')