13306: Changes to arvados-cwl-runner code after running futurize --stage2
[arvados.git] / sdk / cwl / tests / test_submit.py
1 from future import standard_library
2 standard_library.install_aliases()
3 from builtins import object
4 # Copyright (C) The Arvados Authors. All rights reserved.
5 #
6 # SPDX-License-Identifier: Apache-2.0
7
8 import copy
9 import io
10 import functools
11 import hashlib
12 import json
13 import logging
14 import mock
15 import sys
16 import unittest
17
18 import arvados
19 import arvados.collection
20 import arvados_cwl
21 import arvados_cwl.executor
22 import arvados_cwl.runner
23 import arvados.keep
24
25 from .matcher import JsonDiffMatcher, StripYAMLComments
26 from .mock_discovery import get_rootDesc
27
28 import ruamel.yaml as yaml
29
30 _rootDesc = None
31
32 def stubs(func):
33     @functools.wraps(func)
34     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
35     @mock.patch("arvados.collection.KeepClient")
36     @mock.patch("arvados.keep.KeepClient")
37     @mock.patch("arvados.events.subscribe")
38     def wrapped(self, events, keep_client1, keep_client2, keepdocker, *args, **kwargs):
39         class Stubs(object):
40             pass
41         stubs = Stubs()
42         stubs.events = events
43         stubs.keepdocker = keepdocker
44
45         def putstub(p, **kwargs):
46             return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p))
47         keep_client1().put.side_effect = putstub
48         keep_client1.put.side_effect = putstub
49         keep_client2().put.side_effect = putstub
50         keep_client2.put.side_effect = putstub
51
52         stubs.keep_client = keep_client2
53         stubs.docker_images = {
54             "arvados/jobs:"+arvados_cwl.__version__: [("zzzzz-4zz18-zzzzzzzzzzzzzd3", "")],
55             "debian:8": [("zzzzz-4zz18-zzzzzzzzzzzzzd4", "")],
56             "arvados/jobs:123": [("zzzzz-4zz18-zzzzzzzzzzzzzd5", "")],
57             "arvados/jobs:latest": [("zzzzz-4zz18-zzzzzzzzzzzzzd6", "")],
58         }
59         def kd(a, b, image_name=None, image_tag=None):
60             return stubs.docker_images.get("%s:%s" % (image_name, image_tag), [])
61         stubs.keepdocker.side_effect = kd
62
63         stubs.fake_user_uuid = "zzzzz-tpzed-zzzzzzzzzzzzzzz"
64         stubs.fake_container_uuid = "zzzzz-dz642-zzzzzzzzzzzzzzz"
65
66         stubs.api = mock.MagicMock()
67         stubs.api._rootDesc = get_rootDesc()
68
69         stubs.api.users().current().execute.return_value = {
70             "uuid": stubs.fake_user_uuid,
71         }
72         stubs.api.collections().list().execute.return_value = {"items": []}
73         stubs.api.containers().current().execute.return_value = {
74             "uuid": stubs.fake_container_uuid,
75         }
76
77         class CollectionExecute(object):
78             def __init__(self, exe):
79                 self.exe = exe
80             def execute(self, num_retries=None):
81                 return self.exe
82
83         def collection_createstub(created_collections, body, ensure_unique_name=None):
84             mt = body["manifest_text"]
85             uuid = "zzzzz-4zz18-zzzzzzzzzzzzzx%d" % len(created_collections)
86             pdh = "%s+%i" % (hashlib.md5(mt).hexdigest(), len(mt))
87             created_collections[uuid] = {
88                 "uuid": uuid,
89                 "portable_data_hash": pdh,
90                 "manifest_text": mt
91             }
92             return CollectionExecute(created_collections[uuid])
93
94         def collection_getstub(created_collections, uuid):
95             for v in created_collections.values():
96                 if uuid in (v["uuid"], v["portable_data_hash"]):
97                     return CollectionExecute(v)
98
99         created_collections = {
100             "99999999999999999999999999999998+99": {
101                 "uuid": "",
102                 "portable_data_hash": "99999999999999999999999999999998+99",
103                 "manifest_text": ". 99999999999999999999999999999998+99 0:0:file1.txt"
104             },
105             "99999999999999999999999999999994+99": {
106                 "uuid": "",
107                 "portable_data_hash": "99999999999999999999999999999994+99",
108                 "manifest_text": ". 99999999999999999999999999999994+99 0:0:expect_arvworkflow.cwl"
109             },
110             "zzzzz-4zz18-zzzzzzzzzzzzzd3": {
111                 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd3",
112                 "portable_data_hash": "999999999999999999999999999999d3+99",
113                 "manifest_text": ""
114             },
115             "zzzzz-4zz18-zzzzzzzzzzzzzd4": {
116                 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd4",
117                 "portable_data_hash": "999999999999999999999999999999d4+99",
118                 "manifest_text": ""
119             },
120             "zzzzz-4zz18-zzzzzzzzzzzzzd5": {
121                 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd5",
122                 "portable_data_hash": "999999999999999999999999999999d5+99",
123                 "manifest_text": ""
124             },
125             "zzzzz-4zz18-zzzzzzzzzzzzzd6": {
126                 "uuid": "zzzzz-4zz18-zzzzzzzzzzzzzd6",
127                 "portable_data_hash": "999999999999999999999999999999d6+99",
128                 "manifest_text": ""
129             }
130         }
131         stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
132         stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
133
134         stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
135         stubs.api.jobs().create().execute.return_value = {
136             "uuid": stubs.expect_job_uuid,
137             "state": "Queued",
138         }
139
140         stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
141         stubs.api.container_requests().create().execute.return_value = {
142             "uuid": stubs.expect_container_request_uuid,
143             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
144             "state": "Queued"
145         }
146
147         stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
148         stubs.api.pipeline_templates().create().execute.return_value = {
149             "uuid": stubs.expect_pipeline_template_uuid,
150         }
151         stubs.expect_job_spec = {
152             'runtime_constraints': {
153                 'docker_image': '999999999999999999999999999999d3+99',
154                 'min_ram_mb_per_node': 1024
155             },
156             'script_parameters': {
157                 'x': {
158                     'basename': 'blorp.txt',
159                     'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
160                     'class': 'File'
161                 },
162                 'y': {
163                     'basename': '99999999999999999999999999999998+99',
164                     'location': 'keep:99999999999999999999999999999998+99',
165                     'class': 'Directory'
166                 },
167                 'z': {
168                     'basename': 'anonymous',
169                     "listing": [{
170                         "basename": "renamed.txt",
171                         "class": "File",
172                         "location": "keep:99999999999999999999999999999998+99/file1.txt",
173                         "size": 0
174                     }],
175                     'class': 'Directory'
176                 },
177                 'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main'
178             },
179             'repository': 'arvados',
180             'script_version': 'master',
181             'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
182             'script': 'cwl-runner'
183         }
184         stubs.pipeline_component = stubs.expect_job_spec.copy()
185         stubs.expect_pipeline_instance = {
186             'name': 'submit_wf.cwl',
187             'state': 'RunningOnServer',
188             'owner_uuid': None,
189             "components": {
190                 "cwl-runner": {
191                     'runtime_constraints': {'docker_image': '999999999999999999999999999999d3+99', 'min_ram_mb_per_node': 1024},
192                     'script_parameters': {
193                         'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
194                         'x': {"value": {
195                             'basename': 'blorp.txt',
196                             'class': 'File',
197                             'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
198                             "size": 16
199                         }},
200                         'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
201                               'listing': [
202                                   {
203                                       'basename': 'renamed.txt',
204                                       'class': 'File', 'location':
205                                       'keep:99999999999999999999999999999998+99/file1.txt',
206                                       'size': 0
207                                   }
208                               ]}},
209                         'cwl:tool': '57ad063d64c60dbddc027791f0649211+60/workflow.cwl#main',
210                         'arv:debug': True,
211                         'arv:enable_reuse': True,
212                         'arv:on_error': 'continue'
213                     },
214                     'repository': 'arvados',
215                     'script_version': 'master',
216                     'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
217                     'script': 'cwl-runner',
218                     'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
219                 }
220             }
221         }
222         stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
223         stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
224         stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
225         stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
226         stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
227             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
228             "state": "Queued"
229         }
230         stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
231         stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
232
233         with open("tests/wf/submit_wf_packed.cwl") as f:
234             expect_packed_workflow = yaml.round_trip_load(f)
235
236         stubs.expect_container_spec = {
237             'priority': 500,
238             'mounts': {
239                 '/var/spool/cwl': {
240                     'writable': True,
241                     'kind': 'collection'
242                 },
243                 '/var/lib/cwl/workflow.json': {
244                     'content': expect_packed_workflow,
245                     'kind': 'json'
246                 },
247                 'stdout': {
248                     'path': '/var/spool/cwl/cwl.output.json',
249                     'kind': 'file'
250                 },
251                 '/var/lib/cwl/cwl.input.json': {
252                     'kind': 'json',
253                     'content': {
254                         'y': {
255                             'basename': '99999999999999999999999999999998+99',
256                             'location': 'keep:99999999999999999999999999999998+99',
257                             'class': 'Directory'},
258                         'x': {
259                             'basename': u'blorp.txt',
260                             'class': 'File',
261                             'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
262                             "size": 16
263                         },
264                         'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
265                             {'basename': 'renamed.txt',
266                              'class': 'File',
267                              'location': 'keep:99999999999999999999999999999998+99/file1.txt',
268                              'size': 0
269                             }
270                         ]}
271                     },
272                     'kind': 'json'
273                 }
274             },
275             'secret_mounts': {},
276             'state': 'Committed',
277             'command': ['arvados-cwl-runner', '--local', '--api=containers',
278                         '--no-log-timestamps', '--disable-validate',
279                         '--eval-timeout=20', '--thread-count=1',
280                         '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
281                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
282             'name': 'submit_wf.cwl',
283             'container_image': '999999999999999999999999999999d3+99',
284             'output_path': '/var/spool/cwl',
285             'cwd': '/var/spool/cwl',
286             'runtime_constraints': {
287                 'API': True,
288                 'vcpus': 1,
289                 'ram': (1024+256)*1024*1024
290             },
291             'use_existing': True,
292             'properties': {},
293             'secret_mounts': {}
294         }
295
296         stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
297         stubs.api.workflows().create().execute.return_value = {
298             "uuid": stubs.expect_workflow_uuid,
299         }
300         def update_mock(**kwargs):
301             stubs.updated_uuid = kwargs.get('uuid')
302             return mock.DEFAULT
303         stubs.api.workflows().update.side_effect = update_mock
304         stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
305             "uuid": stubs.updated_uuid,
306         }
307
308         return func(self, stubs, *args, **kwargs)
309     return wrapped
310
311
312 class TestSubmit(unittest.TestCase):
313     @mock.patch("arvados_cwl.arvdocker.arv_docker_get_image")
314     @mock.patch("time.sleep")
315     @stubs
316     def test_submit(self, stubs, tm, arvdock):
317         def get_image(api_client, dockerRequirement, pull_image, project_uuid):
318             if dockerRequirement["dockerPull"] == 'arvados/jobs:'+arvados_cwl.__version__:
319                 return '999999999999999999999999999999d3+99'
320             elif dockerRequirement["dockerPull"] == "debian:8":
321                 return '999999999999999999999999999999d4+99'
322         arvdock.side_effect = get_image
323
324         capture_stdout = io.StringIO()
325         exited = arvados_cwl.main(
326             ["--submit", "--no-wait", "--api=jobs", "--debug",
327              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
328             capture_stdout, sys.stderr, api_client=stubs.api)
329         self.assertEqual(exited, 0)
330
331         stubs.api.collections().create.assert_has_calls([
332             mock.call(body=JsonDiffMatcher({
333                 'manifest_text':
334                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
335                 'replication_desired': None,
336                 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
337             }), ensure_unique_name=False),
338             mock.call(body=JsonDiffMatcher({
339                 'manifest_text':
340                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
341                 'replication_desired': None,
342                 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
343             }), ensure_unique_name=False),
344             mock.call(body=JsonDiffMatcher({
345                 'manifest_text':
346                 ". 68089141fbf7e020ac90a9d6a575bc8f+1312 0:1312:workflow.cwl\n",
347                 'replication_desired': None,
348                 'name': 'submit_wf.cwl',
349             }), ensure_unique_name=True)        ])
350
351         arvdock.assert_has_calls([
352             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
353             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8", 'http://arvados.org/cwl#dockerCollectionPDH': '999999999999999999999999999999d4+99'}, True, None),
354             mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
355         ])
356
357         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
358         stubs.api.pipeline_instances().create.assert_called_with(
359             body=JsonDiffMatcher(expect_pipeline))
360         self.assertEqual(capture_stdout.getvalue(),
361                          stubs.expect_pipeline_uuid + '\n')
362
363
364     @mock.patch("time.sleep")
365     @stubs
366     def test_submit_no_reuse(self, stubs, tm):
367         capture_stdout = io.StringIO()
368         exited = arvados_cwl.main(
369             ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
370              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
371             capture_stdout, sys.stderr, api_client=stubs.api)
372         self.assertEqual(exited, 0)
373
374         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
375         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
376         expect_pipeline["properties"] = {"run_options": {"enable_job_reuse": False}}
377
378         stubs.api.pipeline_instances().create.assert_called_with(
379             body=JsonDiffMatcher(expect_pipeline))
380         self.assertEqual(capture_stdout.getvalue(),
381                          stubs.expect_pipeline_uuid + '\n')
382
383     @stubs
384     def test_error_when_multiple_storage_classes_specified(self, stubs):
385         storage_classes = "foo,bar"
386         exited = arvados_cwl.main(
387                 ["--debug", "--storage-classes", storage_classes,
388                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
389                 sys.stdin, sys.stderr, api_client=stubs.api)
390         self.assertEqual(exited, 1)
391
392     @mock.patch("time.sleep")
393     @stubs
394     def test_submit_on_error(self, stubs, tm):
395         capture_stdout = io.StringIO()
396         exited = arvados_cwl.main(
397             ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
398              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
399             capture_stdout, sys.stderr, api_client=stubs.api)
400         self.assertEqual(exited, 0)
401
402         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
403         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
404
405         stubs.api.pipeline_instances().create.assert_called_with(
406             body=JsonDiffMatcher(expect_pipeline))
407         self.assertEqual(capture_stdout.getvalue(),
408                          stubs.expect_pipeline_uuid + '\n')
409
410
411     @mock.patch("time.sleep")
412     @stubs
413     def test_submit_runner_ram(self, stubs, tm):
414         capture_stdout = io.StringIO()
415         exited = arvados_cwl.main(
416             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
417              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
418             capture_stdout, sys.stderr, api_client=stubs.api)
419         self.assertEqual(exited, 0)
420
421         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
422         expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
423
424         stubs.api.pipeline_instances().create.assert_called_with(
425             body=JsonDiffMatcher(expect_pipeline))
426         self.assertEqual(capture_stdout.getvalue(),
427                          stubs.expect_pipeline_uuid + '\n')
428
429
430     @mock.patch("time.sleep")
431     @stubs
432     def test_submit_invalid_runner_ram(self, stubs, tm):
433         capture_stdout = io.StringIO()
434         exited = arvados_cwl.main(
435             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
436              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
437             capture_stdout, sys.stderr, api_client=stubs.api)
438         self.assertEqual(exited, 1)
439
440     @mock.patch("time.sleep")
441     @stubs
442     def test_submit_output_name(self, stubs, tm):
443         output_name = "test_output_name"
444
445         capture_stdout = io.StringIO()
446         exited = arvados_cwl.main(
447             ["--submit", "--no-wait", "--debug", "--output-name", output_name,
448              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
449             capture_stdout, sys.stderr, api_client=stubs.api)
450         self.assertEqual(exited, 0)
451
452         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
453         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
454
455         stubs.api.pipeline_instances().create.assert_called_with(
456             body=JsonDiffMatcher(expect_pipeline))
457         self.assertEqual(capture_stdout.getvalue(),
458                          stubs.expect_pipeline_uuid + '\n')
459
460
461     @mock.patch("time.sleep")
462     @stubs
463     def test_submit_pipeline_name(self, stubs, tm):
464         capture_stdout = io.StringIO()
465         exited = arvados_cwl.main(
466             ["--submit", "--no-wait", "--debug", "--name=hello job 123",
467              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
468             capture_stdout, sys.stderr, api_client=stubs.api)
469         self.assertEqual(exited, 0)
470
471         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
472         expect_pipeline["name"] = "hello job 123"
473
474         stubs.api.pipeline_instances().create.assert_called_with(
475             body=JsonDiffMatcher(expect_pipeline))
476         self.assertEqual(capture_stdout.getvalue(),
477                          stubs.expect_pipeline_uuid + '\n')
478
479     @mock.patch("time.sleep")
480     @stubs
481     def test_submit_output_tags(self, stubs, tm):
482         output_tags = "tag0,tag1,tag2"
483
484         capture_stdout = io.StringIO()
485         exited = arvados_cwl.main(
486             ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
487              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
488             capture_stdout, sys.stderr, api_client=stubs.api)
489         self.assertEqual(exited, 0)
490
491         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
492         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
493
494         stubs.api.pipeline_instances().create.assert_called_with(
495             body=JsonDiffMatcher(expect_pipeline))
496         self.assertEqual(capture_stdout.getvalue(),
497                          stubs.expect_pipeline_uuid + '\n')
498
499     @mock.patch("time.sleep")
500     @stubs
501     def test_submit_with_project_uuid(self, stubs, tm):
502         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
503
504         exited = arvados_cwl.main(
505             ["--submit", "--no-wait", "--debug",
506              "--project-uuid", project_uuid,
507              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
508             sys.stdout, sys.stderr, api_client=stubs.api)
509         self.assertEqual(exited, 0)
510
511         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
512         expect_pipeline["owner_uuid"] = project_uuid
513         stubs.api.pipeline_instances().create.assert_called_with(
514             body=JsonDiffMatcher(expect_pipeline))
515
516     @stubs
517     def test_submit_container(self, stubs):
518         capture_stdout = io.StringIO()
519         try:
520             exited = arvados_cwl.main(
521                 ["--submit", "--no-wait", "--api=containers", "--debug",
522                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
523                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
524             self.assertEqual(exited, 0)
525         except:
526             logging.exception("")
527
528         stubs.api.collections().create.assert_has_calls([
529             mock.call(body=JsonDiffMatcher({
530                 'manifest_text':
531                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
532                 'replication_desired': None,
533                 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
534             }), ensure_unique_name=False),
535             mock.call(body=JsonDiffMatcher({
536                 'manifest_text':
537                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
538                 'replication_desired': None,
539                 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
540             }), ensure_unique_name=False)])
541
542         expect_container = copy.deepcopy(stubs.expect_container_spec)
543         stubs.api.container_requests().create.assert_called_with(
544             body=JsonDiffMatcher(expect_container))
545         self.assertEqual(capture_stdout.getvalue(),
546                          stubs.expect_container_request_uuid + '\n')
547
548     @stubs
549     def test_submit_container_no_reuse(self, stubs):
550         capture_stdout = io.StringIO()
551         try:
552             exited = arvados_cwl.main(
553                 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
554                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
555                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
556             self.assertEqual(exited, 0)
557         except:
558             logging.exception("")
559
560         expect_container = copy.deepcopy(stubs.expect_container_spec)
561         expect_container["command"] = [
562             'arvados-cwl-runner', '--local', '--api=containers',
563             '--no-log-timestamps', '--disable-validate',
564             '--eval-timeout=20', '--thread-count=1',
565             '--disable-reuse', "--collection-cache-size=256",
566             '--debug', '--on-error=continue',
567             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
568         expect_container["use_existing"] = False
569
570         stubs.api.container_requests().create.assert_called_with(
571             body=JsonDiffMatcher(expect_container))
572         self.assertEqual(capture_stdout.getvalue(),
573                          stubs.expect_container_request_uuid + '\n')
574
575
576     @stubs
577     def test_submit_container_reuse_disabled_by_workflow(self, stubs):
578         capture_stdout = io.StringIO()
579
580         exited = arvados_cwl.main(
581             ["--submit", "--no-wait", "--api=containers", "--debug",
582              "tests/wf/submit_wf_no_reuse.cwl", "tests/submit_test_job.json"],
583             capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
584         self.assertEqual(exited, 0)
585
586         expect_container = copy.deepcopy(stubs.expect_container_spec)
587         expect_container["command"] = [
588             'arvados-cwl-runner', '--local', '--api=containers',
589             '--no-log-timestamps', '--disable-validate',
590             '--eval-timeout=20', '--thread-count=1',
591             '--disable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
592             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
593         expect_container["use_existing"] = False
594         expect_container["name"] = "submit_wf_no_reuse.cwl"
595         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
596             {
597                 "class": "http://arvados.org/cwl#ReuseRequirement",
598                 "enableReuse": False,
599             },
600         ]
601         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
602             "arv": "http://arvados.org/cwl#",
603             "cwltool": "http://commonwl.org/cwltool#"
604         }
605
606         stubs.api.container_requests().create.assert_called_with(
607             body=JsonDiffMatcher(expect_container))
608         self.assertEqual(capture_stdout.getvalue(),
609                          stubs.expect_container_request_uuid + '\n')
610
611
612     @stubs
613     def test_submit_container_on_error(self, stubs):
614         capture_stdout = io.StringIO()
615         try:
616             exited = arvados_cwl.main(
617                 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
618                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
619                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
620             self.assertEqual(exited, 0)
621         except:
622             logging.exception("")
623
624         expect_container = copy.deepcopy(stubs.expect_container_spec)
625         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
626                                        '--no-log-timestamps', '--disable-validate',
627                                        '--eval-timeout=20', '--thread-count=1',
628                                        '--enable-reuse', "--collection-cache-size=256",
629                                        '--debug', '--on-error=stop',
630                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
631
632         stubs.api.container_requests().create.assert_called_with(
633             body=JsonDiffMatcher(expect_container))
634         self.assertEqual(capture_stdout.getvalue(),
635                          stubs.expect_container_request_uuid + '\n')
636
637     @stubs
638     def test_submit_container_output_name(self, stubs):
639         output_name = "test_output_name"
640
641         capture_stdout = io.StringIO()
642         try:
643             exited = arvados_cwl.main(
644                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
645                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
646                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
647             self.assertEqual(exited, 0)
648         except:
649             logging.exception("")
650
651         expect_container = copy.deepcopy(stubs.expect_container_spec)
652         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
653                                        '--no-log-timestamps', '--disable-validate',
654                                        '--eval-timeout=20', '--thread-count=1',
655                                        '--enable-reuse', "--collection-cache-size=256",
656                                        "--output-name="+output_name, '--debug', '--on-error=continue',
657                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
658         expect_container["output_name"] = output_name
659
660         stubs.api.container_requests().create.assert_called_with(
661             body=JsonDiffMatcher(expect_container))
662         self.assertEqual(capture_stdout.getvalue(),
663                          stubs.expect_container_request_uuid + '\n')
664
665     @stubs
666     def test_submit_storage_classes(self, stubs):
667         capture_stdout = io.StringIO()
668         try:
669             exited = arvados_cwl.main(
670                 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo",
671                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
672                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
673             self.assertEqual(exited, 0)
674         except:
675             logging.exception("")
676
677         expect_container = copy.deepcopy(stubs.expect_container_spec)
678         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
679                                        '--no-log-timestamps', '--disable-validate',
680                                        '--eval-timeout=20', '--thread-count=1',
681                                        '--enable-reuse', "--collection-cache-size=256", "--debug",
682                                        "--storage-classes=foo", '--on-error=continue',
683                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
684
685         stubs.api.container_requests().create.assert_called_with(
686             body=JsonDiffMatcher(expect_container))
687         self.assertEqual(capture_stdout.getvalue(),
688                          stubs.expect_container_request_uuid + '\n')
689
690     @mock.patch("arvados_cwl.task_queue.TaskQueue")
691     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
692     @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
693     @stubs
694     def test_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
695         def set_final_output(job_order, output_callback, runtimeContext):
696             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
697             return []
698         job.side_effect = set_final_output
699
700         try:
701             exited = arvados_cwl.main(
702                 ["--debug", "--local", "--storage-classes=foo",
703                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
704                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
705             self.assertEqual(exited, 0)
706         except:
707             logging.exception("")
708
709         make_output.assert_called_with(u'Output of submit_wf.cwl', ['foo'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
710
711     @mock.patch("arvados_cwl.task_queue.TaskQueue")
712     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
713     @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
714     @stubs
715     def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
716         def set_final_output(job_order, output_callback, runtimeContext):
717             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
718             return []
719         job.side_effect = set_final_output
720
721         try:
722             exited = arvados_cwl.main(
723                 ["--debug", "--local",
724                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
725                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
726             self.assertEqual(exited, 0)
727         except:
728             logging.exception("")
729
730         make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
731
732     @stubs
733     def test_submit_container_output_ttl(self, stubs):
734         capture_stdout = io.StringIO()
735         try:
736             exited = arvados_cwl.main(
737                 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
738                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
739                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
740             self.assertEqual(exited, 0)
741         except:
742             logging.exception("")
743
744         expect_container = copy.deepcopy(stubs.expect_container_spec)
745         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
746                                        '--no-log-timestamps', '--disable-validate',
747                                        '--eval-timeout=20', '--thread-count=1',
748                                        '--enable-reuse', "--collection-cache-size=256", '--debug',
749                                        '--on-error=continue',
750                                        "--intermediate-output-ttl=3600",
751                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
752
753         stubs.api.container_requests().create.assert_called_with(
754             body=JsonDiffMatcher(expect_container))
755         self.assertEqual(capture_stdout.getvalue(),
756                          stubs.expect_container_request_uuid + '\n')
757
758     @stubs
759     def test_submit_container_trash_intermediate(self, stubs):
760         capture_stdout = io.StringIO()
761         try:
762             exited = arvados_cwl.main(
763                 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
764                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
765                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
766             self.assertEqual(exited, 0)
767         except:
768             logging.exception("")
769
770         expect_container = copy.deepcopy(stubs.expect_container_spec)
771         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
772                                        '--no-log-timestamps', '--disable-validate',
773                                        '--eval-timeout=20', '--thread-count=1',
774                                        '--enable-reuse', "--collection-cache-size=256",
775                                        '--debug', '--on-error=continue',
776                                        "--trash-intermediate",
777                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
778
779         stubs.api.container_requests().create.assert_called_with(
780             body=JsonDiffMatcher(expect_container))
781         self.assertEqual(capture_stdout.getvalue(),
782                          stubs.expect_container_request_uuid + '\n')
783
784     @stubs
785     def test_submit_container_output_tags(self, stubs):
786         output_tags = "tag0,tag1,tag2"
787
788         capture_stdout = io.StringIO()
789         try:
790             exited = arvados_cwl.main(
791                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
792                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
793                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
794             self.assertEqual(exited, 0)
795         except:
796             logging.exception("")
797
798         expect_container = copy.deepcopy(stubs.expect_container_spec)
799         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
800                                        '--no-log-timestamps', '--disable-validate',
801                                        '--eval-timeout=20', '--thread-count=1',
802                                        '--enable-reuse', "--collection-cache-size=256",
803                                        "--output-tags="+output_tags, '--debug', '--on-error=continue',
804                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
805
806         stubs.api.container_requests().create.assert_called_with(
807             body=JsonDiffMatcher(expect_container))
808         self.assertEqual(capture_stdout.getvalue(),
809                          stubs.expect_container_request_uuid + '\n')
810
811     @stubs
812     def test_submit_container_runner_ram(self, stubs):
813         capture_stdout = io.StringIO()
814         try:
815             exited = arvados_cwl.main(
816                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
817                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
818                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
819             self.assertEqual(exited, 0)
820         except:
821             logging.exception("")
822
823         expect_container = copy.deepcopy(stubs.expect_container_spec)
824         expect_container["runtime_constraints"]["ram"] = (2048+256)*1024*1024
825
826         stubs.api.container_requests().create.assert_called_with(
827             body=JsonDiffMatcher(expect_container))
828         self.assertEqual(capture_stdout.getvalue(),
829                          stubs.expect_container_request_uuid + '\n')
830
831     @mock.patch("arvados.collection.CollectionReader")
832     @mock.patch("time.sleep")
833     @stubs
834     def test_submit_file_keepref(self, stubs, tm, collectionReader):
835         capture_stdout = io.StringIO()
836         collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "blorp.txt")
837         exited = arvados_cwl.main(
838             ["--submit", "--no-wait", "--api=containers", "--debug",
839              "tests/wf/submit_keepref_wf.cwl"],
840             capture_stdout, sys.stderr, api_client=stubs.api)
841         self.assertEqual(exited, 0)
842
843
844     @mock.patch("arvados.collection.CollectionReader")
845     @mock.patch("time.sleep")
846     @stubs
847     def test_submit_keepref(self, stubs, tm, reader):
848         capture_stdout = io.StringIO()
849
850         with open("tests/wf/expect_arvworkflow.cwl") as f:
851             reader().open().__enter__().read.return_value = f.read()
852
853         exited = arvados_cwl.main(
854             ["--submit", "--no-wait", "--api=containers", "--debug",
855              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
856             capture_stdout, sys.stderr, api_client=stubs.api)
857         self.assertEqual(exited, 0)
858
859         expect_container = {
860             'priority': 500,
861             'mounts': {
862                 '/var/spool/cwl': {
863                     'writable': True,
864                     'kind': 'collection'
865                 },
866                 'stdout': {
867                     'path': '/var/spool/cwl/cwl.output.json',
868                     'kind': 'file'
869                 },
870                 '/var/lib/cwl/workflow': {
871                     'portable_data_hash': '99999999999999999999999999999994+99',
872                     'kind': 'collection'
873                 },
874                 '/var/lib/cwl/cwl.input.json': {
875                     'content': {
876                         'x': 'XxX'
877                     },
878                     'kind': 'json'
879                 }
880             }, 'state': 'Committed',
881             'output_path': '/var/spool/cwl',
882             'name': 'expect_arvworkflow.cwl#main',
883             'container_image': '999999999999999999999999999999d3+99',
884             'command': ['arvados-cwl-runner', '--local', '--api=containers',
885                         '--no-log-timestamps', '--disable-validate',
886                         '--eval-timeout=20', '--thread-count=1',
887                         '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
888                         '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
889             'cwd': '/var/spool/cwl',
890             'runtime_constraints': {
891                 'API': True,
892                 'vcpus': 1,
893                 'ram': 1342177280
894             },
895             'use_existing': True,
896             'properties': {},
897             'secret_mounts': {}
898         }
899
900         stubs.api.container_requests().create.assert_called_with(
901             body=JsonDiffMatcher(expect_container))
902         self.assertEqual(capture_stdout.getvalue(),
903                          stubs.expect_container_request_uuid + '\n')
904
905
906     @mock.patch("arvados.collection.CollectionReader")
907     @mock.patch("time.sleep")
908     @stubs
909     def test_submit_jobs_keepref(self, stubs, tm, reader):
910         capture_stdout = io.StringIO()
911
912         with open("tests/wf/expect_arvworkflow.cwl") as f:
913             reader().open().__enter__().read.return_value = f.read()
914
915         exited = arvados_cwl.main(
916             ["--submit", "--no-wait", "--api=jobs", "--debug",
917              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
918             capture_stdout, sys.stderr, api_client=stubs.api)
919         self.assertEqual(exited, 0)
920
921         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
922         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
923         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
924         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
925         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
926         expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
927         stubs.api.pipeline_instances().create.assert_called_with(
928             body=JsonDiffMatcher(expect_pipeline))
929
930     @mock.patch("time.sleep")
931     @stubs
932     def test_submit_arvworkflow(self, stubs, tm):
933         capture_stdout = io.StringIO()
934
935         with open("tests/wf/expect_arvworkflow.cwl") as f:
936             stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
937
938         exited = arvados_cwl.main(
939             ["--submit", "--no-wait", "--api=containers", "--debug",
940              "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
941             capture_stdout, sys.stderr, api_client=stubs.api)
942         self.assertEqual(exited, 0)
943
944         expect_container = {
945             'priority': 500,
946             'mounts': {
947                 '/var/spool/cwl': {
948                     'writable': True,
949                     'kind': 'collection'
950                 },
951                 'stdout': {
952                     'path': '/var/spool/cwl/cwl.output.json',
953                     'kind': 'file'
954                 },
955                 '/var/lib/cwl/workflow.json': {
956                     'kind': 'json',
957                     'content': {
958                         'cwlVersion': 'v1.0',
959                         '$graph': [
960                             {
961                                 'id': '#main',
962                                 'inputs': [
963                                     {'type': 'string', 'id': '#main/x'}
964                                 ],
965                                 'steps': [
966                                     {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
967                                      'run': '#submit_tool.cwl',
968                                      'id': '#main/step1',
969                                      'out': []}
970                                 ],
971                                 'class': 'Workflow',
972                                 'outputs': []
973                             },
974                             {
975                                 'inputs': [
976                                     {
977                                         'inputBinding': {'position': 1},
978                                         'type': 'string',
979                                         'id': '#submit_tool.cwl/x'}
980                                 ],
981                                 'requirements': [
982                                     {
983                                         'dockerPull': 'debian:8',
984                                         'class': 'DockerRequirement',
985                                         "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
986                                     }
987                                 ],
988                                 'id': '#submit_tool.cwl',
989                                 'outputs': [],
990                                 'baseCommand': 'cat',
991                                 'class': 'CommandLineTool'
992                             }
993                         ]
994                     }
995                 },
996                 '/var/lib/cwl/cwl.input.json': {
997                     'content': {
998                         'x': 'XxX'
999                     },
1000                     'kind': 'json'
1001                 }
1002             }, 'state': 'Committed',
1003             'output_path': '/var/spool/cwl',
1004             'name': 'a test workflow',
1005             'container_image': "999999999999999999999999999999d3+99",
1006             'command': ['arvados-cwl-runner', '--local', '--api=containers',
1007                         '--no-log-timestamps', '--disable-validate',
1008                         '--eval-timeout=20', '--thread-count=1',
1009                         '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
1010                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
1011             'cwd': '/var/spool/cwl',
1012             'runtime_constraints': {
1013                 'API': True,
1014                 'vcpus': 1,
1015                 'ram': 1342177280
1016             },
1017             'use_existing': True,
1018             'properties': {
1019                 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
1020             },
1021             'secret_mounts': {}
1022         }
1023
1024         stubs.api.container_requests().create.assert_called_with(
1025             body=JsonDiffMatcher(expect_container))
1026         self.assertEqual(capture_stdout.getvalue(),
1027                          stubs.expect_container_request_uuid + '\n')
1028
1029
1030     @stubs
1031     def test_submit_container_name(self, stubs):
1032         capture_stdout = io.StringIO()
1033         try:
1034             exited = arvados_cwl.main(
1035                 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
1036                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1037                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1038             self.assertEqual(exited, 0)
1039         except:
1040             logging.exception("")
1041
1042         expect_container = copy.deepcopy(stubs.expect_container_spec)
1043         expect_container["name"] = "hello container 123"
1044
1045         stubs.api.container_requests().create.assert_called_with(
1046             body=JsonDiffMatcher(expect_container))
1047         self.assertEqual(capture_stdout.getvalue(),
1048                          stubs.expect_container_request_uuid + '\n')
1049
1050
1051     @stubs
1052     def test_submit_missing_input(self, stubs):
1053         capture_stdout = io.StringIO()
1054         exited = arvados_cwl.main(
1055             ["--submit", "--no-wait", "--api=containers", "--debug",
1056              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1057             capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1058         self.assertEqual(exited, 0)
1059
1060         capture_stdout = io.StringIO()
1061         exited = arvados_cwl.main(
1062             ["--submit", "--no-wait", "--api=containers", "--debug",
1063              "tests/wf/submit_wf.cwl", "tests/submit_test_job_missing.json"],
1064             capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1065         self.assertEqual(exited, 1)
1066
1067
1068     @stubs
1069     def test_submit_container_project(self, stubs):
1070         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1071         capture_stdout = io.StringIO()
1072         try:
1073             exited = arvados_cwl.main(
1074                 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
1075                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1076                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1077             self.assertEqual(exited, 0)
1078         except:
1079             logging.exception("")
1080
1081         expect_container = copy.deepcopy(stubs.expect_container_spec)
1082         expect_container["owner_uuid"] = project_uuid
1083         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1084                                        '--no-log-timestamps', '--disable-validate',
1085                                        "--eval-timeout=20", "--thread-count=1",
1086                                        '--enable-reuse', "--collection-cache-size=256", '--debug',
1087                                        '--on-error=continue',
1088                                        '--project-uuid='+project_uuid,
1089                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1090
1091         stubs.api.container_requests().create.assert_called_with(
1092             body=JsonDiffMatcher(expect_container))
1093         self.assertEqual(capture_stdout.getvalue(),
1094                          stubs.expect_container_request_uuid + '\n')
1095
1096     @stubs
1097     def test_submit_container_eval_timeout(self, stubs):
1098         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1099         capture_stdout = io.StringIO()
1100         try:
1101             exited = arvados_cwl.main(
1102                 ["--submit", "--no-wait", "--api=containers", "--debug", "--eval-timeout=60",
1103                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1104                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1105             self.assertEqual(exited, 0)
1106         except:
1107             logging.exception("")
1108
1109         expect_container = copy.deepcopy(stubs.expect_container_spec)
1110         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1111                                        '--no-log-timestamps', '--disable-validate',
1112                                        '--eval-timeout=60.0', '--thread-count=1',
1113                                        '--enable-reuse', "--collection-cache-size=256",
1114                                        '--debug', '--on-error=continue',
1115                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1116
1117         stubs.api.container_requests().create.assert_called_with(
1118             body=JsonDiffMatcher(expect_container))
1119         self.assertEqual(capture_stdout.getvalue(),
1120                          stubs.expect_container_request_uuid + '\n')
1121
1122     @stubs
1123     def test_submit_container_collection_cache(self, stubs):
1124         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1125         capture_stdout = io.StringIO()
1126         try:
1127             exited = arvados_cwl.main(
1128                 ["--submit", "--no-wait", "--api=containers", "--debug", "--collection-cache-size=500",
1129                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1130                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1131             self.assertEqual(exited, 0)
1132         except:
1133             logging.exception("")
1134
1135         expect_container = copy.deepcopy(stubs.expect_container_spec)
1136         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1137                                        '--no-log-timestamps', '--disable-validate',
1138                                        '--eval-timeout=20', '--thread-count=1',
1139                                        '--enable-reuse', "--collection-cache-size=500",
1140                                        '--debug', '--on-error=continue',
1141                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1142         expect_container["runtime_constraints"]["ram"] = (1024+500)*1024*1024
1143
1144         stubs.api.container_requests().create.assert_called_with(
1145             body=JsonDiffMatcher(expect_container))
1146         self.assertEqual(capture_stdout.getvalue(),
1147                          stubs.expect_container_request_uuid + '\n')
1148
1149
1150     @stubs
1151     def test_submit_container_thread_count(self, stubs):
1152         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1153         capture_stdout = io.StringIO()
1154         try:
1155             exited = arvados_cwl.main(
1156                 ["--submit", "--no-wait", "--api=containers", "--debug", "--thread-count=20",
1157                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1158                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1159             self.assertEqual(exited, 0)
1160         except:
1161             logging.exception("")
1162
1163         expect_container = copy.deepcopy(stubs.expect_container_spec)
1164         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1165                                        '--no-log-timestamps', '--disable-validate',
1166                                        '--eval-timeout=20', '--thread-count=20',
1167                                        '--enable-reuse', "--collection-cache-size=256",
1168                                        '--debug', '--on-error=continue',
1169                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1170
1171         stubs.api.container_requests().create.assert_called_with(
1172             body=JsonDiffMatcher(expect_container))
1173         self.assertEqual(capture_stdout.getvalue(),
1174                          stubs.expect_container_request_uuid + '\n')
1175
1176
1177     @stubs
1178     def test_submit_job_runner_image(self, stubs):
1179         capture_stdout = io.StringIO()
1180         try:
1181             exited = arvados_cwl.main(
1182                 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
1183                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1184                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1185             self.assertEqual(exited, 0)
1186         except:
1187             logging.exception("")
1188
1189         stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "999999999999999999999999999999d5+99"
1190
1191         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
1192         stubs.api.pipeline_instances().create.assert_called_with(
1193             body=JsonDiffMatcher(expect_pipeline))
1194         self.assertEqual(capture_stdout.getvalue(),
1195                          stubs.expect_pipeline_uuid + '\n')
1196
1197     @stubs
1198     def test_submit_container_runner_image(self, stubs):
1199         capture_stdout = io.StringIO()
1200         try:
1201             exited = arvados_cwl.main(
1202                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
1203                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1204                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1205             self.assertEqual(exited, 0)
1206         except:
1207             logging.exception("")
1208
1209         stubs.expect_container_spec["container_image"] = "999999999999999999999999999999d5+99"
1210
1211         expect_container = copy.deepcopy(stubs.expect_container_spec)
1212         stubs.api.container_requests().create.assert_called_with(
1213             body=JsonDiffMatcher(expect_container))
1214         self.assertEqual(capture_stdout.getvalue(),
1215                          stubs.expect_container_request_uuid + '\n')
1216
1217     @stubs
1218     def test_submit_priority(self, stubs):
1219         capture_stdout = io.StringIO()
1220         try:
1221             exited = arvados_cwl.main(
1222                 ["--submit", "--no-wait", "--api=containers", "--debug", "--priority=669",
1223                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1224                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1225             self.assertEqual(exited, 0)
1226         except:
1227             logging.exception("")
1228
1229         stubs.expect_container_spec["priority"] = 669
1230
1231         expect_container = copy.deepcopy(stubs.expect_container_spec)
1232         stubs.api.container_requests().create.assert_called_with(
1233             body=JsonDiffMatcher(expect_container))
1234         self.assertEqual(capture_stdout.getvalue(),
1235                          stubs.expect_container_request_uuid + '\n')
1236
1237
1238     @stubs
1239     def test_submit_wf_runner_resources(self, stubs):
1240         capture_stdout = io.StringIO()
1241         try:
1242             exited = arvados_cwl.main(
1243                 ["--submit", "--no-wait", "--api=containers", "--debug",
1244                  "tests/wf/submit_wf_runner_resources.cwl", "tests/submit_test_job.json"],
1245                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1246             self.assertEqual(exited, 0)
1247         except:
1248             logging.exception("")
1249
1250         expect_container = copy.deepcopy(stubs.expect_container_spec)
1251         expect_container["runtime_constraints"] = {
1252             "API": True,
1253             "vcpus": 2,
1254             "ram": (2000+512) * 2**20
1255         }
1256         expect_container["name"] = "submit_wf_runner_resources.cwl"
1257         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1258             {
1259                 "class": "http://arvados.org/cwl#WorkflowRunnerResources",
1260                 "coresMin": 2,
1261                 "ramMin": 2000,
1262                 "keep_cache": 512
1263             }
1264         ]
1265         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
1266             "arv": "http://arvados.org/cwl#",
1267         }
1268         expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1269                         '--no-log-timestamps', '--disable-validate',
1270                         '--eval-timeout=20', '--thread-count=1',
1271                         '--enable-reuse', "--collection-cache-size=512", '--debug', '--on-error=continue',
1272                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1273
1274         stubs.api.container_requests().create.assert_called_with(
1275             body=JsonDiffMatcher(expect_container))
1276         self.assertEqual(capture_stdout.getvalue(),
1277                          stubs.expect_container_request_uuid + '\n')
1278
1279     def tearDown(self):
1280         arvados_cwl.arvdocker.arv_docker_clear_cache()
1281
1282     @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
1283     @mock.patch("cwltool.docker.DockerCommandLineJob.get_image")
1284     @mock.patch("arvados.api")
1285     def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
1286         arvados_cwl.arvdocker.arv_docker_clear_cache()
1287
1288         arvrunner = mock.MagicMock()
1289         arvrunner.project_uuid = ""
1290         api.return_value = mock.MagicMock()
1291         arvrunner.api = api.return_value
1292         arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
1293                                                                         "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1294                                                                         "link_class": "docker_image_repo+tag",
1295                                                                         "name": "arvados/jobs:"+arvados_cwl.__version__,
1296                                                                         "owner_uuid": "",
1297                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1298                                                             {"items": [{"created_at": "",
1299                                                                         "head_uuid": "",
1300                                                                         "link_class": "docker_image_hash",
1301                                                                         "name": "123456",
1302                                                                         "owner_uuid": "",
1303                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
1304         )
1305         find_one_image_hash.return_value = "123456"
1306
1307         arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1308                                                                               "owner_uuid": "",
1309                                                                               "manifest_text": "",
1310                                                                               "properties": ""
1311                                                                           }], "items_available": 1, "offset": 0},)
1312         arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
1313         arvrunner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1314                                                                   "portable_data_hash": "9999999999999999999999999999999b+99"}
1315         self.assertEqual("9999999999999999999999999999999b+99",
1316                          arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
1317
1318
1319     @stubs
1320     def test_submit_secrets(self, stubs):
1321         capture_stdout = io.StringIO()
1322         try:
1323             exited = arvados_cwl.main(
1324                 ["--submit", "--no-wait", "--api=containers", "--debug",
1325                  "tests/wf/secret_wf.cwl", "tests/secret_test_job.yml"],
1326                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1327             self.assertEqual(exited, 0)
1328         except:
1329             logging.exception("")
1330
1331
1332         expect_container = {
1333             "command": [
1334                 "arvados-cwl-runner",
1335                 "--local",
1336                 "--api=containers",
1337                 "--no-log-timestamps",
1338                 "--disable-validate",
1339                 "--eval-timeout=20",
1340                 '--thread-count=1',
1341                 "--enable-reuse",
1342                 "--collection-cache-size=256",
1343                 '--debug',
1344                 "--on-error=continue",
1345                 "/var/lib/cwl/workflow.json#main",
1346                 "/var/lib/cwl/cwl.input.json"
1347             ],
1348             "container_image": "999999999999999999999999999999d3+99",
1349             "cwd": "/var/spool/cwl",
1350             "mounts": {
1351                 "/var/lib/cwl/cwl.input.json": {
1352                     "content": {
1353                         "pw": {
1354                             "$include": "/secrets/s0"
1355                         }
1356                     },
1357                     "kind": "json"
1358                 },
1359                 "/var/lib/cwl/workflow.json": {
1360                     "content": {
1361                         "$graph": [
1362                             {
1363                                 "$namespaces": {
1364                                     "cwltool": "http://commonwl.org/cwltool#"
1365                                 },
1366                                 "arguments": [
1367                                     "md5sum",
1368                                     "example.conf"
1369                                 ],
1370                                 "class": "CommandLineTool",
1371                                 "hints": [
1372                                     {
1373                                         "class": "http://commonwl.org/cwltool#Secrets",
1374                                         "secrets": [
1375                                             "#secret_job.cwl/pw"
1376                                         ]
1377                                     }
1378                                 ],
1379                                 "id": "#secret_job.cwl",
1380                                 "inputs": [
1381                                     {
1382                                         "id": "#secret_job.cwl/pw",
1383                                         "type": "string"
1384                                     }
1385                                 ],
1386                                 "outputs": [
1387                                     {
1388                                         "id": "#secret_job.cwl/out",
1389                                         "type": "stdout"
1390                                     }
1391                                 ],
1392                                 "stdout": "hashed_example.txt",
1393                                 "requirements": [
1394                                     {
1395                                         "class": "InitialWorkDirRequirement",
1396                                         "listing": [
1397                                             {
1398                                                 "entry": "username: user\npassword: $(inputs.pw)\n",
1399                                                 "entryname": "example.conf"
1400                                             }
1401                                         ]
1402                                     }
1403                                 ]
1404                             },
1405                             {
1406                                 "class": "Workflow",
1407                                 "hints": [
1408                                     {
1409                                         "class": "DockerRequirement",
1410                                         "dockerPull": "debian:8",
1411                                         "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
1412                                     },
1413                                     {
1414                                         "class": "http://commonwl.org/cwltool#Secrets",
1415                                         "secrets": [
1416                                             "#main/pw"
1417                                         ]
1418                                     }
1419                                 ],
1420                                 "id": "#main",
1421                                 "inputs": [
1422                                     {
1423                                         "id": "#main/pw",
1424                                         "type": "string"
1425                                     }
1426                                 ],
1427                                 "outputs": [
1428                                     {
1429                                         "id": "#main/out",
1430                                         "outputSource": "#main/step1/out",
1431                                         "type": "File"
1432                                     }
1433                                 ],
1434                                 "steps": [
1435                                     {
1436                                         "id": "#main/step1",
1437                                         "in": [
1438                                             {
1439                                                 "id": "#main/step1/pw",
1440                                                 "source": "#main/pw"
1441                                             }
1442                                         ],
1443                                         "out": [
1444                                             "#main/step1/out"
1445                                         ],
1446                                         "run": "#secret_job.cwl"
1447                                     }
1448                                 ]
1449                             }
1450                         ],
1451                         "cwlVersion": "v1.0"
1452                     },
1453                     "kind": "json"
1454                 },
1455                 "/var/spool/cwl": {
1456                     "kind": "collection",
1457                     "writable": True
1458                 },
1459                 "stdout": {
1460                     "kind": "file",
1461                     "path": "/var/spool/cwl/cwl.output.json"
1462                 }
1463             },
1464             "name": "secret_wf.cwl",
1465             "output_path": "/var/spool/cwl",
1466             "priority": 500,
1467             "properties": {},
1468             "runtime_constraints": {
1469                 "API": True,
1470                 "ram": 1342177280,
1471                 "vcpus": 1
1472             },
1473             "secret_mounts": {
1474                 "/secrets/s0": {
1475                     "content": "blorp",
1476                     "kind": "text"
1477                 }
1478             },
1479             "state": "Committed",
1480             "use_existing": True
1481         }
1482
1483         stubs.api.container_requests().create.assert_called_with(
1484             body=JsonDiffMatcher(expect_container))
1485         self.assertEqual(capture_stdout.getvalue(),
1486                          stubs.expect_container_request_uuid + '\n')
1487
1488     @stubs
1489     def test_submit_request_uuid(self, stubs):
1490         stubs.expect_container_request_uuid = "zzzzz-xvhdp-yyyyyyyyyyyyyyy"
1491
1492         stubs.api.container_requests().update().execute.return_value = {
1493             "uuid": stubs.expect_container_request_uuid,
1494             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
1495             "state": "Queued"
1496         }
1497
1498         capture_stdout = io.StringIO()
1499         try:
1500             exited = arvados_cwl.main(
1501                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-request-uuid=zzzzz-xvhdp-yyyyyyyyyyyyyyy",
1502                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1503                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1504             self.assertEqual(exited, 0)
1505         except:
1506             logging.exception("")
1507
1508         stubs.api.container_requests().update.assert_called_with(
1509             uuid="zzzzz-xvhdp-yyyyyyyyyyyyyyy", body=JsonDiffMatcher(stubs.expect_container_spec), cluster_id="zzzzz")
1510         self.assertEqual(capture_stdout.getvalue(),
1511                          stubs.expect_container_request_uuid + '\n')
1512
1513     @stubs
1514     def test_submit_container_cluster_id(self, stubs):
1515         capture_stdout = io.StringIO()
1516         stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1517         try:
1518             exited = arvados_cwl.main(
1519                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zbbbb",
1520                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1521                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1522             self.assertEqual(exited, 0)
1523         except:
1524             logging.exception("")
1525
1526         expect_container = copy.deepcopy(stubs.expect_container_spec)
1527
1528         stubs.api.container_requests().create.assert_called_with(
1529             body=JsonDiffMatcher(expect_container), cluster_id="zbbbb")
1530         self.assertEqual(capture_stdout.getvalue(),
1531                          stubs.expect_container_request_uuid + '\n')
1532
1533
1534     @stubs
1535     def test_submit_validate_cluster_id(self, stubs):
1536         capture_stdout = io.StringIO()
1537         stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1538         exited = arvados_cwl.main(
1539             ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zcccc",
1540              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1541             capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1542         self.assertEqual(exited, 1)
1543
1544
1545 class TestCreateTemplate(unittest.TestCase):
1546     existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
1547
1548     def _adjust_script_params(self, expect_component):
1549         expect_component['script_parameters']['x'] = {
1550             'dataclass': 'File',
1551             'required': True,
1552             'type': 'File',
1553             'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
1554         }
1555         expect_component['script_parameters']['y'] = {
1556             'dataclass': 'Collection',
1557             'required': True,
1558             'type': 'Directory',
1559             'value': '99999999999999999999999999999998+99',
1560         }
1561         expect_component['script_parameters']['z'] = {
1562             'dataclass': 'Collection',
1563             'required': True,
1564             'type': 'Directory',
1565         }
1566
1567     @stubs
1568     def test_create(self, stubs):
1569         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1570
1571         capture_stdout = io.StringIO()
1572
1573         exited = arvados_cwl.main(
1574             ["--create-workflow", "--debug",
1575              "--api=jobs",
1576              "--project-uuid", project_uuid,
1577              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1578             capture_stdout, sys.stderr, api_client=stubs.api)
1579         self.assertEqual(exited, 0)
1580
1581         stubs.api.pipeline_instances().create.refute_called()
1582         stubs.api.jobs().create.refute_called()
1583
1584         expect_component = copy.deepcopy(stubs.expect_job_spec)
1585         self._adjust_script_params(expect_component)
1586         expect_template = {
1587             "components": {
1588                 "submit_wf.cwl": expect_component,
1589             },
1590             "name": "submit_wf.cwl",
1591             "owner_uuid": project_uuid,
1592         }
1593         stubs.api.pipeline_templates().create.assert_called_with(
1594             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1595
1596         self.assertEqual(capture_stdout.getvalue(),
1597                          stubs.expect_pipeline_template_uuid + '\n')
1598
1599
1600     @stubs
1601     def test_create_name(self, stubs):
1602         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1603
1604         capture_stdout = io.StringIO()
1605
1606         exited = arvados_cwl.main(
1607             ["--create-workflow", "--debug",
1608              "--project-uuid", project_uuid,
1609              "--api=jobs",
1610              "--name", "testing 123",
1611              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1612             capture_stdout, sys.stderr, api_client=stubs.api)
1613         self.assertEqual(exited, 0)
1614
1615         stubs.api.pipeline_instances().create.refute_called()
1616         stubs.api.jobs().create.refute_called()
1617
1618         expect_component = copy.deepcopy(stubs.expect_job_spec)
1619         self._adjust_script_params(expect_component)
1620         expect_template = {
1621             "components": {
1622                 "testing 123": expect_component,
1623             },
1624             "name": "testing 123",
1625             "owner_uuid": project_uuid,
1626         }
1627         stubs.api.pipeline_templates().create.assert_called_with(
1628             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1629
1630         self.assertEqual(capture_stdout.getvalue(),
1631                          stubs.expect_pipeline_template_uuid + '\n')
1632
1633
1634     @stubs
1635     def test_update_name(self, stubs):
1636         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1637
1638         capture_stdout = io.StringIO()
1639
1640         exited = arvados_cwl.main(
1641             ["--update-workflow", self.existing_template_uuid,
1642              "--debug",
1643              "--project-uuid", project_uuid,
1644              "--api=jobs",
1645              "--name", "testing 123",
1646              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1647             capture_stdout, sys.stderr, api_client=stubs.api)
1648         self.assertEqual(exited, 0)
1649
1650         stubs.api.pipeline_instances().create.refute_called()
1651         stubs.api.jobs().create.refute_called()
1652
1653         expect_component = copy.deepcopy(stubs.expect_job_spec)
1654         self._adjust_script_params(expect_component)
1655         expect_template = {
1656             "components": {
1657                 "testing 123": expect_component,
1658             },
1659             "name": "testing 123",
1660             "owner_uuid": project_uuid,
1661         }
1662         stubs.api.pipeline_templates().create.refute_called()
1663         stubs.api.pipeline_templates().update.assert_called_with(
1664             body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1665
1666         self.assertEqual(capture_stdout.getvalue(),
1667                          self.existing_template_uuid + '\n')
1668
1669
1670 class TestCreateWorkflow(unittest.TestCase):
1671     existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1672     expect_workflow = StripYAMLComments(
1673         open("tests/wf/expect_packed.cwl").read())
1674
1675     @stubs
1676     def test_create(self, stubs):
1677         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1678
1679         capture_stdout = io.StringIO()
1680
1681         exited = arvados_cwl.main(
1682             ["--create-workflow", "--debug",
1683              "--api=containers",
1684              "--project-uuid", project_uuid,
1685              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1686             capture_stdout, sys.stderr, api_client=stubs.api)
1687         self.assertEqual(exited, 0)
1688
1689         stubs.api.pipeline_templates().create.refute_called()
1690         stubs.api.container_requests().create.refute_called()
1691
1692         body = {
1693             "workflow": {
1694                 "owner_uuid": project_uuid,
1695                 "name": "submit_wf.cwl",
1696                 "description": "",
1697                 "definition": self.expect_workflow,
1698             }
1699         }
1700         stubs.api.workflows().create.assert_called_with(
1701             body=JsonDiffMatcher(body))
1702
1703         self.assertEqual(capture_stdout.getvalue(),
1704                          stubs.expect_workflow_uuid + '\n')
1705
1706
1707     @stubs
1708     def test_create_name(self, stubs):
1709         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1710
1711         capture_stdout = io.StringIO()
1712
1713         exited = arvados_cwl.main(
1714             ["--create-workflow", "--debug",
1715              "--api=containers",
1716              "--project-uuid", project_uuid,
1717              "--name", "testing 123",
1718              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1719             capture_stdout, sys.stderr, api_client=stubs.api)
1720         self.assertEqual(exited, 0)
1721
1722         stubs.api.pipeline_templates().create.refute_called()
1723         stubs.api.container_requests().create.refute_called()
1724
1725         body = {
1726             "workflow": {
1727                 "owner_uuid": project_uuid,
1728                 "name": "testing 123",
1729                 "description": "",
1730                 "definition": self.expect_workflow,
1731             }
1732         }
1733         stubs.api.workflows().create.assert_called_with(
1734             body=JsonDiffMatcher(body))
1735
1736         self.assertEqual(capture_stdout.getvalue(),
1737                          stubs.expect_workflow_uuid + '\n')
1738
1739     @stubs
1740     def test_incompatible_api(self, stubs):
1741         capture_stderr = io.StringIO()
1742         logging.getLogger('arvados.cwl-runner').addHandler(
1743             logging.StreamHandler(capture_stderr))
1744
1745         exited = arvados_cwl.main(
1746             ["--update-workflow", self.existing_workflow_uuid,
1747              "--api=jobs",
1748              "--debug",
1749              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1750             sys.stderr, sys.stderr, api_client=stubs.api)
1751         self.assertEqual(exited, 1)
1752         self.assertRegexpMatches(
1753             capture_stderr.getvalue(),
1754             "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1755
1756     @stubs
1757     def test_update(self, stubs):
1758         capture_stdout = io.StringIO()
1759
1760         exited = arvados_cwl.main(
1761             ["--update-workflow", self.existing_workflow_uuid,
1762              "--debug",
1763              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1764             capture_stdout, sys.stderr, api_client=stubs.api)
1765         self.assertEqual(exited, 0)
1766
1767         body = {
1768             "workflow": {
1769                 "name": "submit_wf.cwl",
1770                 "description": "",
1771                 "definition": self.expect_workflow,
1772             }
1773         }
1774         stubs.api.workflows().update.assert_called_with(
1775             uuid=self.existing_workflow_uuid,
1776             body=JsonDiffMatcher(body))
1777         self.assertEqual(capture_stdout.getvalue(),
1778                          self.existing_workflow_uuid + '\n')
1779
1780
1781     @stubs
1782     def test_update_name(self, stubs):
1783         capture_stdout = io.StringIO()
1784
1785         exited = arvados_cwl.main(
1786             ["--update-workflow", self.existing_workflow_uuid,
1787              "--debug", "--name", "testing 123",
1788              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1789             capture_stdout, sys.stderr, api_client=stubs.api)
1790         self.assertEqual(exited, 0)
1791
1792         body = {
1793             "workflow": {
1794                 "name": "testing 123",
1795                 "description": "",
1796                 "definition": self.expect_workflow,
1797             }
1798         }
1799         stubs.api.workflows().update.assert_called_with(
1800             uuid=self.existing_workflow_uuid,
1801             body=JsonDiffMatcher(body))
1802         self.assertEqual(capture_stdout.getvalue(),
1803                          self.existing_workflow_uuid + '\n')
1804
1805
1806     @stubs
1807     def test_create_collection_per_tool(self, stubs):
1808         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1809
1810         capture_stdout = io.StringIO()
1811
1812         exited = arvados_cwl.main(
1813             ["--create-workflow", "--debug",
1814              "--api=containers",
1815              "--project-uuid", project_uuid,
1816              "tests/collection_per_tool/collection_per_tool.cwl"],
1817             capture_stdout, sys.stderr, api_client=stubs.api)
1818         self.assertEqual(exited, 0)
1819
1820         toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1821         expect_workflow = StripYAMLComments(open(toolfile).read())
1822
1823         body = {
1824             "workflow": {
1825                 "owner_uuid": project_uuid,
1826                 "name": "collection_per_tool.cwl",
1827                 "description": "",
1828                 "definition": expect_workflow,
1829             }
1830         }
1831         stubs.api.workflows().create.assert_called_with(
1832             body=JsonDiffMatcher(body))
1833
1834         self.assertEqual(capture_stdout.getvalue(),
1835                          stubs.expect_workflow_uuid + '\n')
1836
1837 class TestTemplateInputs(unittest.TestCase):
1838     expect_template = {
1839         "components": {
1840             "inputs_test.cwl": {
1841                 'runtime_constraints': {
1842                     'docker_image': '999999999999999999999999999999d3+99',
1843                     'min_ram_mb_per_node': 1024
1844                 },
1845                 'script_parameters': {
1846                     'cwl:tool':
1847                     'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main',
1848                     'optionalFloatInput': None,
1849                     'fileInput': {
1850                         'type': 'File',
1851                         'dataclass': 'File',
1852                         'required': True,
1853                         'title': "It's a file; we expect to find some characters in it.",
1854                         'description': 'If there were anything further to say, it would be said here,\nor here.'
1855                     },
1856                     'floatInput': {
1857                         'type': 'float',
1858                         'dataclass': 'number',
1859                         'required': True,
1860                         'title': 'Floats like a duck',
1861                         'default': 0.1,
1862                         'value': 0.1,
1863                     },
1864                     'optionalFloatInput': {
1865                         'type': ['null', 'float'],
1866                         'dataclass': 'number',
1867                         'required': False,
1868                     },
1869                     'boolInput': {
1870                         'type': 'boolean',
1871                         'dataclass': 'boolean',
1872                         'required': True,
1873                         'title': 'True or false?',
1874                     },
1875                 },
1876                 'repository': 'arvados',
1877                 'script_version': 'master',
1878                 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1879                 'script': 'cwl-runner',
1880             },
1881         },
1882         "name": "inputs_test.cwl",
1883     }
1884
1885     @stubs
1886     def test_inputs_empty(self, stubs):
1887         exited = arvados_cwl.main(
1888             ["--create-template",
1889              "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1890             io.StringIO(), sys.stderr, api_client=stubs.api)
1891         self.assertEqual(exited, 0)
1892
1893         stubs.api.pipeline_templates().create.assert_called_with(
1894             body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1895
1896     @stubs
1897     def test_inputs(self, stubs):
1898         exited = arvados_cwl.main(
1899             ["--create-template",
1900              "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1901             io.StringIO(), sys.stderr, api_client=stubs.api)
1902         self.assertEqual(exited, 0)
1903
1904         expect_template = copy.deepcopy(self.expect_template)
1905         params = expect_template[
1906             "components"]["inputs_test.cwl"]["script_parameters"]
1907         params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1908         params["cwl:tool"] = 'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main'
1909         params["floatInput"]["value"] = 1.234
1910         params["boolInput"]["value"] = True
1911
1912         stubs.api.pipeline_templates().create.assert_called_with(
1913             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)