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