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