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
20 import arvados.collection
22 import arvados_cwl.executor
23 import arvados_cwl.runner
26 from .matcher import JsonDiffMatcher, StripYAMLComments
27 from .mock_discovery import get_rootDesc
29 import ruamel.yaml as yaml
34 @functools.wraps(func)
35 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
36 @mock.patch("arvados.collection.KeepClient")
37 @mock.patch("arvados.keep.KeepClient")
38 @mock.patch("arvados.events.subscribe")
39 def wrapped(self, events, keep_client1, keep_client2, keepdocker, *args, **kwargs):
44 stubs.keepdocker = keepdocker
46 def putstub(p, **kwargs):
47 return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p))
48 keep_client1().put.side_effect = putstub
49 keep_client1.put.side_effect = putstub
50 keep_client2().put.side_effect = putstub
51 keep_client2.put.side_effect = putstub
53 stubs.keep_client = keep_client2
54 stubs.docker_images = {
55 "arvados/jobs:"+arvados_cwl.__version__: [("zzzzz-4zz18-zzzzzzzzzzzzzd3", "")],
56 "debian:8": [("zzzzz-4zz18-zzzzzzzzzzzzzd4", "")],
57 "arvados/jobs:123": [("zzzzz-4zz18-zzzzzzzzzzzzzd5", "")],
58 "arvados/jobs:latest": [("zzzzz-4zz18-zzzzzzzzzzzzzd6", "")],
60 def kd(a, b, image_name=None, image_tag=None):
61 return stubs.docker_images.get("%s:%s" % (image_name, image_tag), [])
62 stubs.keepdocker.side_effect = kd
64 stubs.fake_user_uuid = "zzzzz-tpzed-zzzzzzzzzzzzzzz"
65 stubs.fake_container_uuid = "zzzzz-dz642-zzzzzzzzzzzzzzz"
67 stubs.api = mock.MagicMock()
68 stubs.api._rootDesc = get_rootDesc()
70 stubs.api.users().current().execute.return_value = {
71 "uuid": stubs.fake_user_uuid,
73 stubs.api.collections().list().execute.return_value = {"items": []}
74 stubs.api.containers().current().execute.return_value = {
75 "uuid": stubs.fake_container_uuid,
78 class CollectionExecute(object):
79 def __init__(self, exe):
81 def execute(self, num_retries=None):
84 def collection_createstub(created_collections, body, ensure_unique_name=None):
85 mt = body["manifest_text"]
86 uuid = "zzzzz-4zz18-zzzzzzzzzzzzzx%d" % len(created_collections)
87 pdh = "%s+%i" % (hashlib.md5(mt).hexdigest(), len(mt))
88 created_collections[uuid] = {
90 "portable_data_hash": pdh,
93 return CollectionExecute(created_collections[uuid])
95 def collection_getstub(created_collections, uuid):
96 for v in created_collections.values():
97 if uuid in (v["uuid"], v["portable_data_hash"]):
98 return CollectionExecute(v)
100 created_collections = {
101 "99999999999999999999999999999998+99": {
103 "portable_data_hash": "99999999999999999999999999999998+99",
104 "manifest_text": ". 99999999999999999999999999999998+99 0:0:file1.txt"
106 "99999999999999999999999999999994+99": {
108 "portable_data_hash": "99999999999999999999999999999994+99",
109 "manifest_text": ". 99999999999999999999999999999994+99 0:0:expect_arvworkflow.cwl"
111 "zzzzz-4zz18-zzzzzzzzzzzzzd3": {
112 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd3",
113 "portable_data_hash": "999999999999999999999999999999d3+99",
116 "zzzzz-4zz18-zzzzzzzzzzzzzd4": {
117 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd4",
118 "portable_data_hash": "999999999999999999999999999999d4+99",
121 "zzzzz-4zz18-zzzzzzzzzzzzzd5": {
122 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd5",
123 "portable_data_hash": "999999999999999999999999999999d5+99",
126 "zzzzz-4zz18-zzzzzzzzzzzzzd6": {
127 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd6",
128 "portable_data_hash": "999999999999999999999999999999d6+99",
132 stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
133 stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
135 stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
136 stubs.api.jobs().create().execute.return_value = {
137 "uuid": stubs.expect_job_uuid,
141 stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
142 stubs.api.container_requests().create().execute.return_value = {
143 "uuid": stubs.expect_container_request_uuid,
144 "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
148 stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
149 stubs.api.pipeline_templates().create().execute.return_value = {
150 "uuid": stubs.expect_pipeline_template_uuid,
152 stubs.expect_job_spec = {
153 'runtime_constraints': {
154 'docker_image': '999999999999999999999999999999d3+99',
155 'min_ram_mb_per_node': 1024
157 'script_parameters': {
159 'basename': 'blorp.txt',
160 'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
164 'basename': '99999999999999999999999999999998+99',
165 'location': 'keep:99999999999999999999999999999998+99',
169 'basename': 'anonymous',
171 "basename": "renamed.txt",
173 "location": "keep:99999999999999999999999999999998+99/file1.txt",
178 'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main'
180 'repository': 'arvados',
181 'script_version': 'master',
182 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
183 'script': 'cwl-runner'
185 stubs.pipeline_component = stubs.expect_job_spec.copy()
186 stubs.expect_pipeline_instance = {
187 'name': 'submit_wf.cwl',
188 'state': 'RunningOnServer',
192 'runtime_constraints': {'docker_image': '999999999999999999999999999999d3+99', 'min_ram_mb_per_node': 1024},
193 'script_parameters': {
194 'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
196 'basename': 'blorp.txt',
198 'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
201 'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
204 'basename': 'renamed.txt',
205 'class': 'File', 'location':
206 'keep:99999999999999999999999999999998+99/file1.txt',
210 'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main',
212 'arv:enable_reuse': True,
213 'arv:on_error': 'continue'
215 'repository': 'arvados',
216 'script_version': 'master',
217 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
218 'script': 'cwl-runner',
219 'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
223 stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
224 stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
225 stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
226 stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
227 stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
228 "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
231 stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
232 stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
234 with open("tests/wf/submit_wf_packed.cwl") as f:
235 expect_packed_workflow = yaml.round_trip_load(f)
237 stubs.expect_container_spec = {
244 '/var/lib/cwl/workflow.json': {
245 'content': expect_packed_workflow,
249 'path': '/var/spool/cwl/cwl.output.json',
252 '/var/lib/cwl/cwl.input.json': {
256 'basename': '99999999999999999999999999999998+99',
257 'location': 'keep:99999999999999999999999999999998+99',
258 'class': 'Directory'},
260 'basename': u'blorp.txt',
262 'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
265 'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
266 {'basename': 'renamed.txt',
268 'location': 'keep:99999999999999999999999999999998+99/file1.txt',
277 'state': 'Committed',
278 'command': ['arvados-cwl-runner', '--local', '--api=containers',
279 '--no-log-timestamps', '--disable-validate',
280 '--eval-timeout=20', '--thread-count=1',
281 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
282 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
283 'name': 'submit_wf.cwl',
284 'container_image': '999999999999999999999999999999d3+99',
285 'output_path': '/var/spool/cwl',
286 'cwd': '/var/spool/cwl',
287 'runtime_constraints': {
290 'ram': (1024+256)*1024*1024
292 'use_existing': True,
297 stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
298 stubs.api.workflows().create().execute.return_value = {
299 "uuid": stubs.expect_workflow_uuid,
301 def update_mock(**kwargs):
302 stubs.updated_uuid = kwargs.get('uuid')
304 stubs.api.workflows().update.side_effect = update_mock
305 stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
306 "uuid": stubs.updated_uuid,
309 return func(self, stubs, *args, **kwargs)
313 class TestSubmit(unittest.TestCase):
314 @mock.patch("arvados_cwl.arvdocker.arv_docker_get_image")
315 @mock.patch("time.sleep")
317 def test_submit(self, stubs, tm, arvdock):
318 def get_image(api_client, dockerRequirement, pull_image, project_uuid):
319 if dockerRequirement["dockerPull"] == 'arvados/jobs:'+arvados_cwl.__version__:
320 return '999999999999999999999999999999d3+99'
321 elif dockerRequirement["dockerPull"] == "debian:8":
322 return '999999999999999999999999999999d4+99'
323 arvdock.side_effect = get_image
325 capture_stdout = io.StringIO()
326 exited = arvados_cwl.main(
327 ["--submit", "--no-wait", "--api=jobs", "--debug",
328 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
329 capture_stdout, sys.stderr, api_client=stubs.api)
330 self.assertEqual(exited, 0)
332 stubs.api.collections().create.assert_has_calls([
333 mock.call(body=JsonDiffMatcher({
335 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
336 'replication_desired': None,
337 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
338 }), ensure_unique_name=False),
339 mock.call(body=JsonDiffMatcher({
341 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
342 'replication_desired': None,
343 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
344 }), ensure_unique_name=False),
345 mock.call(body=JsonDiffMatcher({
347 ". 68089141fbf7e020ac90a9d6a575bc8f+1312 0:1312:workflow.cwl\n",
348 'replication_desired': None,
349 'name': 'submit_wf.cwl',
350 }), ensure_unique_name=True) ])
352 arvdock.assert_has_calls([
353 mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
354 mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8", 'http://arvados.org/cwl#dockerCollectionPDH': '999999999999999999999999999999d4+99'}, True, None),
355 mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
358 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
359 stubs.api.pipeline_instances().create.assert_called_with(
360 body=JsonDiffMatcher(expect_pipeline))
361 self.assertEqual(capture_stdout.getvalue(),
362 stubs.expect_pipeline_uuid + '\n')
365 @mock.patch("time.sleep")
367 def test_submit_no_reuse(self, stubs, tm):
368 capture_stdout = io.StringIO()
369 exited = arvados_cwl.main(
370 ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
371 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
372 capture_stdout, sys.stderr, api_client=stubs.api)
373 self.assertEqual(exited, 0)
375 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
376 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
377 expect_pipeline["properties"] = {"run_options": {"enable_job_reuse": False}}
379 stubs.api.pipeline_instances().create.assert_called_with(
380 body=JsonDiffMatcher(expect_pipeline))
381 self.assertEqual(capture_stdout.getvalue(),
382 stubs.expect_pipeline_uuid + '\n')
385 def test_error_when_multiple_storage_classes_specified(self, stubs):
386 storage_classes = "foo,bar"
387 exited = arvados_cwl.main(
388 ["--debug", "--storage-classes", storage_classes,
389 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
390 sys.stdin, sys.stderr, api_client=stubs.api)
391 self.assertEqual(exited, 1)
393 @mock.patch("time.sleep")
395 def test_submit_on_error(self, stubs, tm):
396 capture_stdout = io.StringIO()
397 exited = arvados_cwl.main(
398 ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
399 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
400 capture_stdout, sys.stderr, api_client=stubs.api)
401 self.assertEqual(exited, 0)
403 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
404 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
406 stubs.api.pipeline_instances().create.assert_called_with(
407 body=JsonDiffMatcher(expect_pipeline))
408 self.assertEqual(capture_stdout.getvalue(),
409 stubs.expect_pipeline_uuid + '\n')
412 @mock.patch("time.sleep")
414 def test_submit_runner_ram(self, stubs, tm):
415 capture_stdout = io.StringIO()
416 exited = arvados_cwl.main(
417 ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
418 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
419 capture_stdout, sys.stderr, api_client=stubs.api)
420 self.assertEqual(exited, 0)
422 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
423 expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
425 stubs.api.pipeline_instances().create.assert_called_with(
426 body=JsonDiffMatcher(expect_pipeline))
427 self.assertEqual(capture_stdout.getvalue(),
428 stubs.expect_pipeline_uuid + '\n')
431 @mock.patch("time.sleep")
433 def test_submit_invalid_runner_ram(self, stubs, tm):
434 capture_stdout = io.StringIO()
435 exited = arvados_cwl.main(
436 ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
437 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
438 capture_stdout, sys.stderr, api_client=stubs.api)
439 self.assertEqual(exited, 1)
441 @mock.patch("time.sleep")
443 def test_submit_output_name(self, stubs, tm):
444 output_name = "test_output_name"
446 capture_stdout = io.StringIO()
447 exited = arvados_cwl.main(
448 ["--submit", "--no-wait", "--debug", "--output-name", output_name,
449 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
450 capture_stdout, sys.stderr, api_client=stubs.api)
451 self.assertEqual(exited, 0)
453 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
454 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
456 stubs.api.pipeline_instances().create.assert_called_with(
457 body=JsonDiffMatcher(expect_pipeline))
458 self.assertEqual(capture_stdout.getvalue(),
459 stubs.expect_pipeline_uuid + '\n')
462 @mock.patch("time.sleep")
464 def test_submit_pipeline_name(self, stubs, tm):
465 capture_stdout = io.StringIO()
466 exited = arvados_cwl.main(
467 ["--submit", "--no-wait", "--debug", "--name=hello job 123",
468 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
469 capture_stdout, sys.stderr, api_client=stubs.api)
470 self.assertEqual(exited, 0)
472 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
473 expect_pipeline["name"] = "hello job 123"
475 stubs.api.pipeline_instances().create.assert_called_with(
476 body=JsonDiffMatcher(expect_pipeline))
477 self.assertEqual(capture_stdout.getvalue(),
478 stubs.expect_pipeline_uuid + '\n')
480 @mock.patch("time.sleep")
482 def test_submit_output_tags(self, stubs, tm):
483 output_tags = "tag0,tag1,tag2"
485 capture_stdout = io.StringIO()
486 exited = arvados_cwl.main(
487 ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
488 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
489 capture_stdout, sys.stderr, api_client=stubs.api)
490 self.assertEqual(exited, 0)
492 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
493 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
495 stubs.api.pipeline_instances().create.assert_called_with(
496 body=JsonDiffMatcher(expect_pipeline))
497 self.assertEqual(capture_stdout.getvalue(),
498 stubs.expect_pipeline_uuid + '\n')
500 @mock.patch("time.sleep")
502 def test_submit_with_project_uuid(self, stubs, tm):
503 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
505 exited = arvados_cwl.main(
506 ["--submit", "--no-wait", "--debug",
507 "--project-uuid", project_uuid,
508 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
509 sys.stdout, sys.stderr, api_client=stubs.api)
510 self.assertEqual(exited, 0)
512 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
513 expect_pipeline["owner_uuid"] = project_uuid
514 stubs.api.pipeline_instances().create.assert_called_with(
515 body=JsonDiffMatcher(expect_pipeline))
518 def test_submit_container(self, stubs):
519 capture_stdout = io.StringIO()
521 exited = arvados_cwl.main(
522 ["--submit", "--no-wait", "--api=containers", "--debug",
523 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
524 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
525 self.assertEqual(exited, 0)
527 logging.exception("")
529 stubs.api.collections().create.assert_has_calls([
530 mock.call(body=JsonDiffMatcher({
532 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
533 'replication_desired': None,
534 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
535 }), ensure_unique_name=False),
536 mock.call(body=JsonDiffMatcher({
538 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
539 'replication_desired': None,
540 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
541 }), ensure_unique_name=False)])
543 expect_container = copy.deepcopy(stubs.expect_container_spec)
544 stubs.api.container_requests().create.assert_called_with(
545 body=JsonDiffMatcher(expect_container))
546 self.assertEqual(capture_stdout.getvalue(),
547 stubs.expect_container_request_uuid + '\n')
550 def test_submit_container_no_reuse(self, stubs):
551 capture_stdout = io.StringIO()
553 exited = arvados_cwl.main(
554 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
555 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
556 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
557 self.assertEqual(exited, 0)
559 logging.exception("")
561 expect_container = copy.deepcopy(stubs.expect_container_spec)
562 expect_container["command"] = [
563 'arvados-cwl-runner', '--local', '--api=containers',
564 '--no-log-timestamps', '--disable-validate',
565 '--eval-timeout=20', '--thread-count=1',
566 '--disable-reuse', "--collection-cache-size=256",
567 '--debug', '--on-error=continue',
568 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
569 expect_container["use_existing"] = False
571 stubs.api.container_requests().create.assert_called_with(
572 body=JsonDiffMatcher(expect_container))
573 self.assertEqual(capture_stdout.getvalue(),
574 stubs.expect_container_request_uuid + '\n')
578 def test_submit_container_reuse_disabled_by_workflow(self, stubs):
579 capture_stdout = io.StringIO()
581 exited = arvados_cwl.main(
582 ["--submit", "--no-wait", "--api=containers", "--debug",
583 "tests/wf/submit_wf_no_reuse.cwl", "tests/submit_test_job.json"],
584 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
585 self.assertEqual(exited, 0)
587 expect_container = copy.deepcopy(stubs.expect_container_spec)
588 expect_container["command"] = [
589 'arvados-cwl-runner', '--local', '--api=containers',
590 '--no-log-timestamps', '--disable-validate',
591 '--eval-timeout=20', '--thread-count=1',
592 '--disable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
593 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
594 expect_container["use_existing"] = False
595 expect_container["name"] = "submit_wf_no_reuse.cwl"
596 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
598 "class": "http://arvados.org/cwl#ReuseRequirement",
599 "enableReuse": False,
602 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
603 "arv": "http://arvados.org/cwl#",
604 "cwltool": "http://commonwl.org/cwltool#"
607 stubs.api.container_requests().create.assert_called_with(
608 body=JsonDiffMatcher(expect_container))
609 self.assertEqual(capture_stdout.getvalue(),
610 stubs.expect_container_request_uuid + '\n')
614 def test_submit_container_on_error(self, stubs):
615 capture_stdout = io.StringIO()
617 exited = arvados_cwl.main(
618 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
619 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
620 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
621 self.assertEqual(exited, 0)
623 logging.exception("")
625 expect_container = copy.deepcopy(stubs.expect_container_spec)
626 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
627 '--no-log-timestamps', '--disable-validate',
628 '--eval-timeout=20', '--thread-count=1',
629 '--enable-reuse', "--collection-cache-size=256",
630 '--debug', '--on-error=stop',
631 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
633 stubs.api.container_requests().create.assert_called_with(
634 body=JsonDiffMatcher(expect_container))
635 self.assertEqual(capture_stdout.getvalue(),
636 stubs.expect_container_request_uuid + '\n')
639 def test_submit_container_output_name(self, stubs):
640 output_name = "test_output_name"
642 capture_stdout = io.StringIO()
644 exited = arvados_cwl.main(
645 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
646 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
647 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
648 self.assertEqual(exited, 0)
650 logging.exception("")
652 expect_container = copy.deepcopy(stubs.expect_container_spec)
653 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
654 '--no-log-timestamps', '--disable-validate',
655 '--eval-timeout=20', '--thread-count=1',
656 '--enable-reuse', "--collection-cache-size=256",
657 "--output-name="+output_name, '--debug', '--on-error=continue',
658 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
659 expect_container["output_name"] = output_name
661 stubs.api.container_requests().create.assert_called_with(
662 body=JsonDiffMatcher(expect_container))
663 self.assertEqual(capture_stdout.getvalue(),
664 stubs.expect_container_request_uuid + '\n')
667 def test_submit_storage_classes(self, stubs):
668 capture_stdout = io.StringIO()
670 exited = arvados_cwl.main(
671 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo",
672 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
673 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
674 self.assertEqual(exited, 0)
676 logging.exception("")
678 expect_container = copy.deepcopy(stubs.expect_container_spec)
679 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
680 '--no-log-timestamps', '--disable-validate',
681 '--eval-timeout=20', '--thread-count=1',
682 '--enable-reuse', "--collection-cache-size=256", "--debug",
683 "--storage-classes=foo", '--on-error=continue',
684 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
686 stubs.api.container_requests().create.assert_called_with(
687 body=JsonDiffMatcher(expect_container))
688 self.assertEqual(capture_stdout.getvalue(),
689 stubs.expect_container_request_uuid + '\n')
691 @mock.patch("arvados_cwl.task_queue.TaskQueue")
692 @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
693 @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
695 def test_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
696 def set_final_output(job_order, output_callback, runtimeContext):
697 output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
699 job.side_effect = set_final_output
702 exited = arvados_cwl.main(
703 ["--debug", "--local", "--storage-classes=foo",
704 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
705 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
706 self.assertEqual(exited, 0)
708 logging.exception("")
710 make_output.assert_called_with(u'Output of submit_wf.cwl', ['foo'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
712 @mock.patch("arvados_cwl.task_queue.TaskQueue")
713 @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
714 @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
716 def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
717 def set_final_output(job_order, output_callback, runtimeContext):
718 output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
720 job.side_effect = set_final_output
723 exited = arvados_cwl.main(
724 ["--debug", "--local",
725 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
726 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
727 self.assertEqual(exited, 0)
729 logging.exception("")
731 make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
734 def test_submit_container_output_ttl(self, stubs):
735 capture_stdout = io.StringIO()
737 exited = arvados_cwl.main(
738 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
739 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
740 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
741 self.assertEqual(exited, 0)
743 logging.exception("")
745 expect_container = copy.deepcopy(stubs.expect_container_spec)
746 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
747 '--no-log-timestamps', '--disable-validate',
748 '--eval-timeout=20', '--thread-count=1',
749 '--enable-reuse', "--collection-cache-size=256", '--debug',
750 '--on-error=continue',
751 "--intermediate-output-ttl=3600",
752 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
754 stubs.api.container_requests().create.assert_called_with(
755 body=JsonDiffMatcher(expect_container))
756 self.assertEqual(capture_stdout.getvalue(),
757 stubs.expect_container_request_uuid + '\n')
760 def test_submit_container_trash_intermediate(self, stubs):
761 capture_stdout = io.StringIO()
763 exited = arvados_cwl.main(
764 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
765 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
766 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
767 self.assertEqual(exited, 0)
769 logging.exception("")
771 expect_container = copy.deepcopy(stubs.expect_container_spec)
772 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
773 '--no-log-timestamps', '--disable-validate',
774 '--eval-timeout=20', '--thread-count=1',
775 '--enable-reuse', "--collection-cache-size=256",
776 '--debug', '--on-error=continue',
777 "--trash-intermediate",
778 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
780 stubs.api.container_requests().create.assert_called_with(
781 body=JsonDiffMatcher(expect_container))
782 self.assertEqual(capture_stdout.getvalue(),
783 stubs.expect_container_request_uuid + '\n')
786 def test_submit_container_output_tags(self, stubs):
787 output_tags = "tag0,tag1,tag2"
789 capture_stdout = io.StringIO()
791 exited = arvados_cwl.main(
792 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
793 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
794 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
795 self.assertEqual(exited, 0)
797 logging.exception("")
799 expect_container = copy.deepcopy(stubs.expect_container_spec)
800 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
801 '--no-log-timestamps', '--disable-validate',
802 '--eval-timeout=20', '--thread-count=1',
803 '--enable-reuse', "--collection-cache-size=256",
804 "--output-tags="+output_tags, '--debug', '--on-error=continue',
805 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
807 stubs.api.container_requests().create.assert_called_with(
808 body=JsonDiffMatcher(expect_container))
809 self.assertEqual(capture_stdout.getvalue(),
810 stubs.expect_container_request_uuid + '\n')
813 def test_submit_container_runner_ram(self, stubs):
814 capture_stdout = io.StringIO()
816 exited = arvados_cwl.main(
817 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
818 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
819 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
820 self.assertEqual(exited, 0)
822 logging.exception("")
824 expect_container = copy.deepcopy(stubs.expect_container_spec)
825 expect_container["runtime_constraints"]["ram"] = (2048+256)*1024*1024
827 stubs.api.container_requests().create.assert_called_with(
828 body=JsonDiffMatcher(expect_container))
829 self.assertEqual(capture_stdout.getvalue(),
830 stubs.expect_container_request_uuid + '\n')
832 @mock.patch("arvados.collection.CollectionReader")
833 @mock.patch("time.sleep")
835 def test_submit_file_keepref(self, stubs, tm, collectionReader):
836 capture_stdout = io.StringIO()
837 collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "blorp.txt")
838 exited = arvados_cwl.main(
839 ["--submit", "--no-wait", "--api=containers", "--debug",
840 "tests/wf/submit_keepref_wf.cwl"],
841 capture_stdout, sys.stderr, api_client=stubs.api)
842 self.assertEqual(exited, 0)
845 @mock.patch("arvados.collection.CollectionReader")
846 @mock.patch("time.sleep")
848 def test_submit_keepref(self, stubs, tm, reader):
849 capture_stdout = io.StringIO()
851 with open("tests/wf/expect_arvworkflow.cwl") as f:
852 reader().open().__enter__().read.return_value = f.read()
854 exited = arvados_cwl.main(
855 ["--submit", "--no-wait", "--api=containers", "--debug",
856 "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
857 capture_stdout, sys.stderr, api_client=stubs.api)
858 self.assertEqual(exited, 0)
868 'path': '/var/spool/cwl/cwl.output.json',
871 '/var/lib/cwl/workflow': {
872 'portable_data_hash': '99999999999999999999999999999994+99',
875 '/var/lib/cwl/cwl.input.json': {
881 }, 'state': 'Committed',
882 'output_path': '/var/spool/cwl',
883 'name': 'expect_arvworkflow.cwl#main',
884 'container_image': '999999999999999999999999999999d3+99',
885 'command': ['arvados-cwl-runner', '--local', '--api=containers',
886 '--no-log-timestamps', '--disable-validate',
887 '--eval-timeout=20', '--thread-count=1',
888 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
889 '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
890 'cwd': '/var/spool/cwl',
891 'runtime_constraints': {
896 'use_existing': True,
901 stubs.api.container_requests().create.assert_called_with(
902 body=JsonDiffMatcher(expect_container))
903 self.assertEqual(capture_stdout.getvalue(),
904 stubs.expect_container_request_uuid + '\n')
907 @mock.patch("arvados.collection.CollectionReader")
908 @mock.patch("time.sleep")
910 def test_submit_jobs_keepref(self, stubs, tm, reader):
911 capture_stdout = io.StringIO()
913 with open("tests/wf/expect_arvworkflow.cwl") as f:
914 reader().open().__enter__().read.return_value = f.read()
916 exited = arvados_cwl.main(
917 ["--submit", "--no-wait", "--api=jobs", "--debug",
918 "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
919 capture_stdout, sys.stderr, api_client=stubs.api)
920 self.assertEqual(exited, 0)
922 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
923 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
924 del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
925 del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
926 expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
927 expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
928 stubs.api.pipeline_instances().create.assert_called_with(
929 body=JsonDiffMatcher(expect_pipeline))
931 @mock.patch("time.sleep")
933 def test_submit_arvworkflow(self, stubs, tm):
934 capture_stdout = io.StringIO()
936 with open("tests/wf/expect_arvworkflow.cwl") as f:
937 stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
939 exited = arvados_cwl.main(
940 ["--submit", "--no-wait", "--api=containers", "--debug",
941 "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
942 capture_stdout, sys.stderr, api_client=stubs.api)
943 self.assertEqual(exited, 0)
953 'path': '/var/spool/cwl/cwl.output.json',
956 '/var/lib/cwl/workflow.json': {
959 'cwlVersion': 'v1.0',
964 {'type': 'string', 'id': '#main/x'}
967 {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
968 'run': '#submit_tool.cwl',
978 'inputBinding': {'position': 1},
980 'id': '#submit_tool.cwl/x'}
984 'dockerPull': 'debian:8',
985 'class': 'DockerRequirement',
986 "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
989 'id': '#submit_tool.cwl',
991 'baseCommand': 'cat',
992 'class': 'CommandLineTool'
997 '/var/lib/cwl/cwl.input.json': {
1003 }, 'state': 'Committed',
1004 'output_path': '/var/spool/cwl',
1005 'name': 'a test workflow',
1006 'container_image': "999999999999999999999999999999d3+99",
1007 'command': ['arvados-cwl-runner', '--local', '--api=containers',
1008 '--no-log-timestamps', '--disable-validate',
1009 '--eval-timeout=20', '--thread-count=1',
1010 '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
1011 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
1012 'cwd': '/var/spool/cwl',
1013 'runtime_constraints': {
1018 'use_existing': True,
1020 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
1025 stubs.api.container_requests().create.assert_called_with(
1026 body=JsonDiffMatcher(expect_container))
1027 self.assertEqual(capture_stdout.getvalue(),
1028 stubs.expect_container_request_uuid + '\n')
1032 def test_submit_container_name(self, stubs):
1033 capture_stdout = io.StringIO()
1035 exited = arvados_cwl.main(
1036 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
1037 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1038 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1039 self.assertEqual(exited, 0)
1041 logging.exception("")
1043 expect_container = copy.deepcopy(stubs.expect_container_spec)
1044 expect_container["name"] = "hello container 123"
1046 stubs.api.container_requests().create.assert_called_with(
1047 body=JsonDiffMatcher(expect_container))
1048 self.assertEqual(capture_stdout.getvalue(),
1049 stubs.expect_container_request_uuid + '\n')
1053 def test_submit_missing_input(self, stubs):
1054 capture_stdout = io.StringIO()
1055 exited = arvados_cwl.main(
1056 ["--submit", "--no-wait", "--api=containers", "--debug",
1057 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1058 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1059 self.assertEqual(exited, 0)
1061 capture_stdout = io.StringIO()
1062 exited = arvados_cwl.main(
1063 ["--submit", "--no-wait", "--api=containers", "--debug",
1064 "tests/wf/submit_wf.cwl", "tests/submit_test_job_missing.json"],
1065 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1066 self.assertEqual(exited, 1)
1070 def test_submit_container_project(self, stubs):
1071 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1072 capture_stdout = io.StringIO()
1074 exited = arvados_cwl.main(
1075 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
1076 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1077 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1078 self.assertEqual(exited, 0)
1080 logging.exception("")
1082 expect_container = copy.deepcopy(stubs.expect_container_spec)
1083 expect_container["owner_uuid"] = project_uuid
1084 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1085 '--no-log-timestamps', '--disable-validate',
1086 "--eval-timeout=20", "--thread-count=1",
1087 '--enable-reuse', "--collection-cache-size=256", '--debug',
1088 '--on-error=continue',
1089 '--project-uuid='+project_uuid,
1090 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1092 stubs.api.container_requests().create.assert_called_with(
1093 body=JsonDiffMatcher(expect_container))
1094 self.assertEqual(capture_stdout.getvalue(),
1095 stubs.expect_container_request_uuid + '\n')
1098 def test_submit_container_eval_timeout(self, stubs):
1099 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1100 capture_stdout = io.StringIO()
1102 exited = arvados_cwl.main(
1103 ["--submit", "--no-wait", "--api=containers", "--debug", "--eval-timeout=60",
1104 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1105 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1106 self.assertEqual(exited, 0)
1108 logging.exception("")
1110 expect_container = copy.deepcopy(stubs.expect_container_spec)
1111 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1112 '--no-log-timestamps', '--disable-validate',
1113 '--eval-timeout=60.0', '--thread-count=1',
1114 '--enable-reuse', "--collection-cache-size=256",
1115 '--debug', '--on-error=continue',
1116 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1118 stubs.api.container_requests().create.assert_called_with(
1119 body=JsonDiffMatcher(expect_container))
1120 self.assertEqual(capture_stdout.getvalue(),
1121 stubs.expect_container_request_uuid + '\n')
1124 def test_submit_container_collection_cache(self, stubs):
1125 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1126 capture_stdout = io.StringIO()
1128 exited = arvados_cwl.main(
1129 ["--submit", "--no-wait", "--api=containers", "--debug", "--collection-cache-size=500",
1130 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1131 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1132 self.assertEqual(exited, 0)
1134 logging.exception("")
1136 expect_container = copy.deepcopy(stubs.expect_container_spec)
1137 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1138 '--no-log-timestamps', '--disable-validate',
1139 '--eval-timeout=20', '--thread-count=1',
1140 '--enable-reuse', "--collection-cache-size=500",
1141 '--debug', '--on-error=continue',
1142 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1143 expect_container["runtime_constraints"]["ram"] = (1024+500)*1024*1024
1145 stubs.api.container_requests().create.assert_called_with(
1146 body=JsonDiffMatcher(expect_container))
1147 self.assertEqual(capture_stdout.getvalue(),
1148 stubs.expect_container_request_uuid + '\n')
1152 def test_submit_container_thread_count(self, stubs):
1153 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1154 capture_stdout = io.StringIO()
1156 exited = arvados_cwl.main(
1157 ["--submit", "--no-wait", "--api=containers", "--debug", "--thread-count=20",
1158 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1159 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1160 self.assertEqual(exited, 0)
1162 logging.exception("")
1164 expect_container = copy.deepcopy(stubs.expect_container_spec)
1165 expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1166 '--no-log-timestamps', '--disable-validate',
1167 '--eval-timeout=20', '--thread-count=20',
1168 '--enable-reuse', "--collection-cache-size=256",
1169 '--debug', '--on-error=continue',
1170 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1172 stubs.api.container_requests().create.assert_called_with(
1173 body=JsonDiffMatcher(expect_container))
1174 self.assertEqual(capture_stdout.getvalue(),
1175 stubs.expect_container_request_uuid + '\n')
1179 def test_submit_job_runner_image(self, stubs):
1180 capture_stdout = io.StringIO()
1182 exited = arvados_cwl.main(
1183 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
1184 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1185 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1186 self.assertEqual(exited, 0)
1188 logging.exception("")
1190 stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "999999999999999999999999999999d5+99"
1192 expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
1193 stubs.api.pipeline_instances().create.assert_called_with(
1194 body=JsonDiffMatcher(expect_pipeline))
1195 self.assertEqual(capture_stdout.getvalue(),
1196 stubs.expect_pipeline_uuid + '\n')
1199 def test_submit_container_runner_image(self, stubs):
1200 capture_stdout = io.StringIO()
1202 exited = arvados_cwl.main(
1203 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
1204 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1205 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1206 self.assertEqual(exited, 0)
1208 logging.exception("")
1210 stubs.expect_container_spec["container_image"] = "999999999999999999999999999999d5+99"
1212 expect_container = copy.deepcopy(stubs.expect_container_spec)
1213 stubs.api.container_requests().create.assert_called_with(
1214 body=JsonDiffMatcher(expect_container))
1215 self.assertEqual(capture_stdout.getvalue(),
1216 stubs.expect_container_request_uuid + '\n')
1219 def test_submit_priority(self, stubs):
1220 capture_stdout = io.StringIO()
1222 exited = arvados_cwl.main(
1223 ["--submit", "--no-wait", "--api=containers", "--debug", "--priority=669",
1224 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1225 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1226 self.assertEqual(exited, 0)
1228 logging.exception("")
1230 stubs.expect_container_spec["priority"] = 669
1232 expect_container = copy.deepcopy(stubs.expect_container_spec)
1233 stubs.api.container_requests().create.assert_called_with(
1234 body=JsonDiffMatcher(expect_container))
1235 self.assertEqual(capture_stdout.getvalue(),
1236 stubs.expect_container_request_uuid + '\n')
1240 def test_submit_wf_runner_resources(self, stubs):
1241 capture_stdout = io.StringIO()
1243 exited = arvados_cwl.main(
1244 ["--submit", "--no-wait", "--api=containers", "--debug",
1245 "tests/wf/submit_wf_runner_resources.cwl", "tests/submit_test_job.json"],
1246 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1247 self.assertEqual(exited, 0)
1249 logging.exception("")
1251 expect_container = copy.deepcopy(stubs.expect_container_spec)
1252 expect_container["runtime_constraints"] = {
1255 "ram": (2000+512) * 2**20
1257 expect_container["name"] = "submit_wf_runner_resources.cwl"
1258 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1260 "class": "http://arvados.org/cwl#WorkflowRunnerResources",
1266 expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
1267 "arv": "http://arvados.org/cwl#",
1269 expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1270 '--no-log-timestamps', '--disable-validate',
1271 '--eval-timeout=20', '--thread-count=1',
1272 '--enable-reuse', "--collection-cache-size=512", '--debug', '--on-error=continue',
1273 '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1275 stubs.api.container_requests().create.assert_called_with(
1276 body=JsonDiffMatcher(expect_container))
1277 self.assertEqual(capture_stdout.getvalue(),
1278 stubs.expect_container_request_uuid + '\n')
1281 arvados_cwl.arvdocker.arv_docker_clear_cache()
1283 @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
1284 @mock.patch("cwltool.docker.DockerCommandLineJob.get_image")
1285 @mock.patch("arvados.api")
1286 def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
1287 arvados_cwl.arvdocker.arv_docker_clear_cache()
1289 arvrunner = mock.MagicMock()
1290 arvrunner.project_uuid = ""
1291 api.return_value = mock.MagicMock()
1292 arvrunner.api = api.return_value
1293 arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
1294 "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1295 "link_class": "docker_image_repo+tag",
1296 "name": "arvados/jobs:"+arvados_cwl.__version__,
1298 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1299 {"items": [{"created_at": "",
1301 "link_class": "docker_image_hash",
1304 "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
1306 find_one_image_hash.return_value = "123456"
1308 arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1310 "manifest_text": "",
1312 }], "items_available": 1, "offset": 0},)
1313 arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
1314 arvrunner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1315 "portable_data_hash": "9999999999999999999999999999999b+99"}
1316 self.assertEqual("9999999999999999999999999999999b+99",
1317 arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
1321 def test_submit_secrets(self, stubs):
1322 capture_stdout = io.StringIO()
1324 exited = arvados_cwl.main(
1325 ["--submit", "--no-wait", "--api=containers", "--debug",
1326 "tests/wf/secret_wf.cwl", "tests/secret_test_job.yml"],
1327 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1328 self.assertEqual(exited, 0)
1330 logging.exception("")
1333 expect_container = {
1335 "arvados-cwl-runner",
1338 "--no-log-timestamps",
1339 "--disable-validate",
1340 "--eval-timeout=20",
1343 "--collection-cache-size=256",
1345 "--on-error=continue",
1346 "/var/lib/cwl/workflow.json#main",
1347 "/var/lib/cwl/cwl.input.json"
1349 "container_image": "999999999999999999999999999999d3+99",
1350 "cwd": "/var/spool/cwl",
1352 "/var/lib/cwl/cwl.input.json": {
1355 "$include": "/secrets/s0"
1360 "/var/lib/cwl/workflow.json": {
1365 "cwltool": "http://commonwl.org/cwltool#"
1371 "class": "CommandLineTool",
1374 "class": "http://commonwl.org/cwltool#Secrets",
1376 "#secret_job.cwl/pw"
1380 "id": "#secret_job.cwl",
1383 "id": "#secret_job.cwl/pw",
1389 "id": "#secret_job.cwl/out",
1393 "stdout": "hashed_example.txt",
1396 "class": "InitialWorkDirRequirement",
1399 "entry": "username: user\npassword: $(inputs.pw)\n",
1400 "entryname": "example.conf"
1407 "class": "Workflow",
1410 "class": "DockerRequirement",
1411 "dockerPull": "debian:8",
1412 "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
1415 "class": "http://commonwl.org/cwltool#Secrets",
1431 "outputSource": "#main/step1/out",
1437 "id": "#main/step1",
1440 "id": "#main/step1/pw",
1441 "source": "#main/pw"
1447 "run": "#secret_job.cwl"
1452 "cwlVersion": "v1.0"
1457 "kind": "collection",
1462 "path": "/var/spool/cwl/cwl.output.json"
1465 "name": "secret_wf.cwl",
1466 "output_path": "/var/spool/cwl",
1469 "runtime_constraints": {
1480 "state": "Committed",
1481 "use_existing": True
1484 stubs.api.container_requests().create.assert_called_with(
1485 body=JsonDiffMatcher(expect_container))
1486 self.assertEqual(capture_stdout.getvalue(),
1487 stubs.expect_container_request_uuid + '\n')
1490 def test_submit_request_uuid(self, stubs):
1491 stubs.expect_container_request_uuid = "zzzzz-xvhdp-yyyyyyyyyyyyyyy"
1493 stubs.api.container_requests().update().execute.return_value = {
1494 "uuid": stubs.expect_container_request_uuid,
1495 "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
1499 capture_stdout = io.StringIO()
1501 exited = arvados_cwl.main(
1502 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-request-uuid=zzzzz-xvhdp-yyyyyyyyyyyyyyy",
1503 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1504 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1505 self.assertEqual(exited, 0)
1507 logging.exception("")
1509 stubs.api.container_requests().update.assert_called_with(
1510 uuid="zzzzz-xvhdp-yyyyyyyyyyyyyyy", body=JsonDiffMatcher(stubs.expect_container_spec), cluster_id="zzzzz")
1511 self.assertEqual(capture_stdout.getvalue(),
1512 stubs.expect_container_request_uuid + '\n')
1515 def test_submit_container_cluster_id(self, stubs):
1516 capture_stdout = io.StringIO()
1517 stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1519 exited = arvados_cwl.main(
1520 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zbbbb",
1521 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1522 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1523 self.assertEqual(exited, 0)
1525 logging.exception("")
1527 expect_container = copy.deepcopy(stubs.expect_container_spec)
1529 stubs.api.container_requests().create.assert_called_with(
1530 body=JsonDiffMatcher(expect_container), cluster_id="zbbbb")
1531 self.assertEqual(capture_stdout.getvalue(),
1532 stubs.expect_container_request_uuid + '\n')
1536 def test_submit_validate_cluster_id(self, stubs):
1537 capture_stdout = io.StringIO()
1538 stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1539 exited = arvados_cwl.main(
1540 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zcccc",
1541 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1542 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1543 self.assertEqual(exited, 1)
1546 class TestCreateTemplate(unittest.TestCase):
1547 existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
1549 def _adjust_script_params(self, expect_component):
1550 expect_component['script_parameters']['x'] = {
1551 'dataclass': 'File',
1554 'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
1556 expect_component['script_parameters']['y'] = {
1557 'dataclass': 'Collection',
1559 'type': 'Directory',
1560 'value': '99999999999999999999999999999998+99',
1562 expect_component['script_parameters']['z'] = {
1563 'dataclass': 'Collection',
1565 'type': 'Directory',
1569 def test_create(self, stubs):
1570 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1572 capture_stdout = io.StringIO()
1574 exited = arvados_cwl.main(
1575 ["--create-workflow", "--debug",
1577 "--project-uuid", project_uuid,
1578 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1579 capture_stdout, sys.stderr, api_client=stubs.api)
1580 self.assertEqual(exited, 0)
1582 stubs.api.pipeline_instances().create.refute_called()
1583 stubs.api.jobs().create.refute_called()
1585 expect_component = copy.deepcopy(stubs.expect_job_spec)
1586 self._adjust_script_params(expect_component)
1589 "submit_wf.cwl": expect_component,
1591 "name": "submit_wf.cwl",
1592 "owner_uuid": project_uuid,
1594 stubs.api.pipeline_templates().create.assert_called_with(
1595 body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1597 self.assertEqual(capture_stdout.getvalue(),
1598 stubs.expect_pipeline_template_uuid + '\n')
1602 def test_create_name(self, stubs):
1603 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1605 capture_stdout = io.StringIO()
1607 exited = arvados_cwl.main(
1608 ["--create-workflow", "--debug",
1609 "--project-uuid", project_uuid,
1611 "--name", "testing 123",
1612 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1613 capture_stdout, sys.stderr, api_client=stubs.api)
1614 self.assertEqual(exited, 0)
1616 stubs.api.pipeline_instances().create.refute_called()
1617 stubs.api.jobs().create.refute_called()
1619 expect_component = copy.deepcopy(stubs.expect_job_spec)
1620 self._adjust_script_params(expect_component)
1623 "testing 123": expect_component,
1625 "name": "testing 123",
1626 "owner_uuid": project_uuid,
1628 stubs.api.pipeline_templates().create.assert_called_with(
1629 body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1631 self.assertEqual(capture_stdout.getvalue(),
1632 stubs.expect_pipeline_template_uuid + '\n')
1636 def test_update_name(self, stubs):
1637 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1639 capture_stdout = io.StringIO()
1641 exited = arvados_cwl.main(
1642 ["--update-workflow", self.existing_template_uuid,
1644 "--project-uuid", project_uuid,
1646 "--name", "testing 123",
1647 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1648 capture_stdout, sys.stderr, api_client=stubs.api)
1649 self.assertEqual(exited, 0)
1651 stubs.api.pipeline_instances().create.refute_called()
1652 stubs.api.jobs().create.refute_called()
1654 expect_component = copy.deepcopy(stubs.expect_job_spec)
1655 self._adjust_script_params(expect_component)
1658 "testing 123": expect_component,
1660 "name": "testing 123",
1661 "owner_uuid": project_uuid,
1663 stubs.api.pipeline_templates().create.refute_called()
1664 stubs.api.pipeline_templates().update.assert_called_with(
1665 body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1667 self.assertEqual(capture_stdout.getvalue(),
1668 self.existing_template_uuid + '\n')
1671 class TestCreateWorkflow(unittest.TestCase):
1672 existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1673 expect_workflow = StripYAMLComments(
1674 open("tests/wf/expect_packed.cwl").read())
1677 def test_create(self, stubs):
1678 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1680 capture_stdout = io.StringIO()
1682 exited = arvados_cwl.main(
1683 ["--create-workflow", "--debug",
1685 "--project-uuid", project_uuid,
1686 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1687 capture_stdout, sys.stderr, api_client=stubs.api)
1688 self.assertEqual(exited, 0)
1690 stubs.api.pipeline_templates().create.refute_called()
1691 stubs.api.container_requests().create.refute_called()
1695 "owner_uuid": project_uuid,
1696 "name": "submit_wf.cwl",
1698 "definition": self.expect_workflow,
1701 stubs.api.workflows().create.assert_called_with(
1702 body=JsonDiffMatcher(body))
1704 self.assertEqual(capture_stdout.getvalue(),
1705 stubs.expect_workflow_uuid + '\n')
1709 def test_create_name(self, stubs):
1710 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1712 capture_stdout = io.StringIO()
1714 exited = arvados_cwl.main(
1715 ["--create-workflow", "--debug",
1717 "--project-uuid", project_uuid,
1718 "--name", "testing 123",
1719 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1720 capture_stdout, sys.stderr, api_client=stubs.api)
1721 self.assertEqual(exited, 0)
1723 stubs.api.pipeline_templates().create.refute_called()
1724 stubs.api.container_requests().create.refute_called()
1728 "owner_uuid": project_uuid,
1729 "name": "testing 123",
1731 "definition": self.expect_workflow,
1734 stubs.api.workflows().create.assert_called_with(
1735 body=JsonDiffMatcher(body))
1737 self.assertEqual(capture_stdout.getvalue(),
1738 stubs.expect_workflow_uuid + '\n')
1741 def test_incompatible_api(self, stubs):
1742 capture_stderr = io.StringIO()
1743 logging.getLogger('arvados.cwl-runner').addHandler(
1744 logging.StreamHandler(capture_stderr))
1746 exited = arvados_cwl.main(
1747 ["--update-workflow", self.existing_workflow_uuid,
1750 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1751 sys.stderr, sys.stderr, api_client=stubs.api)
1752 self.assertEqual(exited, 1)
1753 self.assertRegexpMatches(
1754 capture_stderr.getvalue(),
1755 "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1758 def test_update(self, stubs):
1759 capture_stdout = io.StringIO()
1761 exited = arvados_cwl.main(
1762 ["--update-workflow", self.existing_workflow_uuid,
1764 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1765 capture_stdout, sys.stderr, api_client=stubs.api)
1766 self.assertEqual(exited, 0)
1770 "name": "submit_wf.cwl",
1772 "definition": self.expect_workflow,
1775 stubs.api.workflows().update.assert_called_with(
1776 uuid=self.existing_workflow_uuid,
1777 body=JsonDiffMatcher(body))
1778 self.assertEqual(capture_stdout.getvalue(),
1779 self.existing_workflow_uuid + '\n')
1783 def test_update_name(self, stubs):
1784 capture_stdout = io.StringIO()
1786 exited = arvados_cwl.main(
1787 ["--update-workflow", self.existing_workflow_uuid,
1788 "--debug", "--name", "testing 123",
1789 "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1790 capture_stdout, sys.stderr, api_client=stubs.api)
1791 self.assertEqual(exited, 0)
1795 "name": "testing 123",
1797 "definition": self.expect_workflow,
1800 stubs.api.workflows().update.assert_called_with(
1801 uuid=self.existing_workflow_uuid,
1802 body=JsonDiffMatcher(body))
1803 self.assertEqual(capture_stdout.getvalue(),
1804 self.existing_workflow_uuid + '\n')
1808 def test_create_collection_per_tool(self, stubs):
1809 project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1811 capture_stdout = io.StringIO()
1813 exited = arvados_cwl.main(
1814 ["--create-workflow", "--debug",
1816 "--project-uuid", project_uuid,
1817 "tests/collection_per_tool/collection_per_tool.cwl"],
1818 capture_stdout, sys.stderr, api_client=stubs.api)
1819 self.assertEqual(exited, 0)
1821 toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1822 expect_workflow = StripYAMLComments(open(toolfile).read())
1826 "owner_uuid": project_uuid,
1827 "name": "collection_per_tool.cwl",
1829 "definition": expect_workflow,
1832 stubs.api.workflows().create.assert_called_with(
1833 body=JsonDiffMatcher(body))
1835 self.assertEqual(capture_stdout.getvalue(),
1836 stubs.expect_workflow_uuid + '\n')
1838 class TestTemplateInputs(unittest.TestCase):
1841 "inputs_test.cwl": {
1842 'runtime_constraints': {
1843 'docker_image': '999999999999999999999999999999d3+99',
1844 'min_ram_mb_per_node': 1024
1846 'script_parameters': {
1848 'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main',
1849 'optionalFloatInput': None,
1852 'dataclass': 'File',
1854 'title': "It's a file; we expect to find some characters in it.",
1855 'description': 'If there were anything further to say, it would be said here,\nor here.'
1859 'dataclass': 'number',
1861 'title': 'Floats like a duck',
1865 'optionalFloatInput': {
1866 'type': ['null', 'float'],
1867 'dataclass': 'number',
1872 'dataclass': 'boolean',
1874 'title': 'True or false?',
1877 'repository': 'arvados',
1878 'script_version': 'master',
1879 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1880 'script': 'cwl-runner',
1883 "name": "inputs_test.cwl",
1887 def test_inputs_empty(self, stubs):
1888 exited = arvados_cwl.main(
1889 ["--create-template",
1890 "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1891 io.StringIO(), sys.stderr, api_client=stubs.api)
1892 self.assertEqual(exited, 0)
1894 stubs.api.pipeline_templates().create.assert_called_with(
1895 body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1898 def test_inputs(self, stubs):
1899 exited = arvados_cwl.main(
1900 ["--create-template",
1901 "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1902 io.StringIO(), sys.stderr, api_client=stubs.api)
1903 self.assertEqual(exited, 0)
1905 expect_template = copy.deepcopy(self.expect_template)
1906 params = expect_template[
1907 "components"]["inputs_test.cwl"]["script_parameters"]
1908 params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1909 params["cwl:tool"] = 'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main'
1910 params["floatInput"]["value"] = 1.234
1911 params["boolInput"]["value"] = True
1913 stubs.api.pipeline_templates().create.assert_called_with(
1914 body=JsonDiffMatcher(expect_template), ensure_unique_name=True)