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())
91 del api._rootDesc.get('resources')['jobs']['methods']['create']
93 # Make sure ArvCwlExecutor thinks it's running inside a container so it
94 # adds the logging handler that will call runtime_status_update() mock
95 self.assertFalse(gcc_mock.called)
96 runner = arvados_cwl.ArvCwlExecutor(api)
97 self.assertEqual(runner.work_api, 'containers')
98 root_logger = logging.getLogger('')
99 handlerClasses = [h.__class__ for h in root_logger.handlers]
100 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
103 # The test passes no builder.resources
104 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
105 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
106 def test_run(self, keepdocker):
107 for enable_reuse in (True, False):
108 arv_docker_clear_cache()
110 runner = mock.MagicMock()
111 runner.ignore_docker_for_reuse = False
112 runner.intermediate_output_ttl = 0
113 runner.secret_store = cwltool.secrets.SecretStore()
115 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
116 runner.api.collections().get().execute.return_value = {
117 "portable_data_hash": "99999999999999999999999999999993+99"}
123 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
125 "class": "CommandLineTool"
128 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
130 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
131 arvtool.formatgraph = None
133 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
134 j.run(runtimeContext)
135 runner.api.container_requests().create.assert_called_with(
136 body=JsonDiffMatcher({
138 'HOME': '/var/spool/cwl',
141 'name': 'test_run_'+str(enable_reuse),
142 'runtime_constraints': {
146 'use_existing': enable_reuse,
149 '/tmp': {'kind': 'tmp',
150 "capacity": 1073741824
152 '/var/spool/cwl': {'kind': 'tmp',
153 "capacity": 1073741824 }
155 'state': 'Committed',
156 'output_name': 'Output for step test_run_'+str(enable_reuse),
157 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
158 'output_path': '/var/spool/cwl',
160 'container_image': '99999999999999999999999999999993+99',
161 'command': ['ls', '/var/spool/cwl'],
162 'cwd': '/var/spool/cwl',
163 'scheduling_parameters': {},
168 # The test passes some fields in builder.resources
169 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
170 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
171 def test_resource_requirements(self, keepdocker):
172 arv_docker_clear_cache()
173 runner = mock.MagicMock()
174 runner.ignore_docker_for_reuse = False
175 runner.intermediate_output_ttl = 3600
176 runner.secret_store = cwltool.secrets.SecretStore()
178 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
179 runner.api.collections().get().execute.return_value = {
180 "portable_data_hash": "99999999999999999999999999999993+99"}
186 "class": "ResourceRequirement",
192 "class": "http://arvados.org/cwl#RuntimeConstraints",
195 "class": "http://arvados.org/cwl#APIRequirement",
197 "class": "http://arvados.org/cwl#PartitionRequirement",
200 "class": "http://arvados.org/cwl#IntermediateOutput",
203 "class": "http://arvados.org/cwl#ReuseRequirement",
208 "class": "CommandLineTool"
211 loadingContext, runtimeContext = self.helper(runner)
212 runtimeContext.name = "test_resource_requirements"
214 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
215 arvtool.formatgraph = None
216 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
217 j.run(runtimeContext)
219 call_args, call_kwargs = runner.api.container_requests().create.call_args
221 call_body_expected = {
223 'HOME': '/var/spool/cwl',
226 'name': 'test_resource_requirements',
227 'runtime_constraints': {
230 'keep_cache_ram': 536870912,
233 'use_existing': False,
236 '/tmp': {'kind': 'tmp',
237 "capacity": 4194304000 },
238 '/var/spool/cwl': {'kind': 'tmp',
239 "capacity": 5242880000 }
241 'state': 'Committed',
242 'output_name': 'Output for step test_resource_requirements',
243 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
244 'output_path': '/var/spool/cwl',
246 'container_image': '99999999999999999999999999999993+99',
248 'cwd': '/var/spool/cwl',
249 'scheduling_parameters': {
250 'partitions': ['blurb']
256 call_body = call_kwargs.get('body', None)
257 self.assertNotEqual(None, call_body)
258 for key in call_body:
259 self.assertEqual(call_body_expected.get(key), call_body.get(key))
262 # The test passes some fields in builder.resources
263 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
264 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
265 @mock.patch("arvados.collection.Collection")
266 def test_initial_work_dir(self, collection_mock, keepdocker):
267 arv_docker_clear_cache()
268 runner = mock.MagicMock()
269 runner.ignore_docker_for_reuse = False
270 runner.intermediate_output_ttl = 0
271 runner.secret_store = cwltool.secrets.SecretStore()
273 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
274 runner.api.collections().get().execute.return_value = {
275 "portable_data_hash": "99999999999999999999999999999993+99"}
277 sourcemock = mock.MagicMock()
278 def get_collection_mock(p):
280 return (sourcemock, p.split("/", 1)[1])
282 return (sourcemock, "")
283 runner.fs_access.get_collection.side_effect = get_collection_mock
285 vwdmock = mock.MagicMock()
286 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
292 "class": "InitialWorkDirRequirement",
296 "location": "keep:99999999999999999999999999999995+99/bar"
299 "class": "Directory",
301 "location": "keep:99999999999999999999999999999995+99"
305 "basename": "filename",
306 "location": "keep:99999999999999999999999999999995+99/baz/filename"
309 "class": "Directory",
310 "basename": "subdir",
311 "location": "keep:99999999999999999999999999999995+99/subdir"
316 "class": "CommandLineTool"
319 loadingContext, runtimeContext = self.helper(runner)
320 runtimeContext.name = "test_initial_work_dir"
322 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
323 arvtool.formatgraph = None
324 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
325 j.run(runtimeContext)
327 call_args, call_kwargs = runner.api.container_requests().create.call_args
329 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
330 vwdmock.copy.assert_has_calls([mock.call('.', 'foo2', source_collection=sourcemock)])
331 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
332 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
334 call_body_expected = {
336 'HOME': '/var/spool/cwl',
339 'name': 'test_initial_work_dir',
340 'runtime_constraints': {
344 'use_existing': True,
347 '/tmp': {'kind': 'tmp',
348 "capacity": 1073741824 },
349 '/var/spool/cwl': {'kind': 'tmp',
350 "capacity": 1073741824 },
351 '/var/spool/cwl/foo': {
352 'kind': 'collection',
354 'portable_data_hash': '99999999999999999999999999999996+99'
356 '/var/spool/cwl/foo2': {
357 'kind': 'collection',
359 'portable_data_hash': '99999999999999999999999999999996+99'
361 '/var/spool/cwl/filename': {
362 'kind': 'collection',
364 'portable_data_hash': '99999999999999999999999999999996+99'
366 '/var/spool/cwl/subdir': {
367 'kind': 'collection',
369 'portable_data_hash': '99999999999999999999999999999996+99'
372 'state': 'Committed',
373 'output_name': 'Output for step test_initial_work_dir',
374 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
375 'output_path': '/var/spool/cwl',
377 'container_image': '99999999999999999999999999999993+99',
379 'cwd': '/var/spool/cwl',
380 'scheduling_parameters': {
386 call_body = call_kwargs.get('body', None)
387 self.assertNotEqual(None, call_body)
388 for key in call_body:
389 self.assertEqual(call_body_expected.get(key), call_body.get(key))
392 # Test redirecting stdin/stdout/stderr
393 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
394 def test_redirects(self, keepdocker):
395 arv_docker_clear_cache()
397 runner = mock.MagicMock()
398 runner.ignore_docker_for_reuse = False
399 runner.intermediate_output_ttl = 0
400 runner.secret_store = cwltool.secrets.SecretStore()
402 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
403 runner.api.collections().get().execute.return_value = {
404 "portable_data_hash": "99999999999999999999999999999993+99"}
406 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
412 "stdout": "stdout.txt",
413 "stderr": "stderr.txt",
414 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
415 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
417 "class": "CommandLineTool"
420 loadingContext, runtimeContext = self.helper(runner)
421 runtimeContext.name = "test_run_redirect"
423 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
424 arvtool.formatgraph = None
425 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
426 j.run(runtimeContext)
427 runner.api.container_requests().create.assert_called_with(
428 body=JsonDiffMatcher({
430 'HOME': '/var/spool/cwl',
433 'name': 'test_run_redirect',
434 'runtime_constraints': {
438 'use_existing': True,
441 '/tmp': {'kind': 'tmp',
442 "capacity": 1073741824 },
443 '/var/spool/cwl': {'kind': 'tmp',
444 "capacity": 1073741824 },
447 "path": "/var/spool/cwl/stderr.txt"
450 "kind": "collection",
452 "portable_data_hash": "99999999999999999999999999999996+99"
456 "path": "/var/spool/cwl/stdout.txt"
459 'state': 'Committed',
460 "output_name": "Output for step test_run_redirect",
461 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
462 'output_path': '/var/spool/cwl',
464 'container_image': '99999999999999999999999999999993+99',
465 'command': ['ls', '/var/spool/cwl'],
466 'cwd': '/var/spool/cwl',
467 'scheduling_parameters': {},
472 @mock.patch("arvados.collection.Collection")
473 def test_done(self, col):
474 api = mock.MagicMock()
476 runner = mock.MagicMock()
478 runner.num_retries = 0
479 runner.ignore_docker_for_reuse = False
480 runner.intermediate_output_ttl = 0
481 runner.secret_store = cwltool.secrets.SecretStore()
483 runner.api.containers().get().execute.return_value = {"state":"Complete",
487 col().open.return_value = []
489 loadingContext, runtimeContext = self.helper(runner)
491 arvjob = arvados_cwl.ArvadosContainer(runner,
499 arvjob.output_callback = mock.MagicMock()
500 arvjob.collect_outputs = mock.MagicMock()
501 arvjob.successCodes = [0]
502 arvjob.outdir = "/var/spool/cwl"
503 arvjob.output_ttl = 3600
505 arvjob.collect_outputs.return_value = {"out": "stuff"}
509 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
510 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
511 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
512 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
513 "modified_at": "2017-05-26T12:01:22Z"
516 self.assertFalse(api.collections().create.called)
517 self.assertFalse(runner.runtime_status_error.called)
519 arvjob.collect_outputs.assert_called_with("keep:abc+123", 0)
520 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
521 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
523 # Test to make sure we dont call runtime_status_update if we already did
524 # some where higher up in the call stack
525 @mock.patch("arvados_cwl.util.get_current_container")
526 def test_recursive_runtime_status_update(self, gcc_mock):
527 self.setup_and_test_container_executor_and_logging(gcc_mock)
528 root_logger = logging.getLogger('')
530 # get_current_container is invoked when we call runtime_status_update
531 # so try and log again!
532 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
534 root_logger.error("First Error")
536 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
538 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
539 @mock.patch("arvados_cwl.util.get_current_container")
540 @mock.patch("arvados.collection.CollectionReader")
541 @mock.patch("arvados.collection.Collection")
542 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
543 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
545 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
546 self.assertTrue(gcc_mock.called)
548 runner.num_retries = 0
549 runner.ignore_docker_for_reuse = False
550 runner.intermediate_output_ttl = 0
551 runner.secret_store = cwltool.secrets.SecretStore()
552 runner.label = mock.MagicMock()
553 runner.label.return_value = '[container testjob]'
555 runner.api.containers().get().execute.return_value = {
562 col().open.return_value = []
564 loadingContext, runtimeContext = self.helper(runner)
566 arvjob = arvados_cwl.ArvadosContainer(runner,
574 arvjob.output_callback = mock.MagicMock()
575 arvjob.collect_outputs = mock.MagicMock()
576 arvjob.successCodes = [0]
577 arvjob.outdir = "/var/spool/cwl"
578 arvjob.output_ttl = 3600
579 arvjob.collect_outputs.return_value = {"out": "stuff"}
583 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
584 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
585 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
586 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
587 "modified_at": "2017-05-26T12:01:22Z"
590 rts_mock.assert_called_with(
592 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
593 ' ** log is empty **'
595 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
597 # The test passes no builder.resources
598 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
599 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
600 def test_mounts(self, keepdocker):
601 arv_docker_clear_cache()
603 runner = mock.MagicMock()
604 runner.ignore_docker_for_reuse = False
605 runner.intermediate_output_ttl = 0
606 runner.secret_store = cwltool.secrets.SecretStore()
608 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
609 runner.api.collections().get().execute.return_value = {
610 "portable_data_hash": "99999999999999999999999999999994+99",
611 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
613 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
622 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
624 "class": "CommandLineTool"
627 loadingContext, runtimeContext = self.helper(runner)
628 runtimeContext.name = "test_run_mounts"
630 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
631 arvtool.formatgraph = None
634 "class": "Directory",
635 "location": "keep:99999999999999999999999999999994+44",
636 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
640 "location": "keep:99999999999999999999999999999994+44/file1",
644 "location": "keep:99999999999999999999999999999994+44/file2",
649 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
650 j.run(runtimeContext)
651 runner.api.container_requests().create.assert_called_with(
652 body=JsonDiffMatcher({
654 'HOME': '/var/spool/cwl',
657 'name': 'test_run_mounts',
658 'runtime_constraints': {
662 'use_existing': True,
665 "/keep/99999999999999999999999999999994+44": {
666 "kind": "collection",
667 "portable_data_hash": "99999999999999999999999999999994+44",
668 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
670 '/tmp': {'kind': 'tmp',
671 "capacity": 1073741824 },
672 '/var/spool/cwl': {'kind': 'tmp',
673 "capacity": 1073741824 }
675 'state': 'Committed',
676 'output_name': 'Output for step test_run_mounts',
677 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
678 'output_path': '/var/spool/cwl',
680 'container_image': '99999999999999999999999999999994+99',
681 'command': ['ls', '/var/spool/cwl'],
682 'cwd': '/var/spool/cwl',
683 'scheduling_parameters': {},
688 # The test passes no builder.resources
689 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
690 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
691 def test_secrets(self, keepdocker):
692 arv_docker_clear_cache()
694 runner = mock.MagicMock()
695 runner.ignore_docker_for_reuse = False
696 runner.intermediate_output_ttl = 0
697 runner.secret_store = cwltool.secrets.SecretStore()
699 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
700 runner.api.collections().get().execute.return_value = {
701 "portable_data_hash": "99999999999999999999999999999993+99"}
703 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
705 tool = cmap({"arguments": ["md5sum", "example.conf"],
706 "class": "CommandLineTool",
709 "class": "http://commonwl.org/cwltool#Secrets",
715 "id": "#secret_job.cwl",
718 "id": "#secret_job.cwl/pw",
726 "class": "InitialWorkDirRequirement",
729 "entry": "username: user\npassword: $(inputs.pw)\n",
730 "entryname": "example.conf"
736 loadingContext, runtimeContext = self.helper(runner)
737 runtimeContext.name = "test_secrets"
739 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
740 arvtool.formatgraph = None
742 job_order = {"pw": "blorp"}
743 runner.secret_store.store(["pw"], job_order)
745 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
746 j.run(runtimeContext)
747 runner.api.container_requests().create.assert_called_with(
748 body=JsonDiffMatcher({
750 'HOME': '/var/spool/cwl',
753 'name': 'test_secrets',
754 'runtime_constraints': {
758 'use_existing': True,
761 '/tmp': {'kind': 'tmp',
762 "capacity": 1073741824
764 '/var/spool/cwl': {'kind': 'tmp',
765 "capacity": 1073741824 }
767 'state': 'Committed',
768 'output_name': 'Output for step test_secrets',
769 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
770 'output_path': '/var/spool/cwl',
772 'container_image': '99999999999999999999999999999993+99',
773 'command': ['md5sum', 'example.conf'],
774 'cwd': '/var/spool/cwl',
775 'scheduling_parameters': {},
778 "/var/spool/cwl/example.conf": {
779 "content": "username: user\npassword: blorp\n",
785 # The test passes no builder.resources
786 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
787 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
788 def test_timelimit(self, keepdocker):
789 arv_docker_clear_cache()
791 runner = mock.MagicMock()
792 runner.ignore_docker_for_reuse = False
793 runner.intermediate_output_ttl = 0
794 runner.secret_store = cwltool.secrets.SecretStore()
796 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
797 runner.api.collections().get().execute.return_value = {
798 "portable_data_hash": "99999999999999999999999999999993+99"}
804 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
806 "class": "CommandLineTool",
809 "class": "ToolTimeLimit",
815 loadingContext, runtimeContext = self.helper(runner)
816 runtimeContext.name = "test_timelimit"
818 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
819 arvtool.formatgraph = None
821 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
822 j.run(runtimeContext)
824 _, kwargs = runner.api.container_requests().create.call_args
825 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
828 class TestWorkflow(unittest.TestCase):
830 cwltool.process._names = set()
832 def helper(self, runner, enable_reuse=True):
833 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
835 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
836 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
838 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
839 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
840 document_loader.fetch_text = document_loader.fetcher.fetch_text
841 document_loader.check_exists = document_loader.fetcher.check_exists
843 loadingContext = arvados_cwl.context.ArvLoadingContext(
844 {"avsc_names": avsc_names,
846 "make_fs_access": make_fs_access,
847 "loader": document_loader,
848 "metadata": {"cwlVersion": "v1.1", "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
849 "construct_tool_object": runner.arv_make_tool})
850 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
851 {"work_api": "containers",
853 "name": "test_run_wf_"+str(enable_reuse),
854 "make_fs_access": make_fs_access,
856 "enable_reuse": enable_reuse,
859 return loadingContext, runtimeContext
861 # The test passes no builder.resources
862 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
863 @mock.patch("arvados.collection.CollectionReader")
864 @mock.patch("arvados.collection.Collection")
865 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
866 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
867 arv_docker_clear_cache()
868 arvados_cwl.add_arv_hints()
870 api = mock.MagicMock()
871 api._rootDesc = get_rootDesc()
873 runner = arvados_cwl.executor.ArvCwlExecutor(api)
874 self.assertEqual(runner.work_api, 'containers')
876 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
877 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
878 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
879 "portable_data_hash": "99999999999999999999999999999993+99"}]}
881 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
882 runner.ignore_docker_for_reuse = False
883 runner.num_retries = 0
884 runner.secret_store = cwltool.secrets.SecretStore()
886 loadingContext, runtimeContext = self.helper(runner)
887 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
889 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
890 metadata["cwlVersion"] = tool["cwlVersion"]
892 mockc = mock.MagicMock()
893 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
894 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
896 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
897 arvtool.formatgraph = None
898 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
900 next(it).run(runtimeContext)
901 next(it).run(runtimeContext)
903 with open("tests/wf/scatter2_subwf.cwl") as f:
904 subwf = StripYAMLComments(f.read()).rstrip()
906 runner.api.container_requests().create.assert_called_with(
907 body=JsonDiffMatcher({
912 "--preserve-entire-environment",
916 "container_image": "99999999999999999999999999999993+99",
917 "cwd": "/var/spool/cwl",
919 "HOME": "/var/spool/cwl",
923 "/keep/99999999999999999999999999999999+118": {
924 "kind": "collection",
925 "portable_data_hash": "99999999999999999999999999999999+118"
928 "capacity": 1073741824,
932 "capacity": 1073741824,
935 "/var/spool/cwl/cwl.input.yml": {
936 "kind": "collection",
937 "path": "cwl.input.yml",
938 "portable_data_hash": "99999999999999999999999999999996+99"
940 "/var/spool/cwl/workflow.cwl": {
941 "kind": "collection",
942 "path": "workflow.cwl",
943 "portable_data_hash": "99999999999999999999999999999996+99"
947 "path": "/var/spool/cwl/cwl.output.json"
950 "name": "scatterstep",
951 "output_name": "Output for step scatterstep",
952 "output_path": "/var/spool/cwl",
956 "runtime_constraints": {
960 "scheduling_parameters": {},
962 "state": "Committed",
965 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
966 mockc.open().__enter__().write.assert_has_calls([mock.call(
969 "basename": "token.txt",
971 "location": "/keep/99999999999999999999999999999999+118/token.txt",
977 # The test passes no builder.resources
978 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
979 @mock.patch("arvados.collection.CollectionReader")
980 @mock.patch("arvados.collection.Collection")
981 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
982 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
983 arv_docker_clear_cache()
984 arvados_cwl.add_arv_hints()
986 api = mock.MagicMock()
987 api._rootDesc = get_rootDesc()
989 runner = arvados_cwl.executor.ArvCwlExecutor(api)
990 self.assertEqual(runner.work_api, 'containers')
992 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
993 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
994 "portable_data_hash": "99999999999999999999999999999993+99"}
995 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
996 "portable_data_hash": "99999999999999999999999999999993+99"}]}
998 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
999 runner.ignore_docker_for_reuse = False
1000 runner.num_retries = 0
1001 runner.secret_store = cwltool.secrets.SecretStore()
1003 loadingContext, runtimeContext = self.helper(runner)
1004 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1005 loadingContext.do_update = True
1006 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1008 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1010 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1011 arvtool.formatgraph = None
1012 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1014 next(it).run(runtimeContext)
1015 next(it).run(runtimeContext)
1017 with open("tests/wf/echo-subwf.cwl") as f:
1018 subwf = StripYAMLComments(f.read())
1020 runner.api.container_requests().create.assert_called_with(
1021 body=JsonDiffMatcher({
1023 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1024 'scheduling_parameters': {},
1025 'name': u'echo-subwf',
1026 'secret_mounts': {},
1027 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1031 '/var/spool/cwl/cwl.input.yml': {
1032 'portable_data_hash': '99999999999999999999999999999996+99',
1033 'kind': 'collection',
1034 'path': 'cwl.input.yml'
1036 '/var/spool/cwl/workflow.cwl': {
1037 'portable_data_hash': '99999999999999999999999999999996+99',
1038 'kind': 'collection',
1039 'path': 'workflow.cwl'
1042 'path': '/var/spool/cwl/cwl.output.json',
1047 'capacity': 1073741824
1048 }, '/var/spool/cwl': {
1050 'capacity': 3221225472
1053 'state': 'Committed',
1054 'output_path': '/var/spool/cwl',
1055 'container_image': '99999999999999999999999999999993+99',
1060 u'--preserve-entire-environment',
1061 u'workflow.cwl#main',
1064 'use_existing': True,
1065 'output_name': u'Output for step echo-subwf',
1066 'cwd': '/var/spool/cwl'
1069 def test_default_work_api(self):
1070 arvados_cwl.add_arv_hints()
1072 api = mock.MagicMock()
1073 api._rootDesc = copy.deepcopy(get_rootDesc())
1074 del api._rootDesc.get('resources')['jobs']['methods']['create']
1075 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1076 self.assertEqual(runner.work_api, 'containers')