1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from builtins import str
6 from builtins import object
9 import arvados_cwl.context
10 import arvados_cwl.util
11 from arvados_cwl.arvdocker import arv_docker_clear_cache
19 import cwltool.process
20 import cwltool.secrets
21 from cwltool.update import INTERNAL_VERSION
22 from schema_salad.ref_resolver import Loader
23 from schema_salad.sourceline import cmap
25 from .matcher import JsonDiffMatcher, StripYAMLComments
26 from .mock_discovery import get_rootDesc
28 if not os.getenv('ARVADOS_DEBUG'):
29 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
30 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
32 class CollectionMock(object):
33 def __init__(self, vwdmock, *args, **kwargs):
34 self.vwdmock = vwdmock
37 def open(self, *args, **kwargs):
39 return self.vwdmock.open(*args, **kwargs)
41 def copy(self, *args, **kwargs):
43 self.vwdmock.copy(*args, **kwargs)
45 def save_new(self, *args, **kwargs):
51 def portable_data_hash(self):
53 return arvados.config.EMPTY_BLOCK_LOCATOR
55 return "99999999999999999999999999999996+99"
58 class TestContainer(unittest.TestCase):
61 cwltool.process._names = set()
63 def helper(self, runner, enable_reuse=True):
64 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema(INTERNAL_VERSION)
66 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
67 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
68 loadingContext = arvados_cwl.context.ArvLoadingContext(
69 {"avsc_names": avsc_names,
71 "make_fs_access": make_fs_access,
73 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"}})
74 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
75 {"work_api": "containers",
77 "name": "test_run_"+str(enable_reuse),
78 "make_fs_access": make_fs_access,
80 "enable_reuse": enable_reuse,
82 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
85 return loadingContext, runtimeContext
87 # Helper function to set up the ArvCwlExecutor to use the containers api
88 # and test that the RuntimeStatusLoggingHandler is set up correctly
89 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
90 api = mock.MagicMock()
91 api._rootDesc = copy.deepcopy(get_rootDesc())
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(INTERNAL_VERSION)
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")
539 # Test to make sure that an exception raised from
540 # get_current_container doesn't cause the logger to raise an
542 @mock.patch("arvados_cwl.util.get_current_container")
543 def test_runtime_status_get_current_container_exception(self, gcc_mock):
544 self.setup_and_test_container_executor_and_logging(gcc_mock)
545 root_logger = logging.getLogger('')
547 # get_current_container is invoked when we call
548 # runtime_status_update, it is going to also raise an
550 gcc_mock.side_effect = Exception("Second Error")
552 root_logger.error("First Error")
554 self.fail("Exception in logger should not propagate")
555 self.assertTrue(gcc_mock.called)
557 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
558 @mock.patch("arvados_cwl.util.get_current_container")
559 @mock.patch("arvados.collection.CollectionReader")
560 @mock.patch("arvados.collection.Collection")
561 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
562 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
564 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
565 self.assertTrue(gcc_mock.called)
567 runner.num_retries = 0
568 runner.ignore_docker_for_reuse = False
569 runner.intermediate_output_ttl = 0
570 runner.secret_store = cwltool.secrets.SecretStore()
571 runner.label = mock.MagicMock()
572 runner.label.return_value = '[container testjob]'
574 runner.api.containers().get().execute.return_value = {
581 col().open.return_value = []
583 loadingContext, runtimeContext = self.helper(runner)
585 arvjob = arvados_cwl.ArvadosContainer(runner,
593 arvjob.output_callback = mock.MagicMock()
594 arvjob.collect_outputs = mock.MagicMock()
595 arvjob.successCodes = [0]
596 arvjob.outdir = "/var/spool/cwl"
597 arvjob.output_ttl = 3600
598 arvjob.collect_outputs.return_value = {"out": "stuff"}
602 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
603 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
604 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
605 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
606 "modified_at": "2017-05-26T12:01:22Z"
609 rts_mock.assert_called_with(
611 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
612 ' ** log is empty **'
614 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
616 # The test passes no builder.resources
617 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
618 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
619 def test_mounts(self, keepdocker):
620 arv_docker_clear_cache()
622 runner = mock.MagicMock()
623 runner.ignore_docker_for_reuse = False
624 runner.intermediate_output_ttl = 0
625 runner.secret_store = cwltool.secrets.SecretStore()
627 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
628 runner.api.collections().get().execute.return_value = {
629 "portable_data_hash": "99999999999999999999999999999994+99",
630 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
632 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
641 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
643 "class": "CommandLineTool"
646 loadingContext, runtimeContext = self.helper(runner)
647 runtimeContext.name = "test_run_mounts"
649 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
650 arvtool.formatgraph = None
653 "class": "Directory",
654 "location": "keep:99999999999999999999999999999994+44",
655 "http://arvados.org/cwl#collectionUUID": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
659 "location": "keep:99999999999999999999999999999994+44/file1",
663 "location": "keep:99999999999999999999999999999994+44/file2",
668 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
669 j.run(runtimeContext)
670 runner.api.container_requests().create.assert_called_with(
671 body=JsonDiffMatcher({
673 'HOME': '/var/spool/cwl',
676 'name': 'test_run_mounts',
677 'runtime_constraints': {
681 'use_existing': True,
684 "/keep/99999999999999999999999999999994+44": {
685 "kind": "collection",
686 "portable_data_hash": "99999999999999999999999999999994+44",
687 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz"
689 '/tmp': {'kind': 'tmp',
690 "capacity": 1073741824 },
691 '/var/spool/cwl': {'kind': 'tmp',
692 "capacity": 1073741824 }
694 'state': 'Committed',
695 'output_name': 'Output for step test_run_mounts',
696 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
697 'output_path': '/var/spool/cwl',
699 'container_image': '99999999999999999999999999999994+99',
700 'command': ['ls', '/var/spool/cwl'],
701 'cwd': '/var/spool/cwl',
702 'scheduling_parameters': {},
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_secrets(self, keepdocker):
711 arv_docker_clear_cache()
713 runner = mock.MagicMock()
714 runner.ignore_docker_for_reuse = False
715 runner.intermediate_output_ttl = 0
716 runner.secret_store = cwltool.secrets.SecretStore()
718 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
719 runner.api.collections().get().execute.return_value = {
720 "portable_data_hash": "99999999999999999999999999999993+99"}
722 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.1")
724 tool = cmap({"arguments": ["md5sum", "example.conf"],
725 "class": "CommandLineTool",
728 "class": "http://commonwl.org/cwltool#Secrets",
734 "id": "#secret_job.cwl",
737 "id": "#secret_job.cwl/pw",
745 "class": "InitialWorkDirRequirement",
748 "entry": "username: user\npassword: $(inputs.pw)\n",
749 "entryname": "example.conf"
755 loadingContext, runtimeContext = self.helper(runner)
756 runtimeContext.name = "test_secrets"
758 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
759 arvtool.formatgraph = None
761 job_order = {"pw": "blorp"}
762 runner.secret_store.store(["pw"], job_order)
764 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
765 j.run(runtimeContext)
766 runner.api.container_requests().create.assert_called_with(
767 body=JsonDiffMatcher({
769 'HOME': '/var/spool/cwl',
772 'name': 'test_secrets',
773 'runtime_constraints': {
777 'use_existing': True,
780 '/tmp': {'kind': 'tmp',
781 "capacity": 1073741824
783 '/var/spool/cwl': {'kind': 'tmp',
784 "capacity": 1073741824 }
786 'state': 'Committed',
787 'output_name': 'Output for step test_secrets',
788 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
789 'output_path': '/var/spool/cwl',
791 'container_image': '99999999999999999999999999999993+99',
792 'command': ['md5sum', 'example.conf'],
793 'cwd': '/var/spool/cwl',
794 'scheduling_parameters': {},
797 "/var/spool/cwl/example.conf": {
798 "content": "username: user\npassword: blorp\n",
804 # The test passes no builder.resources
805 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
806 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
807 def test_timelimit(self, keepdocker):
808 arv_docker_clear_cache()
810 runner = mock.MagicMock()
811 runner.ignore_docker_for_reuse = False
812 runner.intermediate_output_ttl = 0
813 runner.secret_store = cwltool.secrets.SecretStore()
815 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
816 runner.api.collections().get().execute.return_value = {
817 "portable_data_hash": "99999999999999999999999999999993+99"}
823 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
825 "class": "CommandLineTool",
828 "class": "ToolTimeLimit",
834 loadingContext, runtimeContext = self.helper(runner)
835 runtimeContext.name = "test_timelimit"
837 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
838 arvtool.formatgraph = None
840 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
841 j.run(runtimeContext)
843 _, kwargs = runner.api.container_requests().create.call_args
844 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))
847 class TestWorkflow(unittest.TestCase):
849 cwltool.process._names = set()
851 def helper(self, runner, enable_reuse=True):
852 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
854 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
855 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
857 document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=runner.api, fs_access=make_fs_access(""))
858 document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
859 document_loader.fetch_text = document_loader.fetcher.fetch_text
860 document_loader.check_exists = document_loader.fetcher.check_exists
862 loadingContext = arvados_cwl.context.ArvLoadingContext(
863 {"avsc_names": avsc_names,
865 "make_fs_access": make_fs_access,
866 "loader": document_loader,
867 "metadata": {"cwlVersion": INTERNAL_VERSION, "http://commonwl.org/cwltool#original_cwlVersion": "v1.0"},
868 "construct_tool_object": runner.arv_make_tool})
869 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
870 {"work_api": "containers",
872 "name": "test_run_wf_"+str(enable_reuse),
873 "make_fs_access": make_fs_access,
875 "enable_reuse": enable_reuse,
878 return loadingContext, runtimeContext
880 # The test passes no builder.resources
881 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
882 @mock.patch("arvados.collection.CollectionReader")
883 @mock.patch("arvados.collection.Collection")
884 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
885 def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
886 arv_docker_clear_cache()
887 arvados_cwl.add_arv_hints()
889 api = mock.MagicMock()
890 api._rootDesc = get_rootDesc()
892 runner = arvados_cwl.executor.ArvCwlExecutor(api)
893 self.assertEqual(runner.work_api, 'containers')
895 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
896 runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
897 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
898 "portable_data_hash": "99999999999999999999999999999993+99"}]}
900 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
901 runner.ignore_docker_for_reuse = False
902 runner.num_retries = 0
903 runner.secret_store = cwltool.secrets.SecretStore()
905 loadingContext, runtimeContext = self.helper(runner)
906 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
908 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/scatter2.cwl")
909 metadata["cwlVersion"] = tool["cwlVersion"]
911 mockc = mock.MagicMock()
912 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mockc, *args, **kwargs)
913 mockcollectionreader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "token.txt")
915 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
916 arvtool.formatgraph = None
917 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
919 next(it).run(runtimeContext)
920 next(it).run(runtimeContext)
922 with open("tests/wf/scatter2_subwf.cwl") as f:
923 subwf = StripYAMLComments(f.read()).rstrip()
925 runner.api.container_requests().create.assert_called_with(
926 body=JsonDiffMatcher({
931 "--preserve-entire-environment",
935 "container_image": "99999999999999999999999999999993+99",
936 "cwd": "/var/spool/cwl",
938 "HOME": "/var/spool/cwl",
942 "/keep/99999999999999999999999999999999+118": {
943 "kind": "collection",
944 "portable_data_hash": "99999999999999999999999999999999+118"
947 "capacity": 1073741824,
951 "capacity": 1073741824,
954 "/var/spool/cwl/cwl.input.yml": {
955 "kind": "collection",
956 "path": "cwl.input.yml",
957 "portable_data_hash": "99999999999999999999999999999996+99"
959 "/var/spool/cwl/workflow.cwl": {
960 "kind": "collection",
961 "path": "workflow.cwl",
962 "portable_data_hash": "99999999999999999999999999999996+99"
966 "path": "/var/spool/cwl/cwl.output.json"
969 "name": "scatterstep",
970 "output_name": "Output for step scatterstep",
971 "output_path": "/var/spool/cwl",
975 "runtime_constraints": {
979 "scheduling_parameters": {},
981 "state": "Committed",
984 mockc.open().__enter__().write.assert_has_calls([mock.call(subwf)])
985 mockc.open().__enter__().write.assert_has_calls([mock.call(
988 "basename": "token.txt",
990 "location": "/keep/99999999999999999999999999999999+118/token.txt",
996 # The test passes no builder.resources
997 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
998 @mock.patch("arvados.collection.CollectionReader")
999 @mock.patch("arvados.collection.Collection")
1000 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
1001 def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
1002 arv_docker_clear_cache()
1003 arvados_cwl.add_arv_hints()
1005 api = mock.MagicMock()
1006 api._rootDesc = get_rootDesc()
1008 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1009 self.assertEqual(runner.work_api, 'containers')
1011 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
1012 runner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1013 "portable_data_hash": "99999999999999999999999999999993+99"}
1014 runner.api.collections().list().execute.return_value = {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz",
1015 "portable_data_hash": "99999999999999999999999999999993+99"}]}
1017 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
1018 runner.ignore_docker_for_reuse = False
1019 runner.num_retries = 0
1020 runner.secret_store = cwltool.secrets.SecretStore()
1022 loadingContext, runtimeContext = self.helper(runner)
1023 runner.fs_access = runtimeContext.make_fs_access(runtimeContext.basedir)
1024 loadingContext.do_update = True
1025 tool, metadata = loadingContext.loader.resolve_ref("tests/wf/echo-wf.cwl")
1027 mockcollection.side_effect = lambda *args, **kwargs: CollectionMock(mock.MagicMock(), *args, **kwargs)
1029 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, loadingContext)
1030 arvtool.formatgraph = None
1031 it = arvtool.job({}, mock.MagicMock(), runtimeContext)
1033 next(it).run(runtimeContext)
1034 next(it).run(runtimeContext)
1036 with open("tests/wf/echo-subwf.cwl") as f:
1037 subwf = StripYAMLComments(f.read())
1039 runner.api.container_requests().create.assert_called_with(
1040 body=JsonDiffMatcher({
1042 'environment': {'HOME': '/var/spool/cwl', 'TMPDIR': '/tmp'},
1043 'scheduling_parameters': {},
1044 'name': u'echo-subwf',
1045 'secret_mounts': {},
1046 'runtime_constraints': {'API': True, 'vcpus': 3, 'ram': 1073741824},
1050 '/var/spool/cwl/cwl.input.yml': {
1051 'portable_data_hash': '99999999999999999999999999999996+99',
1052 'kind': 'collection',
1053 'path': 'cwl.input.yml'
1055 '/var/spool/cwl/workflow.cwl': {
1056 'portable_data_hash': '99999999999999999999999999999996+99',
1057 'kind': 'collection',
1058 'path': 'workflow.cwl'
1061 'path': '/var/spool/cwl/cwl.output.json',
1066 'capacity': 1073741824
1067 }, '/var/spool/cwl': {
1069 'capacity': 3221225472
1072 'state': 'Committed',
1073 'output_path': '/var/spool/cwl',
1074 'container_image': '99999999999999999999999999999993+99',
1079 u'--preserve-entire-environment',
1083 'use_existing': True,
1084 'output_name': u'Output for step echo-subwf',
1085 'cwd': '/var/spool/cwl'
1088 def test_default_work_api(self):
1089 arvados_cwl.add_arv_hints()
1091 api = mock.MagicMock()
1092 api._rootDesc = copy.deepcopy(get_rootDesc())
1093 runner = arvados_cwl.executor.ArvCwlExecutor(api)
1094 self.assertEqual(runner.work_api, 'containers')