1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from future import standard_library
6 standard_library.install_aliases()
7 from builtins import object
8 from builtins import str
9 from future.utils import viewvalues
20 import cwltool.process
23 from io import BytesIO
25 # StringIO.StringIO and io.StringIO have different behavior write() is
26 # called with both python2 (byte) strings and unicode strings
27 # (specifically there's some logging in cwltool that causes trouble).
28 # This isn't a problem on python3 because all string are unicode.
29 if sys.version_info[0] < 3:
30 from StringIO import StringIO
32 from io import StringIO
35 import arvados.collection
37 import arvados_cwl.executor
38 import arvados_cwl.runner
41 from .matcher import JsonDiffMatcher, StripYAMLComments
42 from .mock_discovery import get_rootDesc
44 import ruamel.yaml as yaml
48 def stubs(wfname='submit_wf.cwl'):
49 def outer_wrapper(func, *rest):
50 @functools.wraps(func)
51 @mock.patch("arvados_cwl.arvdocker.determine_image_id")
52 @mock.patch("uuid.uuid4")
53 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
54 @mock.patch("arvados.collection.KeepClient")
55 @mock.patch("arvados.keep.KeepClient")
56 @mock.patch("arvados.events.subscribe")
57 def wrapped(self, events, keep_client1, keep_client2, keepdocker,
58 uuid4, determine_image_id, *args, **kwargs):
63 stubs.keepdocker = keepdocker
65 uuid4.side_effect = ["df80736f-f14d-4b10-b2e3-03aa27f034bb", "df80736f-f14d-4b10-b2e3-03aa27f034b1",
66 "df80736f-f14d-4b10-b2e3-03aa27f034b2", "df80736f-f14d-4b10-b2e3-03aa27f034b3",
67 "df80736f-f14d-4b10-b2e3-03aa27f034b4", "df80736f-f14d-4b10-b2e3-03aa27f034b5"]
69 determine_image_id.return_value = None
71 def putstub(p, **kwargs):
72 return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p))
73 keep_client1().put.side_effect = putstub
74 keep_client1.put.side_effect = putstub
75 keep_client2().put.side_effect = putstub
76 keep_client2.put.side_effect = putstub
78 stubs.keep_client = keep_client2
79 stubs.docker_images = {
80 "arvados/jobs:"+arvados_cwl.__version__: [("zzzzz-4zz18-zzzzzzzzzzzzzd3", {})],
81 "debian:buster-slim": [("zzzzz-4zz18-zzzzzzzzzzzzzd4", {})],
82 "arvados/jobs:123": [("zzzzz-4zz18-zzzzzzzzzzzzzd5", {})],
83 "arvados/jobs:latest": [("zzzzz-4zz18-zzzzzzzzzzzzzd6", {})],
85 def kd(a, b, image_name=None, image_tag=None, project_uuid=None):
86 return stubs.docker_images.get("%s:%s" % (image_name, image_tag), [])
87 stubs.keepdocker.side_effect = kd
89 stubs.fake_user_uuid = "zzzzz-tpzed-zzzzzzzzzzzzzzz"
90 stubs.fake_container_uuid = "zzzzz-dz642-zzzzzzzzzzzzzzz"
92 if sys.version_info[0] < 3:
93 stubs.capture_stdout = BytesIO()
95 stubs.capture_stdout = StringIO()
97 stubs.api = mock.MagicMock()
98 stubs.api._rootDesc = get_rootDesc()
99 stubs.api._rootDesc["uuidPrefix"] = "zzzzz"
100 stubs.api._rootDesc["revision"] = "20210628"
102 stubs.api.users().current().execute.return_value = {
103 "uuid": stubs.fake_user_uuid,
105 stubs.api.collections().list().execute.return_value = {"items": []}
106 stubs.api.containers().current().execute.return_value = {
107 "uuid": stubs.fake_container_uuid,
109 stubs.api.config()["StorageClasses"].items.return_value = {
115 class CollectionExecute(object):
116 def __init__(self, exe):
118 def execute(self, num_retries=None):
121 def collection_createstub(created_collections, body, ensure_unique_name=None):
122 mt = body["manifest_text"].encode('utf-8')
123 uuid = "zzzzz-4zz18-zzzzzzzzzzzzzx%d" % len(created_collections)
124 pdh = "%s+%i" % (hashlib.md5(mt).hexdigest(), len(mt))
125 created_collections[uuid] = {
127 "portable_data_hash": pdh,
128 "manifest_text": mt.decode('utf-8')
130 return CollectionExecute(created_collections[uuid])
132 def collection_getstub(created_collections, uuid):
133 for v in viewvalues(created_collections):
134 if uuid in (v["uuid"], v["portable_data_hash"]):
135 return CollectionExecute(v)
137 created_collections = {
138 "99999999999999999999999999999998+99": {
140 "portable_data_hash": "99999999999999999999999999999998+99",
141 "manifest_text": ". 99999999999999999999999999999998+99 0:0:file1.txt"
143 "99999999999999999999999999999997+99": {
145 "portable_data_hash": "99999999999999999999999999999997+99",
146 "manifest_text": ". 99999999999999999999999999999997+99 0:0:file1.txt"
148 "99999999999999999999999999999994+99": {
150 "portable_data_hash": "99999999999999999999999999999994+99",
151 "manifest_text": ". 99999999999999999999999999999994+99 0:0:expect_arvworkflow.cwl"
153 "zzzzz-4zz18-zzzzzzzzzzzzzd3": {
154 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd3",
155 "portable_data_hash": "999999999999999999999999999999d3+99",
158 "zzzzz-4zz18-zzzzzzzzzzzzzd4": {
159 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd4",
160 "portable_data_hash": "999999999999999999999999999999d4+99",
163 "zzzzz-4zz18-zzzzzzzzzzzzzd5": {
164 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd5",
165 "portable_data_hash": "999999999999999999999999999999d5+99",
168 "zzzzz-4zz18-zzzzzzzzzzzzzd6": {
169 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd6",
170 "portable_data_hash": "999999999999999999999999999999d6+99",
174 stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
175 stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
177 stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
178 stubs.api.jobs().create().execute.return_value = {
179 "uuid": stubs.expect_job_uuid,
183 stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
184 stubs.api.container_requests().create().execute.return_value = {
185 "uuid": stubs.expect_container_request_uuid,
186 "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
190 stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
191 stubs.api.pipeline_templates().create().execute.return_value = {
192 "uuid": stubs.expect_pipeline_template_uuid,
194 stubs.expect_job_spec = {
195 'runtime_constraints': {
196 'docker_image': '999999999999999999999999999999d3+99',
197 'min_ram_mb_per_node': 1024
199 'script_parameters': {
201 'basename': 'blorp.txt',
202 'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
206 'basename': '99999999999999999999999999999998+99',
207 'location': 'keep:99999999999999999999999999999998+99',
211 'basename': 'anonymous',
213 "basename": "renamed.txt",
215 "location": "keep:99999999999999999999999999999998+99/file1.txt",
220 'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main'
222 'repository': 'arvados',
223 'script_version': 'master',
224 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
225 'script': 'cwl-runner'
227 stubs.pipeline_component = stubs.expect_job_spec.copy()
228 stubs.expect_pipeline_instance = {
229 'name': 'submit_wf.cwl',
230 'state': 'RunningOnServer',
234 'runtime_constraints': {'docker_image': '999999999999999999999999999999d3+99', 'min_ram_mb_per_node': 1024},
235 'script_parameters': {
236 'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
238 'basename': 'blorp.txt',
240 'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
243 'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
246 'basename': 'renamed.txt',
247 'class': 'File', 'location':
248 'keep:99999999999999999999999999999998+99/file1.txt',
252 'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main',
254 'arv:enable_reuse': True,
255 'arv:on_error': 'continue'
257 'repository': 'arvados',
258 'script_version': 'master',
259 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
260 'script': 'cwl-runner',
261 'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
265 stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
266 stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
267 stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
268 stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
269 stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
270 "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
273 stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
274 stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
276 with open("tests/wf/submit_wf_packed.cwl") as f:
277 expect_packed_workflow = yaml.round_trip_load(f)
279 stubs.expect_container_spec = {
286 '/var/lib/cwl/workflow.json': {
287 'content': expect_packed_workflow,
291 'path': '/var/spool/cwl/cwl.output.json',
294 '/var/lib/cwl/cwl.input.json': {
298 'basename': '99999999999999999999999999999998+99',
299 'location': 'keep:99999999999999999999999999999998+99',
300 'class': 'Directory'},
302 'basename': u'blorp.txt',
304 'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
307 'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
308 {'basename': 'renamed.txt',
310 'location': 'keep:99999999999999999999999999999998+99/file1.txt',
319 'state': 'Committed',
320 'command': ['arvados-cwl-runner', '--local', '--api=containers',
321 '--no-log-timestamps', '--disable-validate', '--disable-color',
322 '--eval-timeout=20', '--thread-count=0',
323 '--enable-reuse', "--collection-cache-size=256",
324 '--output-name=Output from workflow '+wfname,
325 '--debug', '--on-error=continue',
326 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
328 'container_image': '999999999999999999999999999999d3+99',
329 'output_name': 'Output from workflow '+wfname,
330 'output_path': '/var/spool/cwl',
331 'cwd': '/var/spool/cwl',
332 'runtime_constraints': {
335 'ram': (1024+256)*1024*1024
337 'use_existing': False,
342 stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
343 stubs.api.workflows().create().execute.return_value = {
344 "uuid": stubs.expect_workflow_uuid,
346 def update_mock(**kwargs):
347 stubs.updated_uuid = kwargs.get('uuid')
349 stubs.api.workflows().update.side_effect = update_mock
350 stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
351 "uuid": stubs.updated_uuid,
354 return func(self, stubs, *args, **kwargs)
358 class TestSubmit(unittest.TestCase):
361 cwltool.process._names = set()
362 arvados_cwl.arvdocker.arv_docker_clear_cache()
365 root_logger = logging.getLogger('')
367 # Remove existing RuntimeStatusLoggingHandlers if they exist
368 handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)]
369 root_logger.handlers = handlers
371 @mock.patch("time.sleep")
373 def test_submit_invalid_runner_ram(self, stubs, tm):
374 exited = arvados_cwl.main(
375 ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
376 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
377 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
378 self.assertEqual(exited, 1)
382 def test_submit_container(self, stubs):
383 exited = arvados_cwl.main(
384 ["--submit", "--no-wait", "--api=containers", "--debug",
385 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
386 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
388 stubs.api.collections().create.assert_has_calls([
389 mock.call(body=JsonDiffMatcher({
391 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
392 'replication_desired': None,
393 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
394 }), ensure_unique_name=False),
395 mock.call(body=JsonDiffMatcher({
397 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
398 'replication_desired': None,
399 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
400 }), ensure_unique_name=False),
403 expect_container = copy.deepcopy(stubs.expect_container_spec)
404 stubs.api.container_requests().create.assert_called_with(
405 body=JsonDiffMatcher(expect_container))
406 self.assertEqual(stubs.capture_stdout.getvalue(),
407 stubs.expect_container_request_uuid + '\n')
408 self.assertEqual(exited, 0)
412 def test_submit_container_tool(self, stubs):
413 # test for issue #16139
414 exited = arvados_cwl.main(
415 ["--submit", "--no-wait", "--api=containers", "--debug",
416 "tests/tool/tool_with_sf.cwl", "tests/tool/tool_with_sf.yml"],
417 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
419 self.assertEqual(stubs.capture_stdout.getvalue(),
420 stubs.expect_container_request_uuid + '\n')
421 self.assertEqual(exited, 0)
424 def test_submit_container_no_reuse(self, stubs):
425 exited = arvados_cwl.main(
426 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
427 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
428 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
430 expect_container = copy.deepcopy(stubs.expect_container_spec)
431 expect_container["command"] = [
432 'arvados-cwl-runner', '--local', '--api=containers',
433 '--no-log-timestamps', '--disable-validate', '--disable-color',
434 '--eval-timeout=20', '--thread-count=0',
435 '--disable-reuse', "--collection-cache-size=256",
436 "--output-name=Output from workflow submit_wf.cwl",
437 '--debug', '--on-error=continue',
438 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
439 expect_container["use_existing"] = False
441 stubs.api.container_requests().create.assert_called_with(
442 body=JsonDiffMatcher(expect_container))
443 self.assertEqual(stubs.capture_stdout.getvalue(),
444 stubs.expect_container_request_uuid + '\n')
445 self.assertEqual(exited, 0)
447 @stubs('submit_wf_no_reuse.cwl')
448 def test_submit_container_reuse_disabled_by_workflow(self, stubs):
449 exited = arvados_cwl.main(
450 ["--submit", "--no-wait", "--api=containers", "--debug",
451 "tests/wf/submit_wf_no_reuse.cwl", "tests/submit_test_job.json"],
452 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
453 self.assertEqual(exited, 0)
455 expect_container = copy.deepcopy(stubs.expect_container_spec)
456 expect_container["command"] = [
457 'arvados-cwl-runner', '--local', '--api=containers',
458 '--no-log-timestamps', '--disable-validate', '--disable-color',
459 '--eval-timeout=20', '--thread-count=0',
460 '--disable-reuse', "--collection-cache-size=256",
461 '--output-name=Output from workflow submit_wf_no_reuse.cwl', '--debug', '--on-error=continue',
462 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
463 expect_container["use_existing"] = False
464 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
466 "class": "http://arvados.org/cwl#ReuseRequirement",
467 "enableReuse": False,
470 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = {
471 "arv": "http://arvados.org/cwl#",
472 "cwltool": "http://commonwl.org/cwltool#"
475 stubs.api.container_requests().create.assert_called_with(
476 body=JsonDiffMatcher(expect_container))
477 self.assertEqual(stubs.capture_stdout.getvalue(),
478 stubs.expect_container_request_uuid + '\n')
482 def test_submit_container_on_error(self, stubs):
483 exited = arvados_cwl.main(
484 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
485 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
486 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
488 expect_container = copy.deepcopy(stubs.expect_container_spec)
489 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
490 '--no-log-timestamps', '--disable-validate', '--disable-color',
491 '--eval-timeout=20', '--thread-count=0',
492 '--enable-reuse', "--collection-cache-size=256",
493 "--output-name=Output from workflow submit_wf.cwl",
494 '--debug', '--on-error=stop',
495 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
497 stubs.api.container_requests().create.assert_called_with(
498 body=JsonDiffMatcher(expect_container))
499 self.assertEqual(stubs.capture_stdout.getvalue(),
500 stubs.expect_container_request_uuid + '\n')
501 self.assertEqual(exited, 0)
504 def test_submit_container_output_name(self, stubs):
505 output_name = "test_output_name"
507 exited = arvados_cwl.main(
508 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
509 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
510 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
512 expect_container = copy.deepcopy(stubs.expect_container_spec)
513 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
514 '--no-log-timestamps', '--disable-validate', '--disable-color',
515 '--eval-timeout=20', '--thread-count=0',
516 '--enable-reuse', "--collection-cache-size=256",
517 "--output-name="+output_name, '--debug', '--on-error=continue',
518 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
519 expect_container["output_name"] = output_name
521 stubs.api.container_requests().create.assert_called_with(
522 body=JsonDiffMatcher(expect_container))
523 self.assertEqual(stubs.capture_stdout.getvalue(),
524 stubs.expect_container_request_uuid + '\n')
525 self.assertEqual(exited, 0)
528 def test_submit_storage_classes(self, stubs):
529 exited = arvados_cwl.main(
530 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo",
531 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
532 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
534 expect_container = copy.deepcopy(stubs.expect_container_spec)
535 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
536 '--no-log-timestamps', '--disable-validate', '--disable-color',
537 '--eval-timeout=20', '--thread-count=0',
538 '--enable-reuse', "--collection-cache-size=256",
539 '--output-name=Output from workflow submit_wf.cwl',
541 "--storage-classes=foo", '--on-error=continue',
542 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
544 stubs.api.container_requests().create.assert_called_with(
545 body=JsonDiffMatcher(expect_container))
546 self.assertEqual(stubs.capture_stdout.getvalue(),
547 stubs.expect_container_request_uuid + '\n')
548 self.assertEqual(exited, 0)
551 def test_submit_multiple_storage_classes(self, stubs):
552 exited = arvados_cwl.main(
553 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo,bar", "--intermediate-storage-classes=baz",
554 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
555 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
557 expect_container = copy.deepcopy(stubs.expect_container_spec)
558 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
559 '--no-log-timestamps', '--disable-validate', '--disable-color',
560 '--eval-timeout=20', '--thread-count=0',
561 '--enable-reuse', "--collection-cache-size=256",
562 "--output-name=Output from workflow submit_wf.cwl",
564 "--storage-classes=foo,bar", "--intermediate-storage-classes=baz", '--on-error=continue',
565 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
567 stubs.api.container_requests().create.assert_called_with(
568 body=JsonDiffMatcher(expect_container))
569 self.assertEqual(stubs.capture_stdout.getvalue(),
570 stubs.expect_container_request_uuid + '\n')
571 self.assertEqual(exited, 0)
573 @mock.patch("cwltool.task_queue.TaskQueue")
574 @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
575 @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection")
577 def test_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
578 final_output_c = arvados.collection.Collection()
579 make_output.return_value = ({},final_output_c)
581 def set_final_output(job_order, output_callback, runtimeContext):
582 output_callback({"out": "zzzzz"}, "success")
584 job.side_effect = set_final_output
586 exited = arvados_cwl.main(
587 ["--debug", "--local", "--storage-classes=foo",
588 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
589 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
591 make_output.assert_called_with(u'Output of submit_wf.cwl', ['foo'], '', {}, {"out": "zzzzz"})
592 self.assertEqual(exited, 0)
594 @mock.patch("cwltool.task_queue.TaskQueue")
595 @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
596 @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection")
598 def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
599 final_output_c = arvados.collection.Collection()
600 make_output.return_value = ({},final_output_c)
601 stubs.api.config().get.return_value = {"default": {"Default": True}}
603 def set_final_output(job_order, output_callback, runtimeContext):
604 output_callback({"out": "zzzzz"}, "success")
606 job.side_effect = set_final_output
608 exited = arvados_cwl.main(
609 ["--debug", "--local",
610 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
611 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
613 make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', {}, {"out": "zzzzz"})
614 self.assertEqual(exited, 0)
616 @mock.patch("cwltool.task_queue.TaskQueue")
617 @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
618 @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection")
620 def test_storage_class_hint_to_make_output_collection(self, stubs, make_output, job, tq):
621 final_output_c = arvados.collection.Collection()
622 make_output.return_value = ({},final_output_c)
624 def set_final_output(job_order, output_callback, runtimeContext):
625 output_callback({"out": "zzzzz"}, "success")
627 job.side_effect = set_final_output
629 exited = arvados_cwl.main(
630 ["--debug", "--local",
631 "tests/wf/submit_storage_class_wf.cwl", "tests/submit_test_job.json"],
632 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
634 make_output.assert_called_with(u'Output of submit_storage_class_wf.cwl', ['foo', 'bar'], '', {}, {"out": "zzzzz"})
635 self.assertEqual(exited, 0)
638 def test_submit_container_output_ttl(self, stubs):
639 exited = arvados_cwl.main(
640 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
641 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
642 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
644 expect_container = copy.deepcopy(stubs.expect_container_spec)
645 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
646 '--no-log-timestamps', '--disable-validate', '--disable-color',
647 '--eval-timeout=20', '--thread-count=0',
648 '--enable-reuse', "--collection-cache-size=256",
649 "--output-name=Output from workflow submit_wf.cwl", '--debug',
650 '--on-error=continue',
651 "--intermediate-output-ttl=3600",
652 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
654 stubs.api.container_requests().create.assert_called_with(
655 body=JsonDiffMatcher(expect_container))
656 self.assertEqual(stubs.capture_stdout.getvalue(),
657 stubs.expect_container_request_uuid + '\n')
658 self.assertEqual(exited, 0)
661 def test_submit_container_trash_intermediate(self, stubs):
662 exited = arvados_cwl.main(
663 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
664 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
665 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
668 expect_container = copy.deepcopy(stubs.expect_container_spec)
669 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
670 '--no-log-timestamps', '--disable-validate', '--disable-color',
671 '--eval-timeout=20', '--thread-count=0',
672 '--enable-reuse', "--collection-cache-size=256",
673 '--debug', '--on-error=continue',
674 "--trash-intermediate",
675 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
677 stubs.api.container_requests().create.assert_called_with(
678 body=JsonDiffMatcher(expect_container))
679 self.assertEqual(stubs.capture_stdout.getvalue(),
680 stubs.expect_container_request_uuid + '\n')
681 self.assertEqual(exited, 0)
684 def test_submit_container_output_tags(self, stubs):
685 output_tags = "tag0,tag1,tag2"
687 exited = arvados_cwl.main(
688 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
689 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
690 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
692 expect_container = copy.deepcopy(stubs.expect_container_spec)
693 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
694 '--no-log-timestamps', '--disable-validate', '--disable-color',
695 '--eval-timeout=20', '--thread-count=0',
696 '--enable-reuse', "--collection-cache-size=256",
697 "--output-name=Output from workflow submit_wf.cwl",
698 "--output-tags="+output_tags, '--debug', '--on-error=continue',
699 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
701 stubs.api.container_requests().create.assert_called_with(
702 body=JsonDiffMatcher(expect_container))
703 self.assertEqual(stubs.capture_stdout.getvalue(),
704 stubs.expect_container_request_uuid + '\n')
705 self.assertEqual(exited, 0)
708 def test_submit_container_runner_ram(self, stubs):
709 exited = arvados_cwl.main(
710 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
711 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
712 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
714 expect_container = copy.deepcopy(stubs.expect_container_spec)
715 expect_container["runtime_constraints"]["ram"] = (2048+256)*1024*1024
717 stubs.api.container_requests().create.assert_called_with(
718 body=JsonDiffMatcher(expect_container))
719 self.assertEqual(stubs.capture_stdout.getvalue(),
720 stubs.expect_container_request_uuid + '\n')
721 self.assertEqual(exited, 0)
723 @mock.patch("arvados.collection.CollectionReader")
724 @mock.patch("time.sleep")
726 def test_submit_file_keepref(self, stubs, tm, collectionReader):
727 collectionReader().exists.return_value = True
728 collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "blorp.txt")
729 exited = arvados_cwl.main(
730 ["--submit", "--no-wait", "--api=containers", "--debug",
731 "tests/wf/submit_keepref_wf.cwl"],
732 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
733 self.assertEqual(exited, 0)
735 @mock.patch("arvados.collection.CollectionReader")
736 @mock.patch("time.sleep")
738 def test_submit_keepref(self, stubs, tm, reader):
739 with open("tests/wf/expect_arvworkflow.cwl") as f:
740 reader().open().__enter__().read.return_value = f.read()
742 exited = arvados_cwl.main(
743 ["--submit", "--no-wait", "--api=containers", "--debug",
744 "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
745 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
755 'path': '/var/spool/cwl/cwl.output.json',
758 '/var/lib/cwl/workflow': {
759 'portable_data_hash': '99999999999999999999999999999994+99',
762 '/var/lib/cwl/cwl.input.json': {
768 }, 'state': 'Committed',
769 'output_path': '/var/spool/cwl',
770 'name': 'expect_arvworkflow.cwl#main',
771 'output_name': 'Output from workflow expect_arvworkflow.cwl#main',
772 'container_image': '999999999999999999999999999999d3+99',
773 'command': ['arvados-cwl-runner', '--local', '--api=containers',
774 '--no-log-timestamps', '--disable-validate', '--disable-color',
775 '--eval-timeout=20', '--thread-count=0',
776 '--enable-reuse', "--collection-cache-size=256",
777 '--output-name=Output from workflow expect_arvworkflow.cwl#main',
778 '--debug', '--on-error=continue',
779 '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
780 'cwd': '/var/spool/cwl',
781 'runtime_constraints': {
786 'use_existing': False,
791 stubs.api.container_requests().create.assert_called_with(
792 body=JsonDiffMatcher(expect_container))
793 self.assertEqual(stubs.capture_stdout.getvalue(),
794 stubs.expect_container_request_uuid + '\n')
795 self.assertEqual(exited, 0)
797 @mock.patch("time.sleep")
799 def test_submit_arvworkflow(self, stubs, tm):
800 with open("tests/wf/expect_arvworkflow.cwl") as f:
801 stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
803 exited = arvados_cwl.main(
804 ["--submit", "--no-wait", "--api=containers", "--debug",
805 "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
806 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
816 'path': '/var/spool/cwl/cwl.output.json',
819 '/var/lib/cwl/workflow.json': {
822 'cwlVersion': 'v1.0',
827 {'type': 'string', 'id': '#main/x'}
830 {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
831 'run': '#submit_tool.cwl',
841 'inputBinding': {'position': 1},
843 'id': '#submit_tool.cwl/x'}
847 'dockerPull': 'debian:buster-slim',
848 'class': 'DockerRequirement',
849 "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
852 'id': '#submit_tool.cwl',
854 'baseCommand': 'cat',
855 'class': 'CommandLineTool'
860 '/var/lib/cwl/cwl.input.json': {
866 }, 'state': 'Committed',
867 'output_path': '/var/spool/cwl',
868 'name': 'a test workflow',
869 'container_image': "999999999999999999999999999999d3+99",
870 'command': ['arvados-cwl-runner', '--local', '--api=containers',
871 '--no-log-timestamps', '--disable-validate', '--disable-color',
872 '--eval-timeout=20', '--thread-count=0',
873 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
874 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
875 'cwd': '/var/spool/cwl',
876 'runtime_constraints': {
881 'use_existing': False,
883 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
888 stubs.api.container_requests().create.assert_called_with(
889 body=JsonDiffMatcher(expect_container))
890 self.assertEqual(stubs.capture_stdout.getvalue(),
891 stubs.expect_container_request_uuid + '\n')
892 self.assertEqual(exited, 0)
894 @stubs('hello container 123')
895 def test_submit_container_name(self, stubs):
896 exited = arvados_cwl.main(
897 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
898 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
899 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
901 expect_container = copy.deepcopy(stubs.expect_container_spec)
903 stubs.api.container_requests().create.assert_called_with(
904 body=JsonDiffMatcher(expect_container))
905 self.assertEqual(stubs.capture_stdout.getvalue(),
906 stubs.expect_container_request_uuid + '\n')
907 self.assertEqual(exited, 0)
910 def test_submit_missing_input(self, stubs):
911 exited = arvados_cwl.main(
912 ["--submit", "--no-wait", "--api=containers", "--debug",
913 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
914 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
915 self.assertEqual(exited, 0)
917 exited = arvados_cwl.main(
918 ["--submit", "--no-wait", "--api=containers", "--debug",
919 "tests/wf/submit_wf.cwl", "tests/submit_test_job_missing.json"],
920 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
921 self.assertEqual(exited, 1)
924 def test_submit_container_project(self, stubs):
925 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
926 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
927 exited = arvados_cwl.main(
928 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
929 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
930 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
932 expect_container = copy.deepcopy(stubs.expect_container_spec)
933 expect_container["owner_uuid"] = project_uuid
934 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
935 '--no-log-timestamps', '--disable-validate', '--disable-color',
936 "--eval-timeout=20", "--thread-count=0",
937 '--enable-reuse', "--collection-cache-size=256",
938 "--output-name=Output from workflow submit_wf.cwl", '--debug',
939 '--on-error=continue',
940 '--project-uuid='+project_uuid,
941 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
943 stubs.api.container_requests().create.assert_called_with(
944 body=JsonDiffMatcher(expect_container))
945 self.assertEqual(stubs.capture_stdout.getvalue(),
946 stubs.expect_container_request_uuid + '\n')
947 self.assertEqual(exited, 0)
950 def test_submit_container_eval_timeout(self, stubs):
951 exited = arvados_cwl.main(
952 ["--submit", "--no-wait", "--api=containers", "--debug", "--eval-timeout=60",
953 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
954 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
956 expect_container = copy.deepcopy(stubs.expect_container_spec)
957 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
958 '--no-log-timestamps', '--disable-validate', '--disable-color',
959 '--eval-timeout=60.0', '--thread-count=0',
960 '--enable-reuse', "--collection-cache-size=256",
961 '--debug', '--on-error=continue',
962 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
964 stubs.api.container_requests().create.assert_called_with(
965 body=JsonDiffMatcher(expect_container))
966 self.assertEqual(stubs.capture_stdout.getvalue(),
967 stubs.expect_container_request_uuid + '\n')
968 self.assertEqual(exited, 0)
971 def test_submit_container_collection_cache(self, stubs):
972 exited = arvados_cwl.main(
973 ["--submit", "--no-wait", "--api=containers", "--debug", "--collection-cache-size=500",
974 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
975 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
977 expect_container = copy.deepcopy(stubs.expect_container_spec)
978 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
979 '--no-log-timestamps', '--disable-validate', '--disable-color',
980 '--eval-timeout=20', '--thread-count=0',
981 '--enable-reuse', "--collection-cache-size=500",
982 '--debug', '--on-error=continue',
983 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
984 expect_container["runtime_constraints"]["ram"] = (1024+500)*1024*1024
986 stubs.api.container_requests().create.assert_called_with(
987 body=JsonDiffMatcher(expect_container))
988 self.assertEqual(stubs.capture_stdout.getvalue(),
989 stubs.expect_container_request_uuid + '\n')
990 self.assertEqual(exited, 0)
993 def test_submit_container_thread_count(self, stubs):
994 exited = arvados_cwl.main(
995 ["--submit", "--no-wait", "--api=containers", "--debug", "--thread-count=20",
996 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
997 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
999 expect_container = copy.deepcopy(stubs.expect_container_spec)
1000 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1001 '--no-log-timestamps', '--disable-validate', '--disable-color',
1002 '--eval-timeout=20', '--thread-count=20',
1003 '--enable-reuse', "--collection-cache-size=256",
1004 '--debug', '--on-error=continue',
1005 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1007 stubs.api.container_requests().create.assert_called_with(
1008 body=JsonDiffMatcher(expect_container))
1009 self.assertEqual(stubs.capture_stdout.getvalue(),
1010 stubs.expect_container_request_uuid + '\n')
1011 self.assertEqual(exited, 0)
1014 def test_submit_container_runner_image(self, stubs):
1015 exited = arvados_cwl.main(
1016 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
1017 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1018 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1020 stubs.expect_container_spec["container_image"] = "999999999999999999999999999999d5+99"
1022 expect_container = copy.deepcopy(stubs.expect_container_spec)
1023 stubs.api.container_requests().create.assert_called_with(
1024 body=JsonDiffMatcher(expect_container))
1025 self.assertEqual(stubs.capture_stdout.getvalue(),
1026 stubs.expect_container_request_uuid + '\n')
1027 self.assertEqual(exited, 0)
1030 def test_submit_priority(self, stubs):
1031 exited = arvados_cwl.main(
1032 ["--submit", "--no-wait", "--api=containers", "--debug", "--priority=669",
1033 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1034 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1036 stubs.expect_container_spec["priority"] = 669
1038 expect_container = copy.deepcopy(stubs.expect_container_spec)
1039 stubs.api.container_requests().create.assert_called_with(
1040 body=JsonDiffMatcher(expect_container))
1041 self.assertEqual(stubs.capture_stdout.getvalue(),
1042 stubs.expect_container_request_uuid + '\n')
1043 self.assertEqual(exited, 0)
1045 @stubs('submit_wf_runner_resources.cwl')
1046 def test_submit_wf_runner_resources(self, stubs):
1047 exited = arvados_cwl.main(
1048 ["--submit", "--no-wait", "--api=containers", "--debug",
1049 "tests/wf/submit_wf_runner_resources.cwl", "tests/submit_test_job.json"],
1050 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1052 expect_container = copy.deepcopy(stubs.expect_container_spec)
1053 expect_container["runtime_constraints"] = {
1056 "ram": (2000+512) * 2**20
1058 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1060 "class": "http://arvados.org/cwl#WorkflowRunnerResources",
1066 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = {
1067 "arv": "http://arvados.org/cwl#",
1069 expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1070 '--no-log-timestamps', '--disable-validate', '--disable-color',
1071 '--eval-timeout=20', '--thread-count=0',
1072 '--enable-reuse', "--collection-cache-size=512",
1073 '--output-name=Output from workflow submit_wf_runner_resources.cwl',
1074 '--debug', '--on-error=continue',
1075 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1077 stubs.api.container_requests().create.assert_called_with(
1078 body=JsonDiffMatcher(expect_container))
1079 self.assertEqual(stubs.capture_stdout.getvalue(),
1080 stubs.expect_container_request_uuid + '\n')
1081 self.assertEqual(exited, 0)
1083 @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
1084 @mock.patch("cwltool.docker.DockerCommandLineJob.get_image")
1085 @mock.patch("arvados.api")
1086 def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
1087 arvados_cwl.arvdocker.arv_docker_clear_cache()
1089 arvrunner = mock.MagicMock()
1090 arvrunner.project_uuid = ""
1091 api.return_value = mock.MagicMock()
1092 arvrunner.api = api.return_value
1093 arvrunner.runtimeContext.match_local_docker = False
1094 arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
1095 "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1096 "link_class": "docker_image_repo+tag",
1097 "name": "arvados/jobs:"+arvados_cwl.__version__,
1099 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1100 {"items": [{"created_at": "",
1102 "link_class": "docker_image_hash",
1105 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1106 {"items": [{"created_at": "",
1107 "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1108 "link_class": "docker_image_repo+tag",
1109 "name": "arvados/jobs:"+arvados_cwl.__version__,
1111 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1112 {"items": [{"created_at": "",
1114 "link_class": "docker_image_hash",
1117 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
1119 find_one_image_hash.return_value = "123456"
1121 arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1123 "manifest_text": "",
1125 }], "items_available": 1, "offset": 0},
1126 {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1128 "manifest_text": "",
1130 }], "items_available": 1, "offset": 0})
1131 arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
1132 arvrunner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1133 "portable_data_hash": "9999999999999999999999999999999b+99"}
1135 self.assertEqual("9999999999999999999999999999999b+99",
1136 arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__, arvrunner.runtimeContext))
1140 def test_submit_secrets(self, stubs):
1141 exited = arvados_cwl.main(
1142 ["--submit", "--no-wait", "--api=containers", "--debug",
1143 "tests/wf/secret_wf.cwl", "tests/secret_test_job.yml"],
1144 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1146 expect_container = {
1148 "arvados-cwl-runner",
1151 "--no-log-timestamps",
1152 "--disable-validate",
1154 "--eval-timeout=20",
1157 "--collection-cache-size=256",
1158 '--output-name=Output from workflow secret_wf.cwl'
1160 "--on-error=continue",
1161 "/var/lib/cwl/workflow.json#main",
1162 "/var/lib/cwl/cwl.input.json"
1164 "container_image": "999999999999999999999999999999d3+99",
1165 "cwd": "/var/spool/cwl",
1167 "/var/lib/cwl/cwl.input.json": {
1170 "$include": "/secrets/s0"
1175 "/var/lib/cwl/workflow.json": {
1183 "class": "CommandLineTool",
1186 "class": "http://commonwl.org/cwltool#Secrets",
1188 "#secret_job.cwl/pw"
1192 "id": "#secret_job.cwl",
1195 "id": "#secret_job.cwl/pw",
1201 "id": "#secret_job.cwl/out",
1204 "glob": "hashed_example.txt"
1208 "stdout": "hashed_example.txt",
1211 "class": "InitialWorkDirRequirement",
1214 "entry": "username: user\npassword: $(inputs.pw)\n",
1215 "entryname": "example.conf"
1222 "class": "Workflow",
1225 "class": "DockerRequirement",
1226 "dockerPull": "debian:buster-slim",
1227 "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
1230 "class": "http://commonwl.org/cwltool#Secrets",
1246 "outputSource": "#main/step1/out",
1252 "id": "#main/step1",
1255 "id": "#main/step1/pw",
1256 "source": "#main/pw"
1262 "run": "#secret_job.cwl"
1268 "cwltool": "http://commonwl.org/cwltool#"
1270 "cwlVersion": "v1.0"
1275 "kind": "collection",
1280 "path": "/var/spool/cwl/cwl.output.json"
1283 "name": "secret_wf.cwl",
1284 "output_name": "Output from workflow secret_wf.cwl",
1285 "output_path": "/var/spool/cwl",
1288 "runtime_constraints": {
1299 "state": "Committed",
1300 "use_existing": False
1303 stubs.api.container_requests().create.assert_called_with(
1304 body=JsonDiffMatcher(expect_container))
1305 self.assertEqual(stubs.capture_stdout.getvalue(),
1306 stubs.expect_container_request_uuid + '\n')
1307 self.assertEqual(exited, 0)
1310 def test_submit_request_uuid(self, stubs):
1311 stubs.api._rootDesc["remoteHosts"]["zzzzz"] = "123"
1312 stubs.expect_container_request_uuid = "zzzzz-xvhdp-yyyyyyyyyyyyyyy"
1314 stubs.api.container_requests().update().execute.return_value = {
1315 "uuid": stubs.expect_container_request_uuid,
1316 "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
1320 exited = arvados_cwl.main(
1321 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-request-uuid=zzzzz-xvhdp-yyyyyyyyyyyyyyy",
1322 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1323 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1325 stubs.api.container_requests().update.assert_called_with(
1326 uuid="zzzzz-xvhdp-yyyyyyyyyyyyyyy", body=JsonDiffMatcher(stubs.expect_container_spec))
1327 self.assertEqual(stubs.capture_stdout.getvalue(),
1328 stubs.expect_container_request_uuid + '\n')
1329 self.assertEqual(exited, 0)
1332 def test_submit_container_cluster_id(self, stubs):
1333 stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1335 exited = arvados_cwl.main(
1336 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zbbbb",
1337 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1338 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1340 expect_container = copy.deepcopy(stubs.expect_container_spec)
1342 stubs.api.container_requests().create.assert_called_with(
1343 body=JsonDiffMatcher(expect_container), cluster_id="zbbbb")
1344 self.assertEqual(stubs.capture_stdout.getvalue(),
1345 stubs.expect_container_request_uuid + '\n')
1346 self.assertEqual(exited, 0)
1349 def test_submit_validate_cluster_id(self, stubs):
1350 stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1351 exited = arvados_cwl.main(
1352 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zcccc",
1353 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1354 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1355 self.assertEqual(exited, 1)
1358 def test_submit_validate_project_uuid(self, stubs):
1359 # Fails with bad cluster prefix
1360 exited = arvados_cwl.main(
1361 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzb-j7d0g-zzzzzzzzzzzzzzz",
1362 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1363 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1364 self.assertEqual(exited, 1)
1366 # Project lookup fails
1367 stubs.api.groups().get().execute.side_effect = Exception("Bad project")
1368 exited = arvados_cwl.main(
1369 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzz-j7d0g-zzzzzzzzzzzzzzx",
1370 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1371 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1372 self.assertEqual(exited, 1)
1374 # It should work this time because it is looking up a user (and only group is stubbed out to fail)
1375 exited = arvados_cwl.main(
1376 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzz-tpzed-zzzzzzzzzzzzzzx",
1377 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1378 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1379 self.assertEqual(exited, 0)
1382 @mock.patch("arvados.collection.CollectionReader")
1384 def test_submit_uuid_inputs(self, stubs, collectionReader):
1385 collectionReader().exists.return_value = True
1386 collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "file1.txt")
1387 def list_side_effect(**kwargs):
1388 m = mock.MagicMock()
1389 if "count" in kwargs:
1390 m.execute.return_value = {"items": [
1391 {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz", "portable_data_hash": "99999999999999999999999999999998+99"}
1394 m.execute.return_value = {"items": []}
1396 stubs.api.collections().list.side_effect = list_side_effect
1398 exited = arvados_cwl.main(
1399 ["--submit", "--no-wait", "--api=containers", "--debug",
1400 "tests/wf/submit_wf.cwl", "tests/submit_test_job_with_uuids.json"],
1401 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1403 expect_container = copy.deepcopy(stubs.expect_container_spec)
1404 expect_container['mounts']['/var/lib/cwl/cwl.input.json']['content']['y']['basename'] = 'zzzzz-4zz18-zzzzzzzzzzzzzzz'
1405 expect_container['mounts']['/var/lib/cwl/cwl.input.json']['content']['y']['http://arvados.org/cwl#collectionUUID'] = 'zzzzz-4zz18-zzzzzzzzzzzzzzz'
1406 expect_container['mounts']['/var/lib/cwl/cwl.input.json']['content']['z']['listing'][0]['http://arvados.org/cwl#collectionUUID'] = 'zzzzz-4zz18-zzzzzzzzzzzzzzz'
1408 stubs.api.collections().list.assert_has_calls([
1409 mock.call(count='none',
1410 filters=[['uuid', 'in', ['zzzzz-4zz18-zzzzzzzzzzzzzzz']]],
1411 select=['uuid', 'portable_data_hash'])])
1412 stubs.api.container_requests().create.assert_called_with(
1413 body=JsonDiffMatcher(expect_container))
1414 self.assertEqual(stubs.capture_stdout.getvalue(),
1415 stubs.expect_container_request_uuid + '\n')
1416 self.assertEqual(exited, 0)
1419 def test_submit_mismatched_uuid_inputs(self, stubs):
1420 def list_side_effect(**kwargs):
1421 m = mock.MagicMock()
1422 if "count" in kwargs:
1423 m.execute.return_value = {"items": [
1424 {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzz", "portable_data_hash": "99999999999999999999999999999997+99"}
1427 m.execute.return_value = {"items": []}
1429 stubs.api.collections().list.side_effect = list_side_effect
1431 for infile in ("tests/submit_test_job_with_mismatched_uuids.json", "tests/submit_test_job_with_inconsistent_uuids.json"):
1432 capture_stderr = StringIO()
1433 cwltool_logger = logging.getLogger('cwltool')
1434 stderr_logger = logging.StreamHandler(capture_stderr)
1435 cwltool_logger.addHandler(stderr_logger)
1438 exited = arvados_cwl.main(
1439 ["--submit", "--no-wait", "--api=containers", "--debug",
1440 "tests/wf/submit_wf.cwl", infile],
1441 stubs.capture_stdout, capture_stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1443 self.assertEqual(exited, 1)
1445 re.sub(r'[ \n]+', ' ', capture_stderr.getvalue()),
1446 r"Expected collection uuid zzzzz-4zz18-zzzzzzzzzzzzzzz to be 99999999999999999999999999999998\+99 but API server reported 99999999999999999999999999999997\+99")
1448 cwltool_logger.removeHandler(stderr_logger)
1450 @mock.patch("arvados.collection.CollectionReader")
1452 def test_submit_unknown_uuid_inputs(self, stubs, collectionReader):
1453 collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "file1.txt")
1454 capture_stderr = StringIO()
1456 cwltool_logger = logging.getLogger('cwltool')
1457 stderr_logger = logging.StreamHandler(capture_stderr)
1458 cwltool_logger.addHandler(stderr_logger)
1460 exited = arvados_cwl.main(
1461 ["--submit", "--no-wait", "--api=containers", "--debug",
1462 "tests/wf/submit_wf.cwl", "tests/submit_test_job_with_uuids.json"],
1463 stubs.capture_stdout, capture_stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1466 self.assertEqual(exited, 1)
1468 capture_stderr.getvalue(),
1469 r"Collection\s*uuid\s*zzzzz-4zz18-zzzzzzzzzzzzzzz\s*not\s*found")
1471 cwltool_logger.removeHandler(stderr_logger)
1473 @stubs('submit_wf_process_properties.cwl')
1474 def test_submit_set_process_properties(self, stubs):
1475 exited = arvados_cwl.main(
1476 ["--submit", "--no-wait", "--api=containers", "--debug",
1477 "tests/wf/submit_wf_process_properties.cwl", "tests/submit_test_job.json"],
1478 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1480 expect_container = copy.deepcopy(stubs.expect_container_spec)
1482 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1484 "class": "http://arvados.org/cwl#ProcessProperties",
1485 "processProperties": [
1486 {"propertyName": "baz",
1487 "propertyValue": "$(inputs.x.basename)"},
1488 {"propertyName": "foo",
1489 "propertyValue": "bar"},
1490 {"propertyName": "quux",
1499 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = {
1500 "arv": "http://arvados.org/cwl#"
1503 expect_container["properties"] = {
1512 stubs.api.container_requests().create.assert_called_with(
1513 body=JsonDiffMatcher(expect_container))
1514 self.assertEqual(stubs.capture_stdout.getvalue(),
1515 stubs.expect_container_request_uuid + '\n')
1516 self.assertEqual(exited, 0)
1520 def test_submit_enable_preemptible(self, stubs):
1521 exited = arvados_cwl.main(
1522 ["--submit", "--no-wait", "--api=containers", "--debug", "--enable-preemptible",
1523 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1524 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1526 expect_container = copy.deepcopy(stubs.expect_container_spec)
1527 expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1528 '--no-log-timestamps', '--disable-validate', '--disable-color',
1529 '--eval-timeout=20', '--thread-count=0',
1530 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
1531 '--enable-preemptible',
1532 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1534 stubs.api.container_requests().create.assert_called_with(
1535 body=JsonDiffMatcher(expect_container))
1536 self.assertEqual(stubs.capture_stdout.getvalue(),
1537 stubs.expect_container_request_uuid + '\n')
1538 self.assertEqual(exited, 0)
1541 def test_submit_disable_preemptible(self, stubs):
1542 exited = arvados_cwl.main(
1543 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-preemptible",
1544 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1545 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1547 expect_container = copy.deepcopy(stubs.expect_container_spec)
1548 expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1549 '--no-log-timestamps', '--disable-validate', '--disable-color',
1550 '--eval-timeout=20', '--thread-count=0',
1551 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
1552 '--disable-preemptible',
1553 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1555 stubs.api.container_requests().create.assert_called_with(
1556 body=JsonDiffMatcher(expect_container))
1557 self.assertEqual(stubs.capture_stdout.getvalue(),
1558 stubs.expect_container_request_uuid + '\n')
1559 self.assertEqual(exited, 0)
1562 class TestCreateWorkflow(unittest.TestCase):
1563 existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1564 expect_workflow = StripYAMLComments(
1565 open("tests/wf/expect_upload_packed.cwl").read().rstrip())
1568 cwltool.process._names = set()
1569 arvados_cwl.arvdocker.arv_docker_clear_cache()
1572 root_logger = logging.getLogger('')
1574 # Remove existing RuntimeStatusLoggingHandlers if they exist
1575 handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)]
1576 root_logger.handlers = handlers
1579 def test_create(self, stubs):
1580 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1581 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
1583 exited = arvados_cwl.main(
1584 ["--create-workflow", "--debug",
1586 "--project-uuid", project_uuid,
1587 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1588 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1590 stubs.api.pipeline_templates().create.refute_called()
1591 stubs.api.container_requests().create.refute_called()
1595 "owner_uuid": project_uuid,
1596 "name": "submit_wf.cwl",
1598 "definition": self.expect_workflow,
1601 stubs.api.workflows().create.assert_called_with(
1602 body=JsonDiffMatcher(body))
1604 self.assertEqual(stubs.capture_stdout.getvalue(),
1605 stubs.expect_workflow_uuid + '\n')
1606 self.assertEqual(exited, 0)
1609 def test_create_name(self, stubs):
1610 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1611 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
1613 exited = arvados_cwl.main(
1614 ["--create-workflow", "--debug",
1616 "--project-uuid", project_uuid,
1617 "--name", "testing 123",
1618 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1619 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1621 stubs.api.pipeline_templates().create.refute_called()
1622 stubs.api.container_requests().create.refute_called()
1626 "owner_uuid": project_uuid,
1627 "name": "testing 123",
1629 "definition": self.expect_workflow,
1632 stubs.api.workflows().create.assert_called_with(
1633 body=JsonDiffMatcher(body))
1635 self.assertEqual(stubs.capture_stdout.getvalue(),
1636 stubs.expect_workflow_uuid + '\n')
1637 self.assertEqual(exited, 0)
1641 def test_update(self, stubs):
1642 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1643 stubs.api.workflows().get().execute.return_value = {"owner_uuid": project_uuid}
1645 exited = arvados_cwl.main(
1646 ["--update-workflow", self.existing_workflow_uuid,
1648 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1649 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1653 "name": "submit_wf.cwl",
1655 "definition": self.expect_workflow,
1656 "owner_uuid": project_uuid
1659 stubs.api.workflows().update.assert_called_with(
1660 uuid=self.existing_workflow_uuid,
1661 body=JsonDiffMatcher(body))
1662 self.assertEqual(stubs.capture_stdout.getvalue(),
1663 self.existing_workflow_uuid + '\n')
1664 self.assertEqual(exited, 0)
1668 def test_update_name(self, stubs):
1669 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1670 stubs.api.workflows().get().execute.return_value = {"owner_uuid": project_uuid}
1672 exited = arvados_cwl.main(
1673 ["--update-workflow", self.existing_workflow_uuid,
1674 "--debug", "--name", "testing 123",
1675 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1676 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1680 "name": "testing 123",
1682 "definition": self.expect_workflow,
1683 "owner_uuid": project_uuid
1686 stubs.api.workflows().update.assert_called_with(
1687 uuid=self.existing_workflow_uuid,
1688 body=JsonDiffMatcher(body))
1689 self.assertEqual(stubs.capture_stdout.getvalue(),
1690 self.existing_workflow_uuid + '\n')
1691 self.assertEqual(exited, 0)
1694 def test_create_collection_per_tool(self, stubs):
1695 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1696 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
1698 exited = arvados_cwl.main(
1699 ["--create-workflow", "--debug",
1701 "--project-uuid", project_uuid,
1702 "tests/collection_per_tool/collection_per_tool.cwl"],
1703 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1705 toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1706 expect_workflow = StripYAMLComments(open(toolfile).read().rstrip())
1710 "owner_uuid": project_uuid,
1711 "name": "collection_per_tool.cwl",
1713 "definition": expect_workflow,
1716 stubs.api.workflows().create.assert_called_with(
1717 body=JsonDiffMatcher(body))
1719 self.assertEqual(stubs.capture_stdout.getvalue(),
1720 stubs.expect_workflow_uuid + '\n')
1721 self.assertEqual(exited, 0)
1724 def test_create_with_imports(self, stubs):
1725 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1726 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
1728 exited = arvados_cwl.main(
1729 ["--create-workflow", "--debug",
1731 "--project-uuid", project_uuid,
1732 "tests/wf/feddemo/feddemo.cwl"],
1733 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1735 stubs.api.pipeline_templates().create.refute_called()
1736 stubs.api.container_requests().create.refute_called()
1738 self.assertEqual(stubs.capture_stdout.getvalue(),
1739 stubs.expect_workflow_uuid + '\n')
1740 self.assertEqual(exited, 0)
1743 def test_create_with_no_input(self, stubs):
1744 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1745 stubs.api.groups().get().execute.return_value = {"group_class": "project"}
1747 exited = arvados_cwl.main(
1748 ["--create-workflow", "--debug",
1750 "--project-uuid", project_uuid,
1751 "tests/wf/revsort/revsort.cwl"],
1752 stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1754 stubs.api.pipeline_templates().create.refute_called()
1755 stubs.api.container_requests().create.refute_called()
1757 self.assertEqual(stubs.capture_stdout.getvalue(),
1758 stubs.expect_workflow_uuid + '\n')
1759 self.assertEqual(exited, 0)