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