Merge branch '12186-cwl-no-nameroot' closes #12186
[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 import copy
6 import cStringIO
7 import functools
8 import hashlib
9 import json
10 import logging
11 import mock
12 import sys
13 import unittest
14
15 import arvados
16 import arvados.collection
17 import arvados_cwl
18 import arvados_cwl.runner
19 import arvados.keep
20
21 from .matcher import JsonDiffMatcher, StripYAMLComments
22 from .mock_discovery import get_rootDesc
23
24 import ruamel.yaml as yaml
25
26 _rootDesc = None
27
28 def stubs(func):
29     @functools.wraps(func)
30     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
31     @mock.patch("arvados.collection.KeepClient")
32     @mock.patch("arvados.keep.KeepClient")
33     @mock.patch("arvados.events.subscribe")
34     def wrapped(self, events, keep_client1, keep_client2, keepdocker, *args, **kwargs):
35         class Stubs:
36             pass
37         stubs = Stubs()
38         stubs.events = events
39         stubs.keepdocker = keepdocker
40
41         def putstub(p, **kwargs):
42             return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p))
43         keep_client1().put.side_effect = putstub
44         keep_client1.put.side_effect = putstub
45         keep_client2().put.side_effect = putstub
46         keep_client2.put.side_effect = putstub
47
48         stubs.keep_client = keep_client2
49         stubs.keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
50         stubs.fake_user_uuid = "zzzzz-tpzed-zzzzzzzzzzzzzzz"
51
52         stubs.api = mock.MagicMock()
53         stubs.api._rootDesc = get_rootDesc()
54
55         stubs.api.users().current().execute.return_value = {
56             "uuid": stubs.fake_user_uuid,
57         }
58         stubs.api.collections().list().execute.return_value = {"items": []}
59
60         class CollectionExecute(object):
61             def __init__(self, exe):
62                 self.exe = exe
63             def execute(self, num_retries=None):
64                 return self.exe
65
66         def collection_createstub(created_collections, body, ensure_unique_name=None):
67             mt = body["manifest_text"]
68             uuid = "zzzzz-4zz18-zzzzzzzzzzzzzz%d" % len(created_collections)
69             pdh = "%s+%i" % (hashlib.md5(mt).hexdigest(), len(mt))
70             created_collections[uuid] = {
71                 "uuid": uuid,
72                 "portable_data_hash": pdh,
73                 "manifest_text": mt
74             }
75             return CollectionExecute(created_collections[uuid])
76
77         def collection_getstub(created_collections, uuid):
78             for v in created_collections.itervalues():
79                 if uuid in (v["uuid"], v["portable_data_hash"]):
80                     return CollectionExecute(v)
81
82         created_collections = {
83             "99999999999999999999999999999998+99": {
84                 "uuid": "",
85                 "portable_data_hash": "99999999999999999999999999999998+99",
86                 "manifest_text": ". 99999999999999999999999999999998+99 0:0:file1.txt"
87             },
88             "99999999999999999999999999999994+99": {
89                 "uuid": "",
90                 "portable_data_hash": "99999999999999999999999999999994+99",
91                 "manifest_text": ". 99999999999999999999999999999994+99 0:0:expect_arvworkflow.cwl"
92             }
93         }
94         stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
95         stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
96
97         stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
98         stubs.api.jobs().create().execute.return_value = {
99             "uuid": stubs.expect_job_uuid,
100             "state": "Queued",
101         }
102
103         stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
104         stubs.api.container_requests().create().execute.return_value = {
105             "uuid": stubs.expect_container_request_uuid,
106             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
107             "state": "Queued"
108         }
109
110         stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
111         stubs.api.pipeline_templates().create().execute.return_value = {
112             "uuid": stubs.expect_pipeline_template_uuid,
113         }
114         stubs.expect_job_spec = {
115             'runtime_constraints': {
116                 'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
117                 'min_ram_mb_per_node': 1024
118             },
119             'script_parameters': {
120                 'x': {
121                     'basename': 'blorp.txt',
122                     'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
123                     'class': 'File'
124                 },
125                 'y': {
126                     'basename': '99999999999999999999999999999998+99',
127                     'location': 'keep:99999999999999999999999999999998+99',
128                     'class': 'Directory'
129                 },
130                 'z': {
131                     'basename': 'anonymous',
132                     "listing": [{
133                         "basename": "renamed.txt",
134                         "class": "File",
135                         "location": "keep:99999999999999999999999999999998+99/file1.txt"
136                     }],
137                     'class': 'Directory'
138                 },
139                 'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main'
140             },
141             'repository': 'arvados',
142             'script_version': 'master',
143             'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
144             'script': 'cwl-runner'
145         }
146         stubs.pipeline_component = stubs.expect_job_spec.copy()
147         stubs.expect_pipeline_instance = {
148             'name': 'submit_wf.cwl',
149             'state': 'RunningOnServer',
150             'owner_uuid': None,
151             "components": {
152                 "cwl-runner": {
153                     'runtime_constraints': {'docker_image': 'arvados/jobs:'+arvados_cwl.__version__, 'min_ram_mb_per_node': 1024},
154                     'script_parameters': {
155                         'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
156                         'x': {"value": {
157                             'basename': 'blorp.txt',
158                             'class': 'File',
159                             'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
160                             "size": 16
161                         }},
162                         'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
163                               'listing': [
164                                   {
165                                       'basename': 'renamed.txt',
166                                       'class': 'File', 'location':
167                                       'keep:99999999999999999999999999999998+99/file1.txt'
168                                   }
169                               ]}},
170                         'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main',
171                         'arv:enable_reuse': True,
172                         'arv:on_error': 'continue'
173                     },
174                     'repository': 'arvados',
175                     'script_version': 'master',
176                     'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
177                     'script': 'cwl-runner',
178                     'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
179                 }
180             }
181         }
182         stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
183         stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
184         stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
185         stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
186         stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
187             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
188             "state": "Queued"
189         }
190         stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
191         stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
192
193         with open("tests/wf/submit_wf_packed.cwl") as f:
194             expect_packed_workflow = yaml.round_trip_load(f)
195
196         stubs.expect_container_spec = {
197             'priority': 1,
198             'mounts': {
199                 '/var/spool/cwl': {
200                     'writable': True,
201                     'kind': 'collection'
202                 },
203                 '/var/lib/cwl/workflow.json': {
204                     'content': expect_packed_workflow,
205                     'kind': 'json'
206                 },
207                 'stdout': {
208                     'path': '/var/spool/cwl/cwl.output.json',
209                     'kind': 'file'
210                 },
211                 '/var/lib/cwl/cwl.input.json': {
212                     'kind': 'json',
213                     'content': {
214                         'y': {
215                             'basename': '99999999999999999999999999999998+99',
216                             'location': 'keep:99999999999999999999999999999998+99',
217                             'class': 'Directory'},
218                         'x': {
219                             'basename': u'blorp.txt',
220                             'class': 'File',
221                             'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
222                             "size": 16
223                         },
224                         'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
225                             {'basename': 'renamed.txt',
226                              'class': 'File',
227                              'location': 'keep:99999999999999999999999999999998+99/file1.txt'
228                             }
229                         ]}
230                     },
231                     'kind': 'json'
232                 }
233             },
234             'state': 'Committed',
235             'owner_uuid': None,
236             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
237                         '--enable-reuse', '--on-error=continue',
238                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
239             'name': 'submit_wf.cwl',
240             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
241             'output_path': '/var/spool/cwl',
242             'cwd': '/var/spool/cwl',
243             'runtime_constraints': {
244                 'API': True,
245                 'vcpus': 1,
246                 'ram': 1024*1024*1024
247             },
248             "properties": {}
249         }
250
251         stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
252         stubs.api.workflows().create().execute.return_value = {
253             "uuid": stubs.expect_workflow_uuid,
254         }
255         def update_mock(**kwargs):
256             stubs.updated_uuid = kwargs.get('uuid')
257             return mock.DEFAULT
258         stubs.api.workflows().update.side_effect = update_mock
259         stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
260             "uuid": stubs.updated_uuid,
261         }
262
263         return func(self, stubs, *args, **kwargs)
264     return wrapped
265
266
267 class TestSubmit(unittest.TestCase):
268     @mock.patch("arvados_cwl.runner.arv_docker_get_image")
269     @mock.patch("time.sleep")
270     @stubs
271     def test_submit(self, stubs, tm, arvdock):
272         capture_stdout = cStringIO.StringIO()
273         exited = arvados_cwl.main(
274             ["--submit", "--no-wait", "--api=jobs", "--debug",
275              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
276             capture_stdout, sys.stderr, api_client=stubs.api)
277         self.assertEqual(exited, 0)
278
279         stubs.api.collections().create.assert_has_calls([
280             mock.call(body=JsonDiffMatcher({
281                 'manifest_text':
282                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
283                 'replication_desired': None,
284                 'name': 'submit_tool.cwl dependencies',
285             }), ensure_unique_name=True),
286             mock.call(body=JsonDiffMatcher({
287                 'manifest_text':
288                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
289                 'replication_desired': None,
290                 'name': 'submit_wf.cwl input',
291             }), ensure_unique_name=True),
292             mock.call(body=JsonDiffMatcher({
293                 'manifest_text':
294                 '. 61df2ed9ee3eb7dd9b799e5ca35305fa+1217 0:1217:workflow.cwl\n',
295                 'replication_desired': None,
296                 'name': 'submit_wf.cwl',
297             }), ensure_unique_name=True)        ])
298
299         arvdock.assert_has_calls([
300             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
301             mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
302         ])
303
304         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
305         stubs.api.pipeline_instances().create.assert_called_with(
306             body=JsonDiffMatcher(expect_pipeline))
307         self.assertEqual(capture_stdout.getvalue(),
308                          stubs.expect_pipeline_uuid + '\n')
309
310
311     @mock.patch("time.sleep")
312     @stubs
313     def test_submit_no_reuse(self, stubs, tm):
314         capture_stdout = cStringIO.StringIO()
315         exited = arvados_cwl.main(
316             ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
317              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
318             capture_stdout, sys.stderr, api_client=stubs.api)
319         self.assertEqual(exited, 0)
320
321         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
322         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
323
324         stubs.api.pipeline_instances().create.assert_called_with(
325             body=JsonDiffMatcher(expect_pipeline))
326         self.assertEqual(capture_stdout.getvalue(),
327                          stubs.expect_pipeline_uuid + '\n')
328
329     @mock.patch("time.sleep")
330     @stubs
331     def test_submit_on_error(self, stubs, tm):
332         capture_stdout = cStringIO.StringIO()
333         exited = arvados_cwl.main(
334             ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
335              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
336             capture_stdout, sys.stderr, api_client=stubs.api)
337         self.assertEqual(exited, 0)
338
339         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
340         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
341
342         stubs.api.pipeline_instances().create.assert_called_with(
343             body=JsonDiffMatcher(expect_pipeline))
344         self.assertEqual(capture_stdout.getvalue(),
345                          stubs.expect_pipeline_uuid + '\n')
346
347
348     @mock.patch("time.sleep")
349     @stubs
350     def test_submit_runner_ram(self, stubs, tm):
351         capture_stdout = cStringIO.StringIO()
352         exited = arvados_cwl.main(
353             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
354              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
355             capture_stdout, sys.stderr, api_client=stubs.api)
356         self.assertEqual(exited, 0)
357
358         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
359         expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
360
361         stubs.api.pipeline_instances().create.assert_called_with(
362             body=JsonDiffMatcher(expect_pipeline))
363         self.assertEqual(capture_stdout.getvalue(),
364                          stubs.expect_pipeline_uuid + '\n')
365
366
367     @mock.patch("time.sleep")
368     @stubs
369     def test_submit_invalid_runner_ram(self, stubs, tm):
370         capture_stdout = cStringIO.StringIO()
371         exited = arvados_cwl.main(
372             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
373              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
374             capture_stdout, sys.stderr, api_client=stubs.api)
375         self.assertEqual(exited, 1)
376
377     @mock.patch("time.sleep")
378     @stubs
379     def test_submit_output_name(self, stubs, tm):
380         output_name = "test_output_name"
381
382         capture_stdout = cStringIO.StringIO()
383         exited = arvados_cwl.main(
384             ["--submit", "--no-wait", "--debug", "--output-name", output_name,
385              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
386             capture_stdout, sys.stderr, api_client=stubs.api)
387         self.assertEqual(exited, 0)
388
389         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
390         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
391
392         stubs.api.pipeline_instances().create.assert_called_with(
393             body=JsonDiffMatcher(expect_pipeline))
394         self.assertEqual(capture_stdout.getvalue(),
395                          stubs.expect_pipeline_uuid + '\n')
396
397
398     @mock.patch("time.sleep")
399     @stubs
400     def test_submit_pipeline_name(self, stubs, tm):
401         capture_stdout = cStringIO.StringIO()
402         exited = arvados_cwl.main(
403             ["--submit", "--no-wait", "--debug", "--name=hello job 123",
404              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
405             capture_stdout, sys.stderr, api_client=stubs.api)
406         self.assertEqual(exited, 0)
407
408         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
409         expect_pipeline["name"] = "hello job 123"
410
411         stubs.api.pipeline_instances().create.assert_called_with(
412             body=JsonDiffMatcher(expect_pipeline))
413         self.assertEqual(capture_stdout.getvalue(),
414                          stubs.expect_pipeline_uuid + '\n')
415
416     @mock.patch("time.sleep")
417     @stubs
418     def test_submit_output_tags(self, stubs, tm):
419         output_tags = "tag0,tag1,tag2"
420
421         capture_stdout = cStringIO.StringIO()
422         exited = arvados_cwl.main(
423             ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
424              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
425             capture_stdout, sys.stderr, api_client=stubs.api)
426         self.assertEqual(exited, 0)
427
428         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
429         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
430
431         stubs.api.pipeline_instances().create.assert_called_with(
432             body=JsonDiffMatcher(expect_pipeline))
433         self.assertEqual(capture_stdout.getvalue(),
434                          stubs.expect_pipeline_uuid + '\n')
435
436     @mock.patch("time.sleep")
437     @stubs
438     def test_submit_with_project_uuid(self, stubs, tm):
439         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
440
441         exited = arvados_cwl.main(
442             ["--submit", "--no-wait",
443              "--project-uuid", project_uuid,
444              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
445             sys.stdout, sys.stderr, api_client=stubs.api)
446         self.assertEqual(exited, 0)
447
448         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
449         expect_pipeline["owner_uuid"] = project_uuid
450         stubs.api.pipeline_instances().create.assert_called_with(
451             body=JsonDiffMatcher(expect_pipeline))
452
453     @stubs
454     def test_submit_container(self, stubs):
455         capture_stdout = cStringIO.StringIO()
456         try:
457             exited = arvados_cwl.main(
458                 ["--submit", "--no-wait", "--api=containers", "--debug",
459                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
460                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
461             self.assertEqual(exited, 0)
462         except:
463             logging.exception("")
464
465         stubs.api.collections().create.assert_has_calls([
466             mock.call(body=JsonDiffMatcher({
467                 'manifest_text':
468                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
469                 'replication_desired': None,
470                 'name': 'submit_tool.cwl dependencies',
471             }), ensure_unique_name=True),
472             mock.call(body=JsonDiffMatcher({
473                 'manifest_text':
474                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
475                 'replication_desired': None,
476                 'name': 'submit_wf.cwl input',
477             }), ensure_unique_name=True)])
478
479         expect_container = copy.deepcopy(stubs.expect_container_spec)
480         stubs.api.container_requests().create.assert_called_with(
481             body=JsonDiffMatcher(expect_container))
482         self.assertEqual(capture_stdout.getvalue(),
483                          stubs.expect_container_request_uuid + '\n')
484
485     @stubs
486     def test_submit_container_no_reuse(self, stubs):
487         capture_stdout = cStringIO.StringIO()
488         try:
489             exited = arvados_cwl.main(
490                 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
491                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
492                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
493             self.assertEqual(exited, 0)
494         except:
495             logging.exception("")
496
497         expect_container = copy.deepcopy(stubs.expect_container_spec)
498         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
499                                                   '--disable-reuse', '--on-error=continue',
500                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
501
502         stubs.api.container_requests().create.assert_called_with(
503             body=JsonDiffMatcher(expect_container))
504         self.assertEqual(capture_stdout.getvalue(),
505                          stubs.expect_container_request_uuid + '\n')
506
507
508     @stubs
509     def test_submit_container_on_error(self, stubs):
510         capture_stdout = cStringIO.StringIO()
511         try:
512             exited = arvados_cwl.main(
513                 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
514                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
515                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
516             self.assertEqual(exited, 0)
517         except:
518             logging.exception("")
519
520         expect_container = copy.deepcopy(stubs.expect_container_spec)
521         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
522                                                   '--enable-reuse', '--on-error=stop',
523                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
524
525         stubs.api.container_requests().create.assert_called_with(
526             body=JsonDiffMatcher(expect_container))
527         self.assertEqual(capture_stdout.getvalue(),
528                          stubs.expect_container_request_uuid + '\n')
529
530     @stubs
531     def test_submit_container_output_name(self, stubs):
532         output_name = "test_output_name"
533
534         capture_stdout = cStringIO.StringIO()
535         try:
536             exited = arvados_cwl.main(
537                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
538                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
539                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
540             self.assertEqual(exited, 0)
541         except:
542             logging.exception("")
543
544         expect_container = copy.deepcopy(stubs.expect_container_spec)
545         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
546                                                   "--output-name="+output_name, '--enable-reuse', '--on-error=continue',
547                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
548         expect_container["output_name"] = output_name
549
550         stubs.api.container_requests().create.assert_called_with(
551             body=JsonDiffMatcher(expect_container))
552         self.assertEqual(capture_stdout.getvalue(),
553                          stubs.expect_container_request_uuid + '\n')
554
555
556     @stubs
557     def test_submit_container_output_ttl(self, stubs):
558         capture_stdout = cStringIO.StringIO()
559         try:
560             exited = arvados_cwl.main(
561                 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
562                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
563                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
564             self.assertEqual(exited, 0)
565         except:
566             logging.exception("")
567
568         expect_container = copy.deepcopy(stubs.expect_container_spec)
569         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
570                                        '--enable-reuse', '--on-error=continue',
571                                        "--intermediate-output-ttl=3600",
572                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
573
574         stubs.api.container_requests().create.assert_called_with(
575             body=JsonDiffMatcher(expect_container))
576         self.assertEqual(capture_stdout.getvalue(),
577                          stubs.expect_container_request_uuid + '\n')
578
579     @stubs
580     def test_submit_container_trash_intermediate(self, stubs):
581         capture_stdout = cStringIO.StringIO()
582         try:
583             exited = arvados_cwl.main(
584                 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
585                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
586                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
587             self.assertEqual(exited, 0)
588         except:
589             logging.exception("")
590
591         expect_container = copy.deepcopy(stubs.expect_container_spec)
592         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
593                                        '--enable-reuse', '--on-error=continue',
594                                        "--trash-intermediate",
595                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
596
597         stubs.api.container_requests().create.assert_called_with(
598             body=JsonDiffMatcher(expect_container))
599         self.assertEqual(capture_stdout.getvalue(),
600                          stubs.expect_container_request_uuid + '\n')
601
602     @stubs
603     def test_submit_container_output_tags(self, stubs):
604         output_tags = "tag0,tag1,tag2"
605
606         capture_stdout = cStringIO.StringIO()
607         try:
608             exited = arvados_cwl.main(
609                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
610                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
611                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
612             self.assertEqual(exited, 0)
613         except:
614             logging.exception("")
615
616         expect_container = copy.deepcopy(stubs.expect_container_spec)
617         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
618                                                   "--output-tags="+output_tags, '--enable-reuse', '--on-error=continue',
619                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
620
621         stubs.api.container_requests().create.assert_called_with(
622             body=JsonDiffMatcher(expect_container))
623         self.assertEqual(capture_stdout.getvalue(),
624                          stubs.expect_container_request_uuid + '\n')
625
626     @stubs
627     def test_submit_container_runner_ram(self, stubs):
628         capture_stdout = cStringIO.StringIO()
629         try:
630             exited = arvados_cwl.main(
631                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
632                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
633                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
634             self.assertEqual(exited, 0)
635         except:
636             logging.exception("")
637
638         expect_container = copy.deepcopy(stubs.expect_container_spec)
639         expect_container["runtime_constraints"]["ram"] = 2048*1024*1024
640
641         stubs.api.container_requests().create.assert_called_with(
642             body=JsonDiffMatcher(expect_container))
643         self.assertEqual(capture_stdout.getvalue(),
644                          stubs.expect_container_request_uuid + '\n')
645
646     @mock.patch("arvados.collection.CollectionReader")
647     @mock.patch("time.sleep")
648     @stubs
649     def test_submit_file_keepref(self, stubs, tm, collectionReader):
650         capture_stdout = cStringIO.StringIO()
651         exited = arvados_cwl.main(
652             ["--submit", "--no-wait", "--api=containers", "--debug",
653              "tests/wf/submit_keepref_wf.cwl"],
654             capture_stdout, sys.stderr, api_client=stubs.api)
655         self.assertEqual(exited, 0)
656
657
658     @mock.patch("arvados.collection.CollectionReader")
659     @mock.patch("time.sleep")
660     @stubs
661     def test_submit_keepref(self, stubs, tm, reader):
662         capture_stdout = cStringIO.StringIO()
663
664         with open("tests/wf/expect_arvworkflow.cwl") as f:
665             reader().open().__enter__().read.return_value = f.read()
666
667         exited = arvados_cwl.main(
668             ["--submit", "--no-wait", "--api=containers", "--debug",
669              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
670             capture_stdout, sys.stderr, api_client=stubs.api)
671         self.assertEqual(exited, 0)
672
673         expect_container = {
674             'priority': 1,
675             'mounts': {
676                 '/var/spool/cwl': {
677                     'writable': True,
678                     'kind': 'collection'
679                 },
680                 'stdout': {
681                     'path': '/var/spool/cwl/cwl.output.json',
682                     'kind': 'file'
683                 },
684                 '/var/lib/cwl/workflow': {
685                     'portable_data_hash': '99999999999999999999999999999994+99',
686                     'kind': 'collection'
687                 },
688                 '/var/lib/cwl/cwl.input.json': {
689                     'content': {
690                         'x': 'XxX'
691                     },
692                     'kind': 'json'
693                 }
694             }, 'state': 'Committed',
695             'owner_uuid': None,
696             'output_path': '/var/spool/cwl',
697             'name': 'expect_arvworkflow.cwl#main',
698             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
699             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
700                         '--enable-reuse', '--on-error=continue',
701                         '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
702             'cwd': '/var/spool/cwl',
703             'runtime_constraints': {
704                 'API': True,
705                 'vcpus': 1,
706                 'ram': 1073741824
707             },
708             "properties": {}
709         }
710
711         stubs.api.container_requests().create.assert_called_with(
712             body=JsonDiffMatcher(expect_container))
713         self.assertEqual(capture_stdout.getvalue(),
714                          stubs.expect_container_request_uuid + '\n')
715
716
717     @mock.patch("arvados.collection.CollectionReader")
718     @mock.patch("time.sleep")
719     @stubs
720     def test_submit_jobs_keepref(self, stubs, tm, reader):
721         capture_stdout = cStringIO.StringIO()
722
723         with open("tests/wf/expect_arvworkflow.cwl") as f:
724             reader().open().__enter__().read.return_value = f.read()
725
726         exited = arvados_cwl.main(
727             ["--submit", "--no-wait", "--api=jobs", "--debug",
728              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
729             capture_stdout, sys.stderr, api_client=stubs.api)
730         self.assertEqual(exited, 0)
731
732         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
733         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
734         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
735         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
736         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
737         expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
738         stubs.api.pipeline_instances().create.assert_called_with(
739             body=JsonDiffMatcher(expect_pipeline))
740
741     @mock.patch("time.sleep")
742     @stubs
743     def test_submit_arvworkflow(self, stubs, tm):
744         capture_stdout = cStringIO.StringIO()
745
746         with open("tests/wf/expect_arvworkflow.cwl") as f:
747             stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
748
749         exited = arvados_cwl.main(
750             ["--submit", "--no-wait", "--api=containers", "--debug",
751              "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
752             capture_stdout, sys.stderr, api_client=stubs.api)
753         self.assertEqual(exited, 0)
754
755         expect_container = {
756             'priority': 1,
757             'mounts': {
758                 '/var/spool/cwl': {
759                     'writable': True,
760                     'kind': 'collection'
761                 },
762                 'stdout': {
763                     'path': '/var/spool/cwl/cwl.output.json',
764                     'kind': 'file'
765                 },
766                 '/var/lib/cwl/workflow.json': {
767                     'kind': 'json',
768                     'content': {
769                         'cwlVersion': 'v1.0',
770                         '$graph': [
771                             {
772                                 'id': '#main',
773                                 'inputs': [
774                                     {'type': 'string', 'id': '#main/x'}
775                                 ],
776                                 'steps': [
777                                     {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
778                                      'run': '#submit_tool.cwl',
779                                      'id': '#main/step1',
780                                      'out': []}
781                                 ],
782                                 'class': 'Workflow',
783                                 'outputs': []
784                             },
785                             {
786                                 'inputs': [
787                                     {
788                                         'inputBinding': {'position': 1},
789                                         'type': 'string',
790                                         'id': '#submit_tool.cwl/x'}
791                                 ],
792                                 'requirements': [
793                                     {'dockerPull': 'debian:8', 'class': 'DockerRequirement'}
794                                 ],
795                                 'id': '#submit_tool.cwl',
796                                 'outputs': [],
797                                 'baseCommand': 'cat',
798                                 'class': 'CommandLineTool'
799                             }
800                         ]
801                     }
802                 },
803                 '/var/lib/cwl/cwl.input.json': {
804                     'content': {
805                         'x': 'XxX'
806                     },
807                     'kind': 'json'
808                 }
809             }, 'state': 'Committed',
810             'owner_uuid': None,
811             'output_path': '/var/spool/cwl',
812             'name': 'a test workflow',
813             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
814             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
815                         '--enable-reuse', '--on-error=continue',
816                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
817             'cwd': '/var/spool/cwl',
818             'runtime_constraints': {
819                 'API': True,
820                 'vcpus': 1,
821                 'ram': 1073741824
822             },
823             "properties": {
824                 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
825             }
826         }
827
828         stubs.api.container_requests().create.assert_called_with(
829             body=JsonDiffMatcher(expect_container))
830         self.assertEqual(capture_stdout.getvalue(),
831                          stubs.expect_container_request_uuid + '\n')
832
833
834     @stubs
835     def test_submit_container_name(self, stubs):
836         capture_stdout = cStringIO.StringIO()
837         try:
838             exited = arvados_cwl.main(
839                 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
840                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
841                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
842             self.assertEqual(exited, 0)
843         except:
844             logging.exception("")
845
846         expect_container = copy.deepcopy(stubs.expect_container_spec)
847         expect_container["name"] = "hello container 123"
848
849         stubs.api.container_requests().create.assert_called_with(
850             body=JsonDiffMatcher(expect_container))
851         self.assertEqual(capture_stdout.getvalue(),
852                          stubs.expect_container_request_uuid + '\n')
853
854
855     @stubs
856     def test_submit_container_project(self, stubs):
857         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
858         capture_stdout = cStringIO.StringIO()
859         try:
860             exited = arvados_cwl.main(
861                 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
862                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
863                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
864             self.assertEqual(exited, 0)
865         except:
866             logging.exception("")
867
868         expect_container = copy.deepcopy(stubs.expect_container_spec)
869         expect_container["owner_uuid"] = project_uuid
870         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
871                                        '--enable-reuse', '--on-error=continue', '--project-uuid='+project_uuid,
872                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
873
874         stubs.api.container_requests().create.assert_called_with(
875             body=JsonDiffMatcher(expect_container))
876         self.assertEqual(capture_stdout.getvalue(),
877                          stubs.expect_container_request_uuid + '\n')
878
879
880     @stubs
881     def test_submit_job_runner_image(self, stubs):
882         capture_stdout = cStringIO.StringIO()
883         try:
884             exited = arvados_cwl.main(
885                 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
886                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
887                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
888             self.assertEqual(exited, 0)
889         except:
890             logging.exception("")
891
892         stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "arvados/jobs:123"
893
894         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
895         stubs.api.pipeline_instances().create.assert_called_with(
896             body=JsonDiffMatcher(expect_pipeline))
897         self.assertEqual(capture_stdout.getvalue(),
898                          stubs.expect_pipeline_uuid + '\n')
899
900     @stubs
901     def test_submit_container_runner_image(self, stubs):
902         capture_stdout = cStringIO.StringIO()
903         try:
904             exited = arvados_cwl.main(
905                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
906                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
907                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
908             self.assertEqual(exited, 0)
909         except:
910             logging.exception("")
911
912         stubs.expect_container_spec["container_image"] = "arvados/jobs:123"
913
914         expect_container = copy.deepcopy(stubs.expect_container_spec)
915         stubs.api.container_requests().create.assert_called_with(
916             body=JsonDiffMatcher(expect_container))
917         self.assertEqual(capture_stdout.getvalue(),
918                          stubs.expect_container_request_uuid + '\n')
919
920
921     @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
922     @mock.patch("cwltool.docker.get_image")
923     @mock.patch("arvados.api")
924     def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
925         arvrunner = mock.MagicMock()
926         arvrunner.project_uuid = ""
927         api.return_value = mock.MagicMock()
928         arvrunner.api = api.return_value
929         arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
930                                                                         "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
931                                                                         "link_class": "docker_image_repo+tag",
932                                                                         "name": "arvados/jobs:"+arvados_cwl.__version__,
933                                                                         "owner_uuid": "",
934                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
935                                                             {"items": [{"created_at": "",
936                                                                         "head_uuid": "",
937                                                                         "link_class": "docker_image_hash",
938                                                                         "name": "123456",
939                                                                         "owner_uuid": "",
940                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
941         )
942         find_one_image_hash.return_value = "123456"
943
944         arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
945                                                                               "owner_uuid": "",
946                                                                               "manifest_text": "",
947                                                                               "properties": ""
948                                                                           }], "items_available": 1, "offset": 0},)
949         arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
950         self.assertEqual("arvados/jobs:"+arvados_cwl.__version__,
951                          arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
952
953 class TestCreateTemplate(unittest.TestCase):
954     existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
955
956     def _adjust_script_params(self, expect_component):
957         expect_component['script_parameters']['x'] = {
958             'dataclass': 'File',
959             'required': True,
960             'type': 'File',
961             'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
962         }
963         expect_component['script_parameters']['y'] = {
964             'dataclass': 'Collection',
965             'required': True,
966             'type': 'Directory',
967             'value': '99999999999999999999999999999998+99',
968         }
969         expect_component['script_parameters']['z'] = {
970             'dataclass': 'Collection',
971             'required': True,
972             'type': 'Directory',
973         }
974
975     @stubs
976     def test_create(self, stubs):
977         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
978
979         capture_stdout = cStringIO.StringIO()
980
981         exited = arvados_cwl.main(
982             ["--create-workflow", "--debug",
983              "--api=jobs",
984              "--project-uuid", project_uuid,
985              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
986             capture_stdout, sys.stderr, api_client=stubs.api)
987         self.assertEqual(exited, 0)
988
989         stubs.api.pipeline_instances().create.refute_called()
990         stubs.api.jobs().create.refute_called()
991
992         expect_component = copy.deepcopy(stubs.expect_job_spec)
993         self._adjust_script_params(expect_component)
994         expect_template = {
995             "components": {
996                 "submit_wf.cwl": expect_component,
997             },
998             "name": "submit_wf.cwl",
999             "owner_uuid": project_uuid,
1000         }
1001         stubs.api.pipeline_templates().create.assert_called_with(
1002             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1003
1004         self.assertEqual(capture_stdout.getvalue(),
1005                          stubs.expect_pipeline_template_uuid + '\n')
1006
1007
1008     @stubs
1009     def test_create_name(self, stubs):
1010         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1011
1012         capture_stdout = cStringIO.StringIO()
1013
1014         exited = arvados_cwl.main(
1015             ["--create-workflow", "--debug",
1016              "--project-uuid", project_uuid,
1017              "--api=jobs",
1018              "--name", "testing 123",
1019              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1020             capture_stdout, sys.stderr, api_client=stubs.api)
1021         self.assertEqual(exited, 0)
1022
1023         stubs.api.pipeline_instances().create.refute_called()
1024         stubs.api.jobs().create.refute_called()
1025
1026         expect_component = copy.deepcopy(stubs.expect_job_spec)
1027         self._adjust_script_params(expect_component)
1028         expect_template = {
1029             "components": {
1030                 "testing 123": expect_component,
1031             },
1032             "name": "testing 123",
1033             "owner_uuid": project_uuid,
1034         }
1035         stubs.api.pipeline_templates().create.assert_called_with(
1036             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1037
1038         self.assertEqual(capture_stdout.getvalue(),
1039                          stubs.expect_pipeline_template_uuid + '\n')
1040
1041
1042     @stubs
1043     def test_update_name(self, stubs):
1044         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1045
1046         capture_stdout = cStringIO.StringIO()
1047
1048         exited = arvados_cwl.main(
1049             ["--update-workflow", self.existing_template_uuid,
1050              "--debug",
1051              "--project-uuid", project_uuid,
1052              "--api=jobs",
1053              "--name", "testing 123",
1054              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1055             capture_stdout, sys.stderr, api_client=stubs.api)
1056         self.assertEqual(exited, 0)
1057
1058         stubs.api.pipeline_instances().create.refute_called()
1059         stubs.api.jobs().create.refute_called()
1060
1061         expect_component = copy.deepcopy(stubs.expect_job_spec)
1062         self._adjust_script_params(expect_component)
1063         expect_template = {
1064             "components": {
1065                 "testing 123": expect_component,
1066             },
1067             "name": "testing 123",
1068             "owner_uuid": project_uuid,
1069         }
1070         stubs.api.pipeline_templates().create.refute_called()
1071         stubs.api.pipeline_templates().update.assert_called_with(
1072             body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1073
1074         self.assertEqual(capture_stdout.getvalue(),
1075                          self.existing_template_uuid + '\n')
1076
1077
1078 class TestCreateWorkflow(unittest.TestCase):
1079     existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1080     expect_workflow = StripYAMLComments(
1081         open("tests/wf/expect_packed.cwl").read())
1082
1083     @stubs
1084     def test_create(self, stubs):
1085         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1086
1087         capture_stdout = cStringIO.StringIO()
1088
1089         exited = arvados_cwl.main(
1090             ["--create-workflow", "--debug",
1091              "--api=containers",
1092              "--project-uuid", project_uuid,
1093              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1094             capture_stdout, sys.stderr, api_client=stubs.api)
1095         self.assertEqual(exited, 0)
1096
1097         stubs.api.pipeline_templates().create.refute_called()
1098         stubs.api.container_requests().create.refute_called()
1099
1100         body = {
1101             "workflow": {
1102                 "owner_uuid": project_uuid,
1103                 "name": "submit_wf.cwl",
1104                 "description": "",
1105                 "definition": self.expect_workflow,
1106             }
1107         }
1108         stubs.api.workflows().create.assert_called_with(
1109             body=JsonDiffMatcher(body))
1110
1111         self.assertEqual(capture_stdout.getvalue(),
1112                          stubs.expect_workflow_uuid + '\n')
1113
1114
1115     @stubs
1116     def test_create_name(self, stubs):
1117         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1118
1119         capture_stdout = cStringIO.StringIO()
1120
1121         exited = arvados_cwl.main(
1122             ["--create-workflow", "--debug",
1123              "--api=containers",
1124              "--project-uuid", project_uuid,
1125              "--name", "testing 123",
1126              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1127             capture_stdout, sys.stderr, api_client=stubs.api)
1128         self.assertEqual(exited, 0)
1129
1130         stubs.api.pipeline_templates().create.refute_called()
1131         stubs.api.container_requests().create.refute_called()
1132
1133         body = {
1134             "workflow": {
1135                 "owner_uuid": project_uuid,
1136                 "name": "testing 123",
1137                 "description": "",
1138                 "definition": self.expect_workflow,
1139             }
1140         }
1141         stubs.api.workflows().create.assert_called_with(
1142             body=JsonDiffMatcher(body))
1143
1144         self.assertEqual(capture_stdout.getvalue(),
1145                          stubs.expect_workflow_uuid + '\n')
1146
1147     @stubs
1148     def test_incompatible_api(self, stubs):
1149         capture_stderr = cStringIO.StringIO()
1150         logging.getLogger('arvados.cwl-runner').addHandler(
1151             logging.StreamHandler(capture_stderr))
1152
1153         exited = arvados_cwl.main(
1154             ["--update-workflow", self.existing_workflow_uuid,
1155              "--api=jobs",
1156              "--debug",
1157              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1158             sys.stderr, sys.stderr, api_client=stubs.api)
1159         self.assertEqual(exited, 1)
1160         self.assertRegexpMatches(
1161             capture_stderr.getvalue(),
1162             "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1163
1164     @stubs
1165     def test_update(self, stubs):
1166         capture_stdout = cStringIO.StringIO()
1167
1168         exited = arvados_cwl.main(
1169             ["--update-workflow", self.existing_workflow_uuid,
1170              "--debug",
1171              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1172             capture_stdout, sys.stderr, api_client=stubs.api)
1173         self.assertEqual(exited, 0)
1174
1175         body = {
1176             "workflow": {
1177                 "name": "submit_wf.cwl",
1178                 "description": "",
1179                 "definition": self.expect_workflow,
1180             }
1181         }
1182         stubs.api.workflows().update.assert_called_with(
1183             uuid=self.existing_workflow_uuid,
1184             body=JsonDiffMatcher(body))
1185         self.assertEqual(capture_stdout.getvalue(),
1186                          self.existing_workflow_uuid + '\n')
1187
1188
1189     @stubs
1190     def test_update_name(self, stubs):
1191         capture_stdout = cStringIO.StringIO()
1192
1193         exited = arvados_cwl.main(
1194             ["--update-workflow", self.existing_workflow_uuid,
1195              "--debug", "--name", "testing 123",
1196              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1197             capture_stdout, sys.stderr, api_client=stubs.api)
1198         self.assertEqual(exited, 0)
1199
1200         body = {
1201             "workflow": {
1202                 "name": "testing 123",
1203                 "description": "",
1204                 "definition": self.expect_workflow,
1205             }
1206         }
1207         stubs.api.workflows().update.assert_called_with(
1208             uuid=self.existing_workflow_uuid,
1209             body=JsonDiffMatcher(body))
1210         self.assertEqual(capture_stdout.getvalue(),
1211                          self.existing_workflow_uuid + '\n')
1212
1213
1214     @stubs
1215     def test_create_collection_per_tool(self, stubs):
1216         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1217
1218         capture_stdout = cStringIO.StringIO()
1219
1220         exited = arvados_cwl.main(
1221             ["--create-workflow", "--debug",
1222              "--api=containers",
1223              "--project-uuid", project_uuid,
1224              "tests/collection_per_tool/collection_per_tool.cwl"],
1225             capture_stdout, sys.stderr, api_client=stubs.api)
1226         self.assertEqual(exited, 0)
1227
1228         toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1229         expect_workflow = StripYAMLComments(open(toolfile).read())
1230
1231         body = {
1232             "workflow": {
1233                 "owner_uuid": project_uuid,
1234                 "name": "collection_per_tool.cwl",
1235                 "description": "",
1236                 "definition": expect_workflow,
1237             }
1238         }
1239         stubs.api.workflows().create.assert_called_with(
1240             body=JsonDiffMatcher(body))
1241
1242         self.assertEqual(capture_stdout.getvalue(),
1243                          stubs.expect_workflow_uuid + '\n')
1244
1245 class TestTemplateInputs(unittest.TestCase):
1246     expect_template = {
1247         "components": {
1248             "inputs_test.cwl": {
1249                 'runtime_constraints': {
1250                     'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
1251                     'min_ram_mb_per_node': 1024
1252                 },
1253                 'script_parameters': {
1254                     'cwl:tool':
1255                     '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main',
1256                     'optionalFloatInput': None,
1257                     'fileInput': {
1258                         'type': 'File',
1259                         'dataclass': 'File',
1260                         'required': True,
1261                         'title': "It's a file; we expect to find some characters in it.",
1262                         'description': 'If there were anything further to say, it would be said here,\nor here.'
1263                     },
1264                     'floatInput': {
1265                         'type': 'float',
1266                         'dataclass': 'number',
1267                         'required': True,
1268                         'title': 'Floats like a duck',
1269                         'default': 0.1,
1270                         'value': 0.1,
1271                     },
1272                     'optionalFloatInput': {
1273                         'type': ['null', 'float'],
1274                         'dataclass': 'number',
1275                         'required': False,
1276                     },
1277                     'boolInput': {
1278                         'type': 'boolean',
1279                         'dataclass': 'boolean',
1280                         'required': True,
1281                         'title': 'True or false?',
1282                     },
1283                 },
1284                 'repository': 'arvados',
1285                 'script_version': 'master',
1286                 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1287                 'script': 'cwl-runner',
1288             },
1289         },
1290         "name": "inputs_test.cwl",
1291     }
1292
1293     @stubs
1294     def test_inputs_empty(self, stubs):
1295         exited = arvados_cwl.main(
1296             ["--create-template",
1297              "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1298             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1299         self.assertEqual(exited, 0)
1300
1301         stubs.api.pipeline_templates().create.assert_called_with(
1302             body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1303
1304     @stubs
1305     def test_inputs(self, stubs):
1306         exited = arvados_cwl.main(
1307             ["--create-template",
1308              "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1309             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1310         self.assertEqual(exited, 0)
1311
1312         expect_template = copy.deepcopy(self.expect_template)
1313         params = expect_template[
1314             "components"]["inputs_test.cwl"]["script_parameters"]
1315         params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1316         params["cwl:tool"] = '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main'
1317         params["floatInput"]["value"] = 1.234
1318         params["boolInput"]["value"] = True
1319
1320         stubs.api.pipeline_templates().create.assert_called_with(
1321             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)