13306: test_submit additional fixes for py2 unicode support
[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         self.assertEqual(exited, 0)
338
339         stubs.api.collections().create.assert_has_calls([
340             mock.call(body=JsonDiffMatcher({
341                 'manifest_text':
342                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
343                 'replication_desired': None,
344                 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
345             }), ensure_unique_name=False),
346             mock.call(body=JsonDiffMatcher({
347                 'manifest_text':
348                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
349                 'replication_desired': None,
350                 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
351             }), ensure_unique_name=False),
352             mock.call(body=JsonDiffMatcher({
353                 'manifest_text':
354                 ". 68089141fbf7e020ac90a9d6a575bc8f+1312 0:1312:workflow.cwl\n",
355                 'replication_desired': None,
356                 'name': 'submit_wf.cwl',
357             }), ensure_unique_name=True)        ])
358
359         arvdock.assert_has_calls([
360             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
361             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8", 'http://arvados.org/cwl#dockerCollectionPDH': '999999999999999999999999999999d4+99'}, True, None),
362             mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
363         ])
364
365         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
366         stubs.api.pipeline_instances().create.assert_called_with(
367             body=JsonDiffMatcher(expect_pipeline))
368         self.assertEqual(stubs.capture_stdout.getvalue(),
369                          stubs.expect_pipeline_uuid + '\n')
370
371
372     @mock.patch("time.sleep")
373     @stubs
374     def test_submit_no_reuse(self, stubs, tm):
375         exited = arvados_cwl.main(
376             ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
377              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
378             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
379         self.assertEqual(exited, 0)
380
381         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
382         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
383         expect_pipeline["properties"] = {"run_options": {"enable_job_reuse": False}}
384
385         stubs.api.pipeline_instances().create.assert_called_with(
386             body=JsonDiffMatcher(expect_pipeline))
387         self.assertEqual(stubs.capture_stdout.getvalue(),
388                          stubs.expect_pipeline_uuid + '\n')
389
390     @stubs
391     def test_error_when_multiple_storage_classes_specified(self, stubs):
392         storage_classes = "foo,bar"
393         exited = arvados_cwl.main(
394                 ["--debug", "--storage-classes", storage_classes,
395                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
396                 sys.stdin, sys.stderr, api_client=stubs.api)
397         self.assertEqual(exited, 1)
398
399     @mock.patch("time.sleep")
400     @stubs
401     def test_submit_on_error(self, stubs, tm):
402         exited = arvados_cwl.main(
403             ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
404              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
405             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
406         self.assertEqual(exited, 0)
407
408         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
409         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
410
411         stubs.api.pipeline_instances().create.assert_called_with(
412             body=JsonDiffMatcher(expect_pipeline))
413         self.assertEqual(stubs.capture_stdout.getvalue(),
414                          stubs.expect_pipeline_uuid + '\n')
415
416
417     @mock.patch("time.sleep")
418     @stubs
419     def test_submit_runner_ram(self, stubs, tm):
420         exited = arvados_cwl.main(
421             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
422              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
423             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
424         self.assertEqual(exited, 0)
425
426         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
427         expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
428
429         stubs.api.pipeline_instances().create.assert_called_with(
430             body=JsonDiffMatcher(expect_pipeline))
431         self.assertEqual(stubs.capture_stdout.getvalue(),
432                          stubs.expect_pipeline_uuid + '\n')
433
434
435     @mock.patch("time.sleep")
436     @stubs
437     def test_submit_invalid_runner_ram(self, stubs, tm):
438         exited = arvados_cwl.main(
439             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
440              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
441             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
442         self.assertEqual(exited, 1)
443
444     @mock.patch("time.sleep")
445     @stubs
446     def test_submit_output_name(self, stubs, tm):
447         output_name = "test_output_name"
448
449         exited = arvados_cwl.main(
450             ["--submit", "--no-wait", "--debug", "--output-name", output_name,
451              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
452             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
453         self.assertEqual(exited, 0)
454
455         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
456         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
457
458         stubs.api.pipeline_instances().create.assert_called_with(
459             body=JsonDiffMatcher(expect_pipeline))
460         self.assertEqual(stubs.capture_stdout.getvalue(),
461                          stubs.expect_pipeline_uuid + '\n')
462
463
464     @mock.patch("time.sleep")
465     @stubs
466     def test_submit_pipeline_name(self, stubs, tm):
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             stubs.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(stubs.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         exited = arvados_cwl.main(
487             ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
488              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
489             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
490         self.assertEqual(exited, 0)
491
492         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
493         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
494
495         stubs.api.pipeline_instances().create.assert_called_with(
496             body=JsonDiffMatcher(expect_pipeline))
497         self.assertEqual(stubs.capture_stdout.getvalue(),
498                          stubs.expect_pipeline_uuid + '\n')
499
500     @mock.patch("time.sleep")
501     @stubs
502     def test_submit_with_project_uuid(self, stubs, tm):
503         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
504
505         exited = arvados_cwl.main(
506             ["--submit", "--no-wait", "--debug",
507              "--project-uuid", project_uuid,
508              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
509             sys.stdout, sys.stderr, api_client=stubs.api)
510         self.assertEqual(exited, 0)
511
512         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
513         expect_pipeline["owner_uuid"] = project_uuid
514         stubs.api.pipeline_instances().create.assert_called_with(
515             body=JsonDiffMatcher(expect_pipeline))
516
517     @stubs
518     def test_submit_container(self, stubs):
519         try:
520             exited = arvados_cwl.main(
521                 ["--submit", "--no-wait", "--api=containers", "--debug",
522                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
523                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
524             self.assertEqual(exited, 0)
525         except:
526             logging.exception(str(u""))
527
528         stubs.api.collections().create.assert_has_calls([
529             mock.call(body=JsonDiffMatcher({
530                 'manifest_text':
531                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
532                 'replication_desired': None,
533                 'name': 'submit_tool.cwl dependencies (5d373e7629203ce39e7c22af98a0f881+52)',
534             }), ensure_unique_name=False),
535             mock.call(body=JsonDiffMatcher({
536                 'manifest_text':
537                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
538                 'replication_desired': None,
539                 'name': 'submit_wf.cwl input (169f39d466a5438ac4a90e779bf750c7+53)',
540             }), ensure_unique_name=False)])
541
542         expect_container = copy.deepcopy(stubs.expect_container_spec)
543         stubs.api.container_requests().create.assert_called_with(
544             body=JsonDiffMatcher(expect_container))
545         self.assertEqual(stubs.capture_stdout.getvalue(),
546                          stubs.expect_container_request_uuid + '\n')
547
548     @stubs
549     def test_submit_container_no_reuse(self, stubs):
550         try:
551             exited = arvados_cwl.main(
552                 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
553                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
554                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
555             self.assertEqual(exited, 0)
556         except:
557             logging.exception(str(u""))
558
559         expect_container = copy.deepcopy(stubs.expect_container_spec)
560         expect_container["command"] = [
561             'arvados-cwl-runner', '--local', '--api=containers',
562             '--no-log-timestamps', '--disable-validate',
563             '--eval-timeout=20', '--thread-count=1',
564             '--disable-reuse', "--collection-cache-size=256",
565             '--debug', '--on-error=continue',
566             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
567         expect_container["use_existing"] = False
568
569         stubs.api.container_requests().create.assert_called_with(
570             body=JsonDiffMatcher(expect_container))
571         self.assertEqual(stubs.capture_stdout.getvalue(),
572                          stubs.expect_container_request_uuid + '\n')
573
574
575     @stubs
576     def test_submit_container_reuse_disabled_by_workflow(self, stubs):
577         exited = arvados_cwl.main(
578             ["--submit", "--no-wait", "--api=containers", "--debug",
579              "tests/wf/submit_wf_no_reuse.cwl", "tests/submit_test_job.json"],
580             stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
581         self.assertEqual(exited, 0)
582
583         expect_container = copy.deepcopy(stubs.expect_container_spec)
584         expect_container["command"] = [
585             'arvados-cwl-runner', '--local', '--api=containers',
586             '--no-log-timestamps', '--disable-validate',
587             '--eval-timeout=20', '--thread-count=1',
588             '--disable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
589             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
590         expect_container["use_existing"] = False
591         expect_container["name"] = "submit_wf_no_reuse.cwl"
592         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
593             {
594                 "class": "http://arvados.org/cwl#ReuseRequirement",
595                 "enableReuse": False,
596             },
597         ]
598         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
599             "arv": "http://arvados.org/cwl#",
600             "cwltool": "http://commonwl.org/cwltool#"
601         }
602
603         stubs.api.container_requests().create.assert_called_with(
604             body=JsonDiffMatcher(expect_container))
605         self.assertEqual(stubs.capture_stdout.getvalue(),
606                          stubs.expect_container_request_uuid + '\n')
607
608
609     @stubs
610     def test_submit_container_on_error(self, stubs):
611         try:
612             exited = arvados_cwl.main(
613                 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
614                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
615                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
616             self.assertEqual(exited, 0)
617         except:
618             logging.exception(str(u""))
619
620         expect_container = copy.deepcopy(stubs.expect_container_spec)
621         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
622                                        '--no-log-timestamps', '--disable-validate',
623                                        '--eval-timeout=20', '--thread-count=1',
624                                        '--enable-reuse', "--collection-cache-size=256",
625                                        '--debug', '--on-error=stop',
626                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
627
628         stubs.api.container_requests().create.assert_called_with(
629             body=JsonDiffMatcher(expect_container))
630         self.assertEqual(stubs.capture_stdout.getvalue(),
631                          stubs.expect_container_request_uuid + '\n')
632
633     @stubs
634     def test_submit_container_output_name(self, stubs):
635         output_name = "test_output_name"
636
637         try:
638             exited = arvados_cwl.main(
639                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
640                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
641                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
642             self.assertEqual(exited, 0)
643         except:
644             logging.exception(str(u""))
645
646         expect_container = copy.deepcopy(stubs.expect_container_spec)
647         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
648                                        '--no-log-timestamps', '--disable-validate',
649                                        '--eval-timeout=20', '--thread-count=1',
650                                        '--enable-reuse', "--collection-cache-size=256",
651                                        "--output-name="+output_name, '--debug', '--on-error=continue',
652                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
653         expect_container["output_name"] = output_name
654
655         stubs.api.container_requests().create.assert_called_with(
656             body=JsonDiffMatcher(expect_container))
657         self.assertEqual(stubs.capture_stdout.getvalue(),
658                          stubs.expect_container_request_uuid + '\n')
659
660     @stubs
661     def test_submit_storage_classes(self, stubs):
662         try:
663             exited = arvados_cwl.main(
664                 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo",
665                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
666                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
667             self.assertEqual(exited, 0)
668         except:
669             logging.exception(str(u""))
670
671         expect_container = copy.deepcopy(stubs.expect_container_spec)
672         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
673                                        '--no-log-timestamps', '--disable-validate',
674                                        '--eval-timeout=20', '--thread-count=1',
675                                        '--enable-reuse', "--collection-cache-size=256", "--debug",
676                                        "--storage-classes=foo", '--on-error=continue',
677                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
678
679         stubs.api.container_requests().create.assert_called_with(
680             body=JsonDiffMatcher(expect_container))
681         self.assertEqual(stubs.capture_stdout.getvalue(),
682                          stubs.expect_container_request_uuid + '\n')
683
684     @mock.patch("arvados_cwl.task_queue.TaskQueue")
685     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
686     @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
687     @stubs
688     def test_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
689         def set_final_output(job_order, output_callback, runtimeContext):
690             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
691             return []
692         job.side_effect = set_final_output
693
694         try:
695             exited = arvados_cwl.main(
696                 ["--debug", "--local", "--storage-classes=foo",
697                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
698                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
699             self.assertEqual(exited, 0)
700         except:
701             logging.exception(str(u""))
702
703         make_output.assert_called_with(u'Output of submit_wf.cwl', ['foo'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
704
705     @mock.patch("arvados_cwl.task_queue.TaskQueue")
706     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
707     @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection", return_value = (None, None))
708     @stubs
709     def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
710         def set_final_output(job_order, output_callback, runtimeContext):
711             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
712             return []
713         job.side_effect = set_final_output
714
715         try:
716             exited = arvados_cwl.main(
717                 ["--debug", "--local",
718                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
719                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
720             self.assertEqual(exited, 0)
721         except:
722             logging.exception(str(u""))
723
724         make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
725
726     @stubs
727     def test_submit_container_output_ttl(self, stubs):
728         try:
729             exited = arvados_cwl.main(
730                 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
731                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
732                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
733             self.assertEqual(exited, 0)
734         except:
735             logging.exception(str(u""))
736
737         expect_container = copy.deepcopy(stubs.expect_container_spec)
738         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
739                                        '--no-log-timestamps', '--disable-validate',
740                                        '--eval-timeout=20', '--thread-count=1',
741                                        '--enable-reuse', "--collection-cache-size=256", '--debug',
742                                        '--on-error=continue',
743                                        "--intermediate-output-ttl=3600",
744                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
745
746         stubs.api.container_requests().create.assert_called_with(
747             body=JsonDiffMatcher(expect_container))
748         self.assertEqual(stubs.capture_stdout.getvalue(),
749                          stubs.expect_container_request_uuid + '\n')
750
751     @stubs
752     def test_submit_container_trash_intermediate(self, stubs):
753         try:
754             exited = arvados_cwl.main(
755                 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
756                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
757                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
758             self.assertEqual(exited, 0)
759         except:
760             logging.exception(str(u""))
761
762         expect_container = copy.deepcopy(stubs.expect_container_spec)
763         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
764                                        '--no-log-timestamps', '--disable-validate',
765                                        '--eval-timeout=20', '--thread-count=1',
766                                        '--enable-reuse', "--collection-cache-size=256",
767                                        '--debug', '--on-error=continue',
768                                        "--trash-intermediate",
769                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
770
771         stubs.api.container_requests().create.assert_called_with(
772             body=JsonDiffMatcher(expect_container))
773         self.assertEqual(stubs.capture_stdout.getvalue(),
774                          stubs.expect_container_request_uuid + '\n')
775
776     @stubs
777     def test_submit_container_output_tags(self, stubs):
778         output_tags = "tag0,tag1,tag2"
779
780         try:
781             exited = arvados_cwl.main(
782                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
783                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
784                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
785             self.assertEqual(exited, 0)
786         except:
787             logging.exception(str(u""))
788
789         expect_container = copy.deepcopy(stubs.expect_container_spec)
790         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
791                                        '--no-log-timestamps', '--disable-validate',
792                                        '--eval-timeout=20', '--thread-count=1',
793                                        '--enable-reuse', "--collection-cache-size=256",
794                                        "--output-tags="+output_tags, '--debug', '--on-error=continue',
795                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
796
797         stubs.api.container_requests().create.assert_called_with(
798             body=JsonDiffMatcher(expect_container))
799         self.assertEqual(stubs.capture_stdout.getvalue(),
800                          stubs.expect_container_request_uuid + '\n')
801
802     @stubs
803     def test_submit_container_runner_ram(self, stubs):
804
805         try:
806             exited = arvados_cwl.main(
807                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
808                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
809                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
810             self.assertEqual(exited, 0)
811         except:
812             logging.exception(str(u""))
813
814         expect_container = copy.deepcopy(stubs.expect_container_spec)
815         expect_container["runtime_constraints"]["ram"] = (2048+256)*1024*1024
816
817         stubs.api.container_requests().create.assert_called_with(
818             body=JsonDiffMatcher(expect_container))
819         self.assertEqual(stubs.capture_stdout.getvalue(),
820                          stubs.expect_container_request_uuid + '\n')
821
822     @mock.patch("arvados.collection.CollectionReader")
823     @mock.patch("time.sleep")
824     @stubs
825     def test_submit_file_keepref(self, stubs, tm, collectionReader):
826         collectionReader().find.return_value = arvados.arvfile.ArvadosFile(mock.MagicMock(), "blorp.txt")
827         exited = arvados_cwl.main(
828             ["--submit", "--no-wait", "--api=containers", "--debug",
829              "tests/wf/submit_keepref_wf.cwl"],
830             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
831         self.assertEqual(exited, 0)
832
833
834     @mock.patch("arvados.collection.CollectionReader")
835     @mock.patch("time.sleep")
836     @stubs
837     def test_submit_keepref(self, stubs, tm, reader):
838         with open("tests/wf/expect_arvworkflow.cwl") as f:
839             reader().open().__enter__().read.return_value = f.read()
840
841         exited = arvados_cwl.main(
842             ["--submit", "--no-wait", "--api=containers", "--debug",
843              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
844             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
845         self.assertEqual(exited, 0)
846
847         expect_container = {
848             'priority': 500,
849             'mounts': {
850                 '/var/spool/cwl': {
851                     'writable': True,
852                     'kind': 'collection'
853                 },
854                 'stdout': {
855                     'path': '/var/spool/cwl/cwl.output.json',
856                     'kind': 'file'
857                 },
858                 '/var/lib/cwl/workflow': {
859                     'portable_data_hash': '99999999999999999999999999999994+99',
860                     'kind': 'collection'
861                 },
862                 '/var/lib/cwl/cwl.input.json': {
863                     'content': {
864                         'x': 'XxX'
865                     },
866                     'kind': 'json'
867                 }
868             }, 'state': 'Committed',
869             'output_path': '/var/spool/cwl',
870             'name': 'expect_arvworkflow.cwl#main',
871             'container_image': '999999999999999999999999999999d3+99',
872             'command': ['arvados-cwl-runner', '--local', '--api=containers',
873                         '--no-log-timestamps', '--disable-validate',
874                         '--eval-timeout=20', '--thread-count=1',
875                         '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
876                         '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
877             'cwd': '/var/spool/cwl',
878             'runtime_constraints': {
879                 'API': True,
880                 'vcpus': 1,
881                 'ram': 1342177280
882             },
883             'use_existing': True,
884             'properties': {},
885             'secret_mounts': {}
886         }
887
888         stubs.api.container_requests().create.assert_called_with(
889             body=JsonDiffMatcher(expect_container))
890         self.assertEqual(stubs.capture_stdout.getvalue(),
891                          stubs.expect_container_request_uuid + '\n')
892
893
894     @mock.patch("arvados.collection.CollectionReader")
895     @mock.patch("time.sleep")
896     @stubs
897     def test_submit_jobs_keepref(self, stubs, tm, reader):
898         with open("tests/wf/expect_arvworkflow.cwl") as f:
899             reader().open().__enter__().read.return_value = f.read()
900
901         exited = arvados_cwl.main(
902             ["--submit", "--no-wait", "--api=jobs", "--debug",
903              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
904             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
905         self.assertEqual(exited, 0)
906
907         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
908         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
909         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
910         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
911         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
912         expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
913         stubs.api.pipeline_instances().create.assert_called_with(
914             body=JsonDiffMatcher(expect_pipeline))
915
916     @mock.patch("time.sleep")
917     @stubs
918     def test_submit_arvworkflow(self, stubs, tm):
919         with open("tests/wf/expect_arvworkflow.cwl") as f:
920             stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
921
922         exited = arvados_cwl.main(
923             ["--submit", "--no-wait", "--api=containers", "--debug",
924              "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
925             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
926         self.assertEqual(exited, 0)
927
928         expect_container = {
929             'priority': 500,
930             'mounts': {
931                 '/var/spool/cwl': {
932                     'writable': True,
933                     'kind': 'collection'
934                 },
935                 'stdout': {
936                     'path': '/var/spool/cwl/cwl.output.json',
937                     'kind': 'file'
938                 },
939                 '/var/lib/cwl/workflow.json': {
940                     'kind': 'json',
941                     'content': {
942                         'cwlVersion': 'v1.0',
943                         '$graph': [
944                             {
945                                 'id': '#main',
946                                 'inputs': [
947                                     {'type': 'string', 'id': '#main/x'}
948                                 ],
949                                 'steps': [
950                                     {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
951                                      'run': '#submit_tool.cwl',
952                                      'id': '#main/step1',
953                                      'out': []}
954                                 ],
955                                 'class': 'Workflow',
956                                 'outputs': []
957                             },
958                             {
959                                 'inputs': [
960                                     {
961                                         'inputBinding': {'position': 1},
962                                         'type': 'string',
963                                         'id': '#submit_tool.cwl/x'}
964                                 ],
965                                 'requirements': [
966                                     {
967                                         'dockerPull': 'debian:8',
968                                         'class': 'DockerRequirement',
969                                         "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
970                                     }
971                                 ],
972                                 'id': '#submit_tool.cwl',
973                                 'outputs': [],
974                                 'baseCommand': 'cat',
975                                 'class': 'CommandLineTool'
976                             }
977                         ]
978                     }
979                 },
980                 '/var/lib/cwl/cwl.input.json': {
981                     'content': {
982                         'x': 'XxX'
983                     },
984                     'kind': 'json'
985                 }
986             }, 'state': 'Committed',
987             'output_path': '/var/spool/cwl',
988             'name': 'a test workflow',
989             'container_image': "999999999999999999999999999999d3+99",
990             'command': ['arvados-cwl-runner', '--local', '--api=containers',
991                         '--no-log-timestamps', '--disable-validate',
992                         '--eval-timeout=20', '--thread-count=1',
993                         '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue',
994                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
995             'cwd': '/var/spool/cwl',
996             'runtime_constraints': {
997                 'API': True,
998                 'vcpus': 1,
999                 'ram': 1342177280
1000             },
1001             'use_existing': True,
1002             'properties': {
1003                 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
1004             },
1005             'secret_mounts': {}
1006         }
1007
1008         stubs.api.container_requests().create.assert_called_with(
1009             body=JsonDiffMatcher(expect_container))
1010         self.assertEqual(stubs.capture_stdout.getvalue(),
1011                          stubs.expect_container_request_uuid + '\n')
1012
1013
1014     @stubs
1015     def test_submit_container_name(self, stubs):
1016         try:
1017             exited = arvados_cwl.main(
1018                 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
1019                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1020                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1021             self.assertEqual(exited, 0)
1022         except:
1023             logging.exception(str(u""))
1024
1025         expect_container = copy.deepcopy(stubs.expect_container_spec)
1026         expect_container["name"] = "hello container 123"
1027
1028         stubs.api.container_requests().create.assert_called_with(
1029             body=JsonDiffMatcher(expect_container))
1030         self.assertEqual(stubs.capture_stdout.getvalue(),
1031                          stubs.expect_container_request_uuid + '\n')
1032
1033
1034     @stubs
1035     def test_submit_missing_input(self, stubs):
1036         exited = arvados_cwl.main(
1037             ["--submit", "--no-wait", "--api=containers", "--debug",
1038              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1039             stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1040         self.assertEqual(exited, 0)
1041
1042         exited = arvados_cwl.main(
1043             ["--submit", "--no-wait", "--api=containers", "--debug",
1044              "tests/wf/submit_wf.cwl", "tests/submit_test_job_missing.json"],
1045             stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1046         self.assertEqual(exited, 1)
1047
1048
1049     @stubs
1050     def test_submit_container_project(self, stubs):
1051         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1052         try:
1053             exited = arvados_cwl.main(
1054                 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
1055                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1056                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1057             self.assertEqual(exited, 0)
1058         except:
1059             logging.exception(str(u""))
1060
1061         expect_container = copy.deepcopy(stubs.expect_container_spec)
1062         expect_container["owner_uuid"] = project_uuid
1063         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1064                                        '--no-log-timestamps', '--disable-validate',
1065                                        "--eval-timeout=20", "--thread-count=1",
1066                                        '--enable-reuse', "--collection-cache-size=256", '--debug',
1067                                        '--on-error=continue',
1068                                        '--project-uuid='+project_uuid,
1069                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1070
1071         stubs.api.container_requests().create.assert_called_with(
1072             body=JsonDiffMatcher(expect_container))
1073         self.assertEqual(stubs.capture_stdout.getvalue(),
1074                          stubs.expect_container_request_uuid + '\n')
1075
1076     @stubs
1077     def test_submit_container_eval_timeout(self, stubs):
1078         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1079         try:
1080             exited = arvados_cwl.main(
1081                 ["--submit", "--no-wait", "--api=containers", "--debug", "--eval-timeout=60",
1082                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1083                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1084             self.assertEqual(exited, 0)
1085         except:
1086             logging.exception(str(u""))
1087
1088         expect_container = copy.deepcopy(stubs.expect_container_spec)
1089         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1090                                        '--no-log-timestamps', '--disable-validate',
1091                                        '--eval-timeout=60.0', '--thread-count=1',
1092                                        '--enable-reuse', "--collection-cache-size=256",
1093                                        '--debug', '--on-error=continue',
1094                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1095
1096         stubs.api.container_requests().create.assert_called_with(
1097             body=JsonDiffMatcher(expect_container))
1098         self.assertEqual(stubs.capture_stdout.getvalue(),
1099                          stubs.expect_container_request_uuid + '\n')
1100
1101     @stubs
1102     def test_submit_container_collection_cache(self, stubs):
1103         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1104         try:
1105             exited = arvados_cwl.main(
1106                 ["--submit", "--no-wait", "--api=containers", "--debug", "--collection-cache-size=500",
1107                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1108                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1109             self.assertEqual(exited, 0)
1110         except:
1111             logging.exception(str(u""))
1112
1113         expect_container = copy.deepcopy(stubs.expect_container_spec)
1114         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1115                                        '--no-log-timestamps', '--disable-validate',
1116                                        '--eval-timeout=20', '--thread-count=1',
1117                                        '--enable-reuse', "--collection-cache-size=500",
1118                                        '--debug', '--on-error=continue',
1119                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1120         expect_container["runtime_constraints"]["ram"] = (1024+500)*1024*1024
1121
1122         stubs.api.container_requests().create.assert_called_with(
1123             body=JsonDiffMatcher(expect_container))
1124         self.assertEqual(stubs.capture_stdout.getvalue(),
1125                          stubs.expect_container_request_uuid + '\n')
1126
1127
1128     @stubs
1129     def test_submit_container_thread_count(self, stubs):
1130         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1131         try:
1132             exited = arvados_cwl.main(
1133                 ["--submit", "--no-wait", "--api=containers", "--debug", "--thread-count=20",
1134                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1135                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1136             self.assertEqual(exited, 0)
1137         except:
1138             logging.exception(str(u""))
1139
1140         expect_container = copy.deepcopy(stubs.expect_container_spec)
1141         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1142                                        '--no-log-timestamps', '--disable-validate',
1143                                        '--eval-timeout=20', '--thread-count=20',
1144                                        '--enable-reuse', "--collection-cache-size=256",
1145                                        '--debug', '--on-error=continue',
1146                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1147
1148         stubs.api.container_requests().create.assert_called_with(
1149             body=JsonDiffMatcher(expect_container))
1150         self.assertEqual(stubs.capture_stdout.getvalue(),
1151                          stubs.expect_container_request_uuid + '\n')
1152
1153
1154     @stubs
1155     def test_submit_job_runner_image(self, stubs):
1156         try:
1157             exited = arvados_cwl.main(
1158                 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
1159                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1160                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1161             self.assertEqual(exited, 0)
1162         except:
1163             logging.exception(str(u""))
1164
1165         stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "999999999999999999999999999999d5+99"
1166
1167         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
1168         stubs.api.pipeline_instances().create.assert_called_with(
1169             body=JsonDiffMatcher(expect_pipeline))
1170         self.assertEqual(stubs.capture_stdout.getvalue(),
1171                          stubs.expect_pipeline_uuid + '\n')
1172
1173     @stubs
1174     def test_submit_container_runner_image(self, stubs):
1175         try:
1176             exited = arvados_cwl.main(
1177                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
1178                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1179                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1180             self.assertEqual(exited, 0)
1181         except:
1182             logging.exception(str(u""))
1183
1184         stubs.expect_container_spec["container_image"] = "999999999999999999999999999999d5+99"
1185
1186         expect_container = copy.deepcopy(stubs.expect_container_spec)
1187         stubs.api.container_requests().create.assert_called_with(
1188             body=JsonDiffMatcher(expect_container))
1189         self.assertEqual(stubs.capture_stdout.getvalue(),
1190                          stubs.expect_container_request_uuid + '\n')
1191
1192     @stubs
1193     def test_submit_priority(self, stubs):
1194         try:
1195             exited = arvados_cwl.main(
1196                 ["--submit", "--no-wait", "--api=containers", "--debug", "--priority=669",
1197                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1198                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1199             self.assertEqual(exited, 0)
1200         except:
1201             logging.exception(str(u""))
1202
1203         stubs.expect_container_spec["priority"] = 669
1204
1205         expect_container = copy.deepcopy(stubs.expect_container_spec)
1206         stubs.api.container_requests().create.assert_called_with(
1207             body=JsonDiffMatcher(expect_container))
1208         self.assertEqual(stubs.capture_stdout.getvalue(),
1209                          stubs.expect_container_request_uuid + '\n')
1210
1211
1212     @stubs
1213     def test_submit_wf_runner_resources(self, stubs):
1214         try:
1215             exited = arvados_cwl.main(
1216                 ["--submit", "--no-wait", "--api=containers", "--debug",
1217                  "tests/wf/submit_wf_runner_resources.cwl", "tests/submit_test_job.json"],
1218                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1219             self.assertEqual(exited, 0)
1220         except:
1221             logging.exception(str(u""))
1222
1223         expect_container = copy.deepcopy(stubs.expect_container_spec)
1224         expect_container["runtime_constraints"] = {
1225             "API": True,
1226             "vcpus": 2,
1227             "ram": (2000+512) * 2**20
1228         }
1229         expect_container["name"] = "submit_wf_runner_resources.cwl"
1230         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1231             {
1232                 "class": "http://arvados.org/cwl#WorkflowRunnerResources",
1233                 "coresMin": 2,
1234                 "ramMin": 2000,
1235                 "keep_cache": 512
1236             }
1237         ]
1238         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
1239             "arv": "http://arvados.org/cwl#",
1240         }
1241         expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers',
1242                         '--no-log-timestamps', '--disable-validate',
1243                         '--eval-timeout=20', '--thread-count=1',
1244                         '--enable-reuse', "--collection-cache-size=512", '--debug', '--on-error=continue',
1245                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1246
1247         stubs.api.container_requests().create.assert_called_with(
1248             body=JsonDiffMatcher(expect_container))
1249         self.assertEqual(stubs.capture_stdout.getvalue(),
1250                          stubs.expect_container_request_uuid + '\n')
1251
1252     def tearDown(self):
1253         arvados_cwl.arvdocker.arv_docker_clear_cache()
1254
1255     @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
1256     @mock.patch("cwltool.docker.DockerCommandLineJob.get_image")
1257     @mock.patch("arvados.api")
1258     def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
1259         arvados_cwl.arvdocker.arv_docker_clear_cache()
1260
1261         arvrunner = mock.MagicMock()
1262         arvrunner.project_uuid = ""
1263         api.return_value = mock.MagicMock()
1264         arvrunner.api = api.return_value
1265         arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
1266                                                                         "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1267                                                                         "link_class": "docker_image_repo+tag",
1268                                                                         "name": "arvados/jobs:"+arvados_cwl.__version__,
1269                                                                         "owner_uuid": "",
1270                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1271                                                             {"items": [{"created_at": "",
1272                                                                         "head_uuid": "",
1273                                                                         "link_class": "docker_image_hash",
1274                                                                         "name": "123456",
1275                                                                         "owner_uuid": "",
1276                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
1277         )
1278         find_one_image_hash.return_value = "123456"
1279
1280         arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1281                                                                               "owner_uuid": "",
1282                                                                               "manifest_text": "",
1283                                                                               "properties": ""
1284                                                                           }], "items_available": 1, "offset": 0},)
1285         arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
1286         arvrunner.api.collections().get().execute.return_value = {"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1287                                                                   "portable_data_hash": "9999999999999999999999999999999b+99"}
1288         self.assertEqual("9999999999999999999999999999999b+99",
1289                          arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
1290
1291
1292     @stubs
1293     def test_submit_secrets(self, stubs):
1294         try:
1295             exited = arvados_cwl.main(
1296                 ["--submit", "--no-wait", "--api=containers", "--debug",
1297                  "tests/wf/secret_wf.cwl", "tests/secret_test_job.yml"],
1298                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1299             self.assertEqual(exited, 0)
1300         except:
1301             logging.exception(str(u""))
1302
1303
1304         expect_container = {
1305             "command": [
1306                 "arvados-cwl-runner",
1307                 "--local",
1308                 "--api=containers",
1309                 "--no-log-timestamps",
1310                 "--disable-validate",
1311                 "--eval-timeout=20",
1312                 '--thread-count=1',
1313                 "--enable-reuse",
1314                 "--collection-cache-size=256",
1315                 '--debug',
1316                 "--on-error=continue",
1317                 "/var/lib/cwl/workflow.json#main",
1318                 "/var/lib/cwl/cwl.input.json"
1319             ],
1320             "container_image": "999999999999999999999999999999d3+99",
1321             "cwd": "/var/spool/cwl",
1322             "mounts": {
1323                 "/var/lib/cwl/cwl.input.json": {
1324                     "content": {
1325                         "pw": {
1326                             "$include": "/secrets/s0"
1327                         }
1328                     },
1329                     "kind": "json"
1330                 },
1331                 "/var/lib/cwl/workflow.json": {
1332                     "content": {
1333                         "$graph": [
1334                             {
1335                                 "$namespaces": {
1336                                     "cwltool": "http://commonwl.org/cwltool#"
1337                                 },
1338                                 "arguments": [
1339                                     "md5sum",
1340                                     "example.conf"
1341                                 ],
1342                                 "class": "CommandLineTool",
1343                                 "hints": [
1344                                     {
1345                                         "class": "http://commonwl.org/cwltool#Secrets",
1346                                         "secrets": [
1347                                             "#secret_job.cwl/pw"
1348                                         ]
1349                                     }
1350                                 ],
1351                                 "id": "#secret_job.cwl",
1352                                 "inputs": [
1353                                     {
1354                                         "id": "#secret_job.cwl/pw",
1355                                         "type": "string"
1356                                     }
1357                                 ],
1358                                 "outputs": [
1359                                     {
1360                                         "id": "#secret_job.cwl/out",
1361                                         "type": "stdout"
1362                                     }
1363                                 ],
1364                                 "stdout": "hashed_example.txt",
1365                                 "requirements": [
1366                                     {
1367                                         "class": "InitialWorkDirRequirement",
1368                                         "listing": [
1369                                             {
1370                                                 "entry": "username: user\npassword: $(inputs.pw)\n",
1371                                                 "entryname": "example.conf"
1372                                             }
1373                                         ]
1374                                     }
1375                                 ]
1376                             },
1377                             {
1378                                 "class": "Workflow",
1379                                 "hints": [
1380                                     {
1381                                         "class": "DockerRequirement",
1382                                         "dockerPull": "debian:8",
1383                                         "http://arvados.org/cwl#dockerCollectionPDH": "999999999999999999999999999999d4+99"
1384                                     },
1385                                     {
1386                                         "class": "http://commonwl.org/cwltool#Secrets",
1387                                         "secrets": [
1388                                             "#main/pw"
1389                                         ]
1390                                     }
1391                                 ],
1392                                 "id": "#main",
1393                                 "inputs": [
1394                                     {
1395                                         "id": "#main/pw",
1396                                         "type": "string"
1397                                     }
1398                                 ],
1399                                 "outputs": [
1400                                     {
1401                                         "id": "#main/out",
1402                                         "outputSource": "#main/step1/out",
1403                                         "type": "File"
1404                                     }
1405                                 ],
1406                                 "steps": [
1407                                     {
1408                                         "id": "#main/step1",
1409                                         "in": [
1410                                             {
1411                                                 "id": "#main/step1/pw",
1412                                                 "source": "#main/pw"
1413                                             }
1414                                         ],
1415                                         "out": [
1416                                             "#main/step1/out"
1417                                         ],
1418                                         "run": "#secret_job.cwl"
1419                                     }
1420                                 ]
1421                             }
1422                         ],
1423                         "cwlVersion": "v1.0"
1424                     },
1425                     "kind": "json"
1426                 },
1427                 "/var/spool/cwl": {
1428                     "kind": "collection",
1429                     "writable": True
1430                 },
1431                 "stdout": {
1432                     "kind": "file",
1433                     "path": "/var/spool/cwl/cwl.output.json"
1434                 }
1435             },
1436             "name": "secret_wf.cwl",
1437             "output_path": "/var/spool/cwl",
1438             "priority": 500,
1439             "properties": {},
1440             "runtime_constraints": {
1441                 "API": True,
1442                 "ram": 1342177280,
1443                 "vcpus": 1
1444             },
1445             "secret_mounts": {
1446                 "/secrets/s0": {
1447                     "content": "blorp",
1448                     "kind": "text"
1449                 }
1450             },
1451             "state": "Committed",
1452             "use_existing": True
1453         }
1454
1455         stubs.api.container_requests().create.assert_called_with(
1456             body=JsonDiffMatcher(expect_container))
1457         self.assertEqual(stubs.capture_stdout.getvalue(),
1458                          stubs.expect_container_request_uuid + '\n')
1459
1460     @stubs
1461     def test_submit_request_uuid(self, stubs):
1462         stubs.expect_container_request_uuid = "zzzzz-xvhdp-yyyyyyyyyyyyyyy"
1463
1464         stubs.api.container_requests().update().execute.return_value = {
1465             "uuid": stubs.expect_container_request_uuid,
1466             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
1467             "state": "Queued"
1468         }
1469
1470         try:
1471             exited = arvados_cwl.main(
1472                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-request-uuid=zzzzz-xvhdp-yyyyyyyyyyyyyyy",
1473                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1474                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1475             self.assertEqual(exited, 0)
1476         except:
1477             logging.exception(str(u""))
1478
1479         stubs.api.container_requests().update.assert_called_with(
1480             uuid="zzzzz-xvhdp-yyyyyyyyyyyyyyy", body=JsonDiffMatcher(stubs.expect_container_spec), cluster_id="zzzzz")
1481         self.assertEqual(stubs.capture_stdout.getvalue(),
1482                          stubs.expect_container_request_uuid + '\n')
1483
1484     @stubs
1485     def test_submit_container_cluster_id(self, stubs):
1486         stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1487         try:
1488             exited = arvados_cwl.main(
1489                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zbbbb",
1490                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1491                 stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1492             self.assertEqual(exited, 0)
1493         except:
1494             logging.exception(str(u""))
1495
1496         expect_container = copy.deepcopy(stubs.expect_container_spec)
1497
1498         stubs.api.container_requests().create.assert_called_with(
1499             body=JsonDiffMatcher(expect_container), cluster_id="zbbbb")
1500         self.assertEqual(stubs.capture_stdout.getvalue(),
1501                          stubs.expect_container_request_uuid + '\n')
1502
1503
1504     @stubs
1505     def test_submit_validate_cluster_id(self, stubs):
1506         stubs.api._rootDesc["remoteHosts"]["zbbbb"] = "123"
1507         exited = arvados_cwl.main(
1508             ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-cluster=zcccc",
1509              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1510             stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1511         self.assertEqual(exited, 1)
1512
1513
1514 class TestCreateTemplate(unittest.TestCase):
1515     existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
1516
1517     def _adjust_script_params(self, expect_component):
1518         expect_component['script_parameters']['x'] = {
1519             'dataclass': 'File',
1520             'required': True,
1521             'type': 'File',
1522             'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
1523         }
1524         expect_component['script_parameters']['y'] = {
1525             'dataclass': 'Collection',
1526             'required': True,
1527             'type': 'Directory',
1528             'value': '99999999999999999999999999999998+99',
1529         }
1530         expect_component['script_parameters']['z'] = {
1531             'dataclass': 'Collection',
1532             'required': True,
1533             'type': 'Directory',
1534         }
1535
1536     @stubs
1537     def test_create(self, stubs):
1538         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1539
1540         exited = arvados_cwl.main(
1541             ["--create-workflow", "--debug",
1542              "--api=jobs",
1543              "--project-uuid", project_uuid,
1544              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1545             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1546         self.assertEqual(exited, 0)
1547
1548         stubs.api.pipeline_instances().create.refute_called()
1549         stubs.api.jobs().create.refute_called()
1550
1551         expect_component = copy.deepcopy(stubs.expect_job_spec)
1552         self._adjust_script_params(expect_component)
1553         expect_template = {
1554             "components": {
1555                 "submit_wf.cwl": expect_component,
1556             },
1557             "name": "submit_wf.cwl",
1558             "owner_uuid": project_uuid,
1559         }
1560         stubs.api.pipeline_templates().create.assert_called_with(
1561             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1562
1563         self.assertEqual(stubs.capture_stdout.getvalue(),
1564                          stubs.expect_pipeline_template_uuid + '\n')
1565
1566
1567     @stubs
1568     def test_create_name(self, stubs):
1569         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1570
1571         exited = arvados_cwl.main(
1572             ["--create-workflow", "--debug",
1573              "--project-uuid", project_uuid,
1574              "--api=jobs",
1575              "--name", "testing 123",
1576              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1577             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1578         self.assertEqual(exited, 0)
1579
1580         stubs.api.pipeline_instances().create.refute_called()
1581         stubs.api.jobs().create.refute_called()
1582
1583         expect_component = copy.deepcopy(stubs.expect_job_spec)
1584         self._adjust_script_params(expect_component)
1585         expect_template = {
1586             "components": {
1587                 "testing 123": expect_component,
1588             },
1589             "name": "testing 123",
1590             "owner_uuid": project_uuid,
1591         }
1592         stubs.api.pipeline_templates().create.assert_called_with(
1593             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1594
1595         self.assertEqual(stubs.capture_stdout.getvalue(),
1596                          stubs.expect_pipeline_template_uuid + '\n')
1597
1598
1599     @stubs
1600     def test_update_name(self, stubs):
1601         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1602
1603         exited = arvados_cwl.main(
1604             ["--update-workflow", self.existing_template_uuid,
1605              "--debug",
1606              "--project-uuid", project_uuid,
1607              "--api=jobs",
1608              "--name", "testing 123",
1609              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1610             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1611         self.assertEqual(exited, 0)
1612
1613         stubs.api.pipeline_instances().create.refute_called()
1614         stubs.api.jobs().create.refute_called()
1615
1616         expect_component = copy.deepcopy(stubs.expect_job_spec)
1617         self._adjust_script_params(expect_component)
1618         expect_template = {
1619             "components": {
1620                 "testing 123": expect_component,
1621             },
1622             "name": "testing 123",
1623             "owner_uuid": project_uuid,
1624         }
1625         stubs.api.pipeline_templates().create.refute_called()
1626         stubs.api.pipeline_templates().update.assert_called_with(
1627             body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1628
1629         self.assertEqual(stubs.capture_stdout.getvalue(),
1630                          self.existing_template_uuid + '\n')
1631
1632
1633 class TestCreateWorkflow(unittest.TestCase):
1634     existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1635     expect_workflow = StripYAMLComments(
1636         open("tests/wf/expect_packed.cwl").read())
1637
1638     @stubs
1639     def test_create(self, stubs):
1640         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1641
1642         exited = arvados_cwl.main(
1643             ["--create-workflow", "--debug",
1644              "--api=containers",
1645              "--project-uuid", project_uuid,
1646              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1647             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1648         self.assertEqual(exited, 0)
1649
1650         stubs.api.pipeline_templates().create.refute_called()
1651         stubs.api.container_requests().create.refute_called()
1652
1653         body = {
1654             "workflow": {
1655                 "owner_uuid": project_uuid,
1656                 "name": "submit_wf.cwl",
1657                 "description": "",
1658                 "definition": self.expect_workflow,
1659             }
1660         }
1661         stubs.api.workflows().create.assert_called_with(
1662             body=JsonDiffMatcher(body))
1663
1664         self.assertEqual(stubs.capture_stdout.getvalue(),
1665                          stubs.expect_workflow_uuid + '\n')
1666
1667
1668     @stubs
1669     def test_create_name(self, stubs):
1670         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1671
1672         exited = arvados_cwl.main(
1673             ["--create-workflow", "--debug",
1674              "--api=containers",
1675              "--project-uuid", project_uuid,
1676              "--name", "testing 123",
1677              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1678             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1679         self.assertEqual(exited, 0)
1680
1681         stubs.api.pipeline_templates().create.refute_called()
1682         stubs.api.container_requests().create.refute_called()
1683
1684         body = {
1685             "workflow": {
1686                 "owner_uuid": project_uuid,
1687                 "name": "testing 123",
1688                 "description": "",
1689                 "definition": self.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
1698     @stubs
1699     def test_incompatible_api(self, stubs):
1700         capture_stderr = io.StringIO()
1701         logging.getLogger('arvados.cwl-runner').addHandler(
1702             logging.StreamHandler(capture_stderr))
1703
1704         exited = arvados_cwl.main(
1705             ["--update-workflow", self.existing_workflow_uuid,
1706              "--api=jobs",
1707              "--debug",
1708              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1709             sys.stderr, sys.stderr, api_client=stubs.api)
1710         self.assertEqual(exited, 1)
1711         self.assertRegexpMatches(
1712             capture_stderr.getvalue(),
1713             "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1714
1715     @stubs
1716     def test_update(self, stubs):
1717         exited = arvados_cwl.main(
1718             ["--update-workflow", self.existing_workflow_uuid,
1719              "--debug",
1720              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1721             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1722         self.assertEqual(exited, 0)
1723
1724         body = {
1725             "workflow": {
1726                 "name": "submit_wf.cwl",
1727                 "description": "",
1728                 "definition": self.expect_workflow,
1729             }
1730         }
1731         stubs.api.workflows().update.assert_called_with(
1732             uuid=self.existing_workflow_uuid,
1733             body=JsonDiffMatcher(body))
1734         self.assertEqual(stubs.capture_stdout.getvalue(),
1735                          self.existing_workflow_uuid + '\n')
1736
1737
1738     @stubs
1739     def test_update_name(self, stubs):
1740         exited = arvados_cwl.main(
1741             ["--update-workflow", self.existing_workflow_uuid,
1742              "--debug", "--name", "testing 123",
1743              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1744             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1745         self.assertEqual(exited, 0)
1746
1747         body = {
1748             "workflow": {
1749                 "name": "testing 123",
1750                 "description": "",
1751                 "definition": self.expect_workflow,
1752             }
1753         }
1754         stubs.api.workflows().update.assert_called_with(
1755             uuid=self.existing_workflow_uuid,
1756             body=JsonDiffMatcher(body))
1757         self.assertEqual(stubs.capture_stdout.getvalue(),
1758                          self.existing_workflow_uuid + '\n')
1759
1760
1761     @stubs
1762     def test_create_collection_per_tool(self, stubs):
1763         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1764
1765         exited = arvados_cwl.main(
1766             ["--create-workflow", "--debug",
1767              "--api=containers",
1768              "--project-uuid", project_uuid,
1769              "tests/collection_per_tool/collection_per_tool.cwl"],
1770             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1771         self.assertEqual(exited, 0)
1772
1773         toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1774         expect_workflow = StripYAMLComments(open(toolfile).read())
1775
1776         body = {
1777             "workflow": {
1778                 "owner_uuid": project_uuid,
1779                 "name": "collection_per_tool.cwl",
1780                 "description": "",
1781                 "definition": expect_workflow,
1782             }
1783         }
1784         stubs.api.workflows().create.assert_called_with(
1785             body=JsonDiffMatcher(body))
1786
1787         self.assertEqual(stubs.capture_stdout.getvalue(),
1788                          stubs.expect_workflow_uuid + '\n')
1789
1790 class TestTemplateInputs(unittest.TestCase):
1791     expect_template = {
1792         "components": {
1793             "inputs_test.cwl": {
1794                 'runtime_constraints': {
1795                     'docker_image': '999999999999999999999999999999d3+99',
1796                     'min_ram_mb_per_node': 1024
1797                 },
1798                 'script_parameters': {
1799                     'cwl:tool':
1800                     'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main',
1801                     'optionalFloatInput': None,
1802                     'fileInput': {
1803                         'type': 'File',
1804                         'dataclass': 'File',
1805                         'required': True,
1806                         'title': "It's a file; we expect to find some characters in it.",
1807                         'description': 'If there were anything further to say, it would be said here,\nor here.'
1808                     },
1809                     'floatInput': {
1810                         'type': 'float',
1811                         'dataclass': 'number',
1812                         'required': True,
1813                         'title': 'Floats like a duck',
1814                         'default': 0.1,
1815                         'value': 0.1,
1816                     },
1817                     'optionalFloatInput': {
1818                         'type': ['null', 'float'],
1819                         'dataclass': 'number',
1820                         'required': False,
1821                     },
1822                     'boolInput': {
1823                         'type': 'boolean',
1824                         'dataclass': 'boolean',
1825                         'required': True,
1826                         'title': 'True or false?',
1827                     },
1828                 },
1829                 'repository': 'arvados',
1830                 'script_version': 'master',
1831                 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1832                 'script': 'cwl-runner',
1833             },
1834         },
1835         "name": "inputs_test.cwl",
1836     }
1837
1838     @stubs
1839     def test_inputs_empty(self, stubs):
1840         exited = arvados_cwl.main(
1841             ["--create-template",
1842              "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1843             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1844         self.assertEqual(exited, 0)
1845
1846         stubs.api.pipeline_templates().create.assert_called_with(
1847             body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1848
1849     @stubs
1850     def test_inputs(self, stubs):
1851         exited = arvados_cwl.main(
1852             ["--create-template",
1853              "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1854             stubs.capture_stdout, sys.stderr, api_client=stubs.api)
1855         self.assertEqual(exited, 0)
1856
1857         expect_template = copy.deepcopy(self.expect_template)
1858         params = expect_template[
1859             "components"]["inputs_test.cwl"]["script_parameters"]
1860         params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1861         params["cwl:tool"] = 'a2de777156fb700f1363b1f2e370adca+60/workflow.cwl#main'
1862         params["floatInput"]["value"] = 1.234
1863         params["boolInput"]["value"] = True
1864
1865         stubs.api.pipeline_templates().create.assert_called_with(
1866             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)