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