Merge branch '14345-proppatch'
[arvados.git] / sdk / cwl / tests / test_container.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 import arvados_cwl
6 import arvados_cwl.context
7 import arvados_cwl.util
8 from arvados_cwl.arvdocker import arv_docker_clear_cache
9 import copy
10 import arvados.config
11 import logging
12 import mock
13 import unittest
14 import os
15 import functools
16 import cwltool.process
17 import cwltool.secrets
18 from schema_salad.ref_resolver import Loader
19 from schema_salad.sourceline import cmap
20
21 from .matcher import JsonDiffMatcher
22 from .mock_discovery import get_rootDesc
23
24 if not os.getenv('ARVADOS_DEBUG'):
25     logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
26     logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
27
28 class CollectionMock(object):
29     def __init__(self, vwdmock, *args, **kwargs):
30         self.vwdmock = vwdmock
31         self.count = 0
32
33     def open(self, *args, **kwargs):
34         self.count += 1
35         return self.vwdmock.open(*args, **kwargs)
36
37     def copy(self, *args, **kwargs):
38         self.count += 1
39         self.vwdmock.copy(*args, **kwargs)
40
41     def save_new(self, *args, **kwargs):
42         pass
43
44     def __len__(self):
45         return self.count
46
47     def portable_data_hash(self):
48         if self.count == 0:
49             return arvados.config.EMPTY_BLOCK_LOCATOR
50         else:
51             return "99999999999999999999999999999996+99"
52
53
54 class TestContainer(unittest.TestCase):
55
56     def helper(self, runner, enable_reuse=True):
57         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
58
59         make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
60                                          collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
61         loadingContext = arvados_cwl.context.ArvLoadingContext(
62             {"avsc_names": avsc_names,
63              "basedir": "",
64              "make_fs_access": make_fs_access,
65              "loader": Loader({}),
66              "metadata": {"cwlVersion": "v1.0"}})
67         runtimeContext = arvados_cwl.context.ArvRuntimeContext(
68             {"work_api": "containers",
69              "basedir": "",
70              "name": "test_run_"+str(enable_reuse),
71              "make_fs_access": make_fs_access,
72              "tmpdir": "/tmp",
73              "enable_reuse": enable_reuse,
74              "priority": 500})
75
76         return loadingContext, runtimeContext
77
78     # The test passes no builder.resources
79     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
80     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
81     def test_run(self, keepdocker):
82         for enable_reuse in (True, False):
83             arv_docker_clear_cache()
84
85             runner = mock.MagicMock()
86             runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
87             runner.ignore_docker_for_reuse = False
88             runner.intermediate_output_ttl = 0
89             runner.secret_store = cwltool.secrets.SecretStore()
90
91             keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
92             runner.api.collections().get().execute.return_value = {
93                 "portable_data_hash": "99999999999999999999999999999993+99"}
94
95             tool = cmap({
96                 "inputs": [],
97                 "outputs": [],
98                 "baseCommand": "ls",
99                 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
100                 "id": "#",
101                 "class": "CommandLineTool"
102             })
103
104             loadingContext, runtimeContext = self.helper(runner, enable_reuse)
105
106             arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
107             arvtool.formatgraph = None
108
109             for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
110                 j.run(runtimeContext)
111                 runner.api.container_requests().create.assert_called_with(
112                     body=JsonDiffMatcher({
113                         'environment': {
114                             'HOME': '/var/spool/cwl',
115                             'TMPDIR': '/tmp'
116                         },
117                         'name': 'test_run_'+str(enable_reuse),
118                         'runtime_constraints': {
119                             'vcpus': 1,
120                             'ram': 1073741824
121                         },
122                         'use_existing': enable_reuse,
123                         'priority': 500,
124                         'mounts': {
125                             '/tmp': {'kind': 'tmp',
126                                      "capacity": 1073741824
127                                  },
128                             '/var/spool/cwl': {'kind': 'tmp',
129                                                "capacity": 1073741824 }
130                         },
131                         'state': 'Committed',
132                         'output_name': 'Output for step test_run_'+str(enable_reuse),
133                         'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
134                         'output_path': '/var/spool/cwl',
135                         'output_ttl': 0,
136                         'container_image': '99999999999999999999999999999993+99',
137                         'command': ['ls', '/var/spool/cwl'],
138                         'cwd': '/var/spool/cwl',
139                         'scheduling_parameters': {},
140                         'properties': {},
141                         'secret_mounts': {}
142                     }))
143
144     # The test passes some fields in builder.resources
145     # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
146     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
147     def test_resource_requirements(self, keepdocker):
148         arv_docker_clear_cache()
149         runner = mock.MagicMock()
150         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
151         runner.ignore_docker_for_reuse = False
152         runner.intermediate_output_ttl = 3600
153         runner.secret_store = cwltool.secrets.SecretStore()
154
155         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
156         runner.api.collections().get().execute.return_value = {
157             "portable_data_hash": "99999999999999999999999999999993+99"}
158
159         tool = cmap({
160             "inputs": [],
161             "outputs": [],
162             "hints": [{
163                 "class": "ResourceRequirement",
164                 "coresMin": 3,
165                 "ramMin": 3000,
166                 "tmpdirMin": 4000,
167                 "outdirMin": 5000
168             }, {
169                 "class": "http://arvados.org/cwl#RuntimeConstraints",
170                 "keep_cache": 512
171             }, {
172                 "class": "http://arvados.org/cwl#APIRequirement",
173             }, {
174                 "class": "http://arvados.org/cwl#PartitionRequirement",
175                 "partition": "blurb"
176             }, {
177                 "class": "http://arvados.org/cwl#IntermediateOutput",
178                 "outputTTL": 7200
179             }, {
180                 "class": "http://arvados.org/cwl#ReuseRequirement",
181                 "enableReuse": False
182             }],
183             "baseCommand": "ls",
184             "id": "#",
185             "class": "CommandLineTool"
186         })
187
188         loadingContext, runtimeContext = self.helper(runner)
189         runtimeContext.name = "test_resource_requirements"
190
191         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
192         arvtool.formatgraph = None
193         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
194             j.run(runtimeContext)
195
196         call_args, call_kwargs = runner.api.container_requests().create.call_args
197
198         call_body_expected = {
199             'environment': {
200                 'HOME': '/var/spool/cwl',
201                 'TMPDIR': '/tmp'
202             },
203             'name': 'test_resource_requirements',
204             'runtime_constraints': {
205                 'vcpus': 3,
206                 'ram': 3145728000,
207                 'keep_cache_ram': 536870912,
208                 'API': True
209             },
210             'use_existing': False,
211             'priority': 500,
212             'mounts': {
213                 '/tmp': {'kind': 'tmp',
214                          "capacity": 4194304000 },
215                 '/var/spool/cwl': {'kind': 'tmp',
216                                    "capacity": 5242880000 }
217             },
218             'state': 'Committed',
219             'output_name': 'Output for step test_resource_requirements',
220             'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
221             'output_path': '/var/spool/cwl',
222             'output_ttl': 7200,
223             'container_image': '99999999999999999999999999999993+99',
224             'command': ['ls'],
225             'cwd': '/var/spool/cwl',
226             'scheduling_parameters': {
227                 'partitions': ['blurb']
228             },
229             'properties': {},
230             'secret_mounts': {}
231         }
232
233         call_body = call_kwargs.get('body', None)
234         self.assertNotEqual(None, call_body)
235         for key in call_body:
236             self.assertEqual(call_body_expected.get(key), call_body.get(key))
237
238
239     # The test passes some fields in builder.resources
240     # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
241     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
242     @mock.patch("arvados.collection.Collection")
243     def test_initial_work_dir(self, collection_mock, keepdocker):
244         arv_docker_clear_cache()
245         runner = mock.MagicMock()
246         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
247         runner.ignore_docker_for_reuse = False
248         runner.intermediate_output_ttl = 0
249         runner.secret_store = cwltool.secrets.SecretStore()
250
251         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
252         runner.api.collections().get().execute.return_value = {
253             "portable_data_hash": "99999999999999999999999999999993+99"}
254
255         sourcemock = mock.MagicMock()
256         def get_collection_mock(p):
257             if "/" in p:
258                 return (sourcemock, p.split("/", 1)[1])
259             else:
260                 return (sourcemock, "")
261         runner.fs_access.get_collection.side_effect = get_collection_mock
262
263         vwdmock = mock.MagicMock()
264         collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
265
266         tool = cmap({
267             "inputs": [],
268             "outputs": [],
269             "hints": [{
270                 "class": "InitialWorkDirRequirement",
271                 "listing": [{
272                     "class": "File",
273                     "basename": "foo",
274                     "location": "keep:99999999999999999999999999999995+99/bar"
275                 },
276                 {
277                     "class": "Directory",
278                     "basename": "foo2",
279                     "location": "keep:99999999999999999999999999999995+99"
280                 },
281                 {
282                     "class": "File",
283                     "basename": "filename",
284                     "location": "keep:99999999999999999999999999999995+99/baz/filename"
285                 },
286                 {
287                     "class": "Directory",
288                     "basename": "subdir",
289                     "location": "keep:99999999999999999999999999999995+99/subdir"
290                 }                        ]
291             }],
292             "baseCommand": "ls",
293             "id": "#",
294             "class": "CommandLineTool"
295         })
296
297         loadingContext, runtimeContext = self.helper(runner)
298         runtimeContext.name = "test_initial_work_dir"
299
300         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
301         arvtool.formatgraph = None
302         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
303             j.run(runtimeContext)
304
305         call_args, call_kwargs = runner.api.container_requests().create.call_args
306
307         vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
308         vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
309         vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
310         vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
311
312         call_body_expected = {
313             'environment': {
314                 'HOME': '/var/spool/cwl',
315                 'TMPDIR': '/tmp'
316             },
317             'name': 'test_initial_work_dir',
318             'runtime_constraints': {
319                 'vcpus': 1,
320                 'ram': 1073741824
321             },
322             'use_existing': True,
323             'priority': 500,
324             'mounts': {
325                 '/tmp': {'kind': 'tmp',
326                          "capacity": 1073741824 },
327                 '/var/spool/cwl': {'kind': 'tmp',
328                                    "capacity": 1073741824 },
329                 '/var/spool/cwl/foo': {
330                     'kind': 'collection',
331                     'path': 'foo',
332                     'portable_data_hash': '99999999999999999999999999999996+99'
333                 },
334                 '/var/spool/cwl/foo2': {
335                     'kind': 'collection',
336                     'path': 'foo2',
337                     'portable_data_hash': '99999999999999999999999999999996+99'
338                 },
339                 '/var/spool/cwl/filename': {
340                     'kind': 'collection',
341                     'path': 'filename',
342                     'portable_data_hash': '99999999999999999999999999999996+99'
343                 },
344                 '/var/spool/cwl/subdir': {
345                     'kind': 'collection',
346                     'path': 'subdir',
347                     'portable_data_hash': '99999999999999999999999999999996+99'
348                 }
349             },
350             'state': 'Committed',
351             'output_name': 'Output for step test_initial_work_dir',
352             'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
353             'output_path': '/var/spool/cwl',
354             'output_ttl': 0,
355             'container_image': '99999999999999999999999999999993+99',
356             'command': ['ls'],
357             'cwd': '/var/spool/cwl',
358             'scheduling_parameters': {
359             },
360             'properties': {},
361             'secret_mounts': {}
362         }
363
364         call_body = call_kwargs.get('body', None)
365         self.assertNotEqual(None, call_body)
366         for key in call_body:
367             self.assertEqual(call_body_expected.get(key), call_body.get(key))
368
369
370     # Test redirecting stdin/stdout/stderr
371     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
372     def test_redirects(self, keepdocker):
373         arv_docker_clear_cache()
374
375         runner = mock.MagicMock()
376         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
377         runner.ignore_docker_for_reuse = False
378         runner.intermediate_output_ttl = 0
379         runner.secret_store = cwltool.secrets.SecretStore()
380
381         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
382         runner.api.collections().get().execute.return_value = {
383             "portable_data_hash": "99999999999999999999999999999993+99"}
384
385         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
386
387         tool = cmap({
388             "inputs": [],
389             "outputs": [],
390             "baseCommand": "ls",
391             "stdout": "stdout.txt",
392             "stderr": "stderr.txt",
393             "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
394             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
395             "id": "#",
396             "class": "CommandLineTool"
397         })
398
399         loadingContext, runtimeContext = self.helper(runner)
400         runtimeContext.name = "test_run_redirect"
401
402         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
403         arvtool.formatgraph = None
404         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
405             j.run(runtimeContext)
406             runner.api.container_requests().create.assert_called_with(
407                 body=JsonDiffMatcher({
408                     'environment': {
409                         'HOME': '/var/spool/cwl',
410                         'TMPDIR': '/tmp'
411                     },
412                     'name': 'test_run_redirect',
413                     'runtime_constraints': {
414                         'vcpus': 1,
415                         'ram': 1073741824
416                     },
417                     'use_existing': True,
418                     'priority': 500,
419                     'mounts': {
420                         '/tmp': {'kind': 'tmp',
421                                  "capacity": 1073741824 },
422                         '/var/spool/cwl': {'kind': 'tmp',
423                                            "capacity": 1073741824 },
424                         "stderr": {
425                             "kind": "file",
426                             "path": "/var/spool/cwl/stderr.txt"
427                         },
428                         "stdin": {
429                             "kind": "collection",
430                             "path": "file.txt",
431                             "portable_data_hash": "99999999999999999999999999999996+99"
432                         },
433                         "stdout": {
434                             "kind": "file",
435                             "path": "/var/spool/cwl/stdout.txt"
436                         },
437                     },
438                     'state': 'Committed',
439                     "output_name": "Output for step test_run_redirect",
440                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
441                     'output_path': '/var/spool/cwl',
442                     'output_ttl': 0,
443                     'container_image': '99999999999999999999999999999993+99',
444                     'command': ['ls', '/var/spool/cwl'],
445                     'cwd': '/var/spool/cwl',
446                     'scheduling_parameters': {},
447                     'properties': {},
448                     'secret_mounts': {}
449                 }))
450
451     @mock.patch("arvados.collection.Collection")
452     def test_done(self, col):
453         api = mock.MagicMock()
454
455         runner = mock.MagicMock()
456         runner.api = api
457         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
458         runner.num_retries = 0
459         runner.ignore_docker_for_reuse = False
460         runner.intermediate_output_ttl = 0
461         runner.secret_store = cwltool.secrets.SecretStore()
462
463         runner.api.containers().get().execute.return_value = {"state":"Complete",
464                                                               "output": "abc+123",
465                                                               "exit_code": 0}
466
467         col().open.return_value = []
468
469         loadingContext, runtimeContext = self.helper(runner)
470
471         arvjob = arvados_cwl.ArvadosContainer(runner,
472                                               runtimeContext,
473                                               mock.MagicMock(),
474                                               {},
475                                               None,
476                                               [],
477                                               [],
478                                               "testjob")
479         arvjob.output_callback = mock.MagicMock()
480         arvjob.collect_outputs = mock.MagicMock()
481         arvjob.successCodes = [0]
482         arvjob.outdir = "/var/spool/cwl"
483         arvjob.output_ttl = 3600
484
485         arvjob.collect_outputs.return_value = {"out": "stuff"}
486
487         arvjob.done({
488             "state": "Final",
489             "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
490             "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
491             "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
492             "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
493             "modified_at": "2017-05-26T12:01:22Z"
494         })
495
496         self.assertFalse(api.collections().create.called)
497         self.assertFalse(runner.runtime_status_error.called)
498
499         arvjob.collect_outputs.assert_called_with("keep:abc+123")
500         arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
501         runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
502
503     @mock.patch("arvados_cwl.util.get_current_container")
504     @mock.patch("arvados.collection.CollectionReader")
505     @mock.patch("arvados.collection.Collection")
506     def test_child_failure(self, col, reader, gcc_mock):
507         api = mock.MagicMock()
508         api._rootDesc = copy.deepcopy(get_rootDesc())
509         del api._rootDesc.get('resources')['jobs']['methods']['create']
510
511         # Set up runner with mocked runtime_status_update()
512         self.assertFalse(gcc_mock.called)
513         runtime_status_update = mock.MagicMock()
514         arvados_cwl.ArvCwlExecutor.runtime_status_update = runtime_status_update
515         runner = arvados_cwl.ArvCwlExecutor(api)
516         self.assertEqual(runner.work_api, 'containers')
517
518         # Make sure ArvCwlExecutor thinks it's running inside a container so it
519         # adds the logging handler that will call runtime_status_update() mock
520         gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
521         self.assertTrue(gcc_mock.called)
522         root_logger = logging.getLogger('')
523         handlerClasses = [h.__class__ for h in root_logger.handlers]
524         self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
525
526         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
527         runner.num_retries = 0
528         runner.ignore_docker_for_reuse = False
529         runner.intermediate_output_ttl = 0
530         runner.secret_store = cwltool.secrets.SecretStore()
531         runner.label = mock.MagicMock()
532         runner.label.return_value = '[container testjob]'
533
534         runner.api.containers().get().execute.return_value = {
535             "state":"Complete",
536             "output": "abc+123",
537             "exit_code": 1,
538             "log": "def+234"
539         }
540
541         col().open.return_value = []
542
543         loadingContext, runtimeContext = self.helper(runner)
544
545         arvjob = arvados_cwl.ArvadosContainer(runner,
546                                               runtimeContext,
547                                               mock.MagicMock(),
548                                               {},
549                                               None,
550                                               [],
551                                               [],
552                                               "testjob")
553         arvjob.output_callback = mock.MagicMock()
554         arvjob.collect_outputs = mock.MagicMock()
555         arvjob.successCodes = [0]
556         arvjob.outdir = "/var/spool/cwl"
557         arvjob.output_ttl = 3600
558         arvjob.collect_outputs.return_value = {"out": "stuff"}
559
560         arvjob.done({
561             "state": "Final",
562             "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
563             "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
564             "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
565             "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
566             "modified_at": "2017-05-26T12:01:22Z"
567         })
568
569         runtime_status_update.assert_called_with(
570             'error',
571             'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
572             '  ** log is empty **'
573         )
574         arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
575
576     # The test passes no builder.resources
577     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
578     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
579     def test_mounts(self, keepdocker):
580         arv_docker_clear_cache()
581
582         runner = mock.MagicMock()
583         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
584         runner.ignore_docker_for_reuse = False
585         runner.intermediate_output_ttl = 0
586         runner.secret_store = cwltool.secrets.SecretStore()
587
588         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
589         runner.api.collections().get().execute.return_value = {
590             "portable_data_hash": "99999999999999999999999999999994+99",
591             "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
592
593         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
594
595         tool = cmap({
596             "inputs": [
597                 {"id": "p1",
598                  "type": "Directory"}
599             ],
600             "outputs": [],
601             "baseCommand": "ls",
602             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
603             "id": "#",
604             "class": "CommandLineTool"
605         })
606
607         loadingContext, runtimeContext = self.helper(runner)
608         runtimeContext.name = "test_run_mounts"
609
610         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
611         arvtool.formatgraph = None
612         job_order = {
613             "p1": {
614                 "class": "Directory",
615                 "location": "keep:99999999999999999999999999999994+44",
616                 "listing": [
617                     {
618                         "class": "File",
619                         "location": "keep:99999999999999999999999999999994+44/file1",
620                     },
621                     {
622                         "class": "File",
623                         "location": "keep:99999999999999999999999999999994+44/file2",
624                     }
625                 ]
626             }
627         }
628         for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
629             j.run(runtimeContext)
630             runner.api.container_requests().create.assert_called_with(
631                 body=JsonDiffMatcher({
632                     'environment': {
633                         'HOME': '/var/spool/cwl',
634                         'TMPDIR': '/tmp'
635                     },
636                     'name': 'test_run_mounts',
637                     'runtime_constraints': {
638                         'vcpus': 1,
639                         'ram': 1073741824
640                     },
641                     'use_existing': True,
642                     'priority': 500,
643                     'mounts': {
644                         "/keep/99999999999999999999999999999994+44": {
645                             "kind": "collection",
646                             "portable_data_hash": "99999999999999999999999999999994+44"
647                         },
648                         '/tmp': {'kind': 'tmp',
649                                  "capacity": 1073741824 },
650                         '/var/spool/cwl': {'kind': 'tmp',
651                                            "capacity": 1073741824 }
652                     },
653                     'state': 'Committed',
654                     'output_name': 'Output for step test_run_mounts',
655                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
656                     'output_path': '/var/spool/cwl',
657                     'output_ttl': 0,
658                     'container_image': '99999999999999999999999999999994+99',
659                     'command': ['ls', '/var/spool/cwl'],
660                     'cwd': '/var/spool/cwl',
661                     'scheduling_parameters': {},
662                     'properties': {},
663                     'secret_mounts': {}
664                 }))
665
666     # The test passes no builder.resources
667     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
668     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
669     def test_secrets(self, keepdocker):
670         arv_docker_clear_cache()
671
672         runner = mock.MagicMock()
673         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
674         runner.ignore_docker_for_reuse = False
675         runner.intermediate_output_ttl = 0
676         runner.secret_store = cwltool.secrets.SecretStore()
677
678         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
679         runner.api.collections().get().execute.return_value = {
680             "portable_data_hash": "99999999999999999999999999999993+99"}
681
682         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
683
684         tool = cmap({"arguments": ["md5sum", "example.conf"],
685                      "class": "CommandLineTool",
686                      "hints": [
687                          {
688                              "class": "http://commonwl.org/cwltool#Secrets",
689                              "secrets": [
690                                  "#secret_job.cwl/pw"
691                              ]
692                          }
693                      ],
694                      "id": "#secret_job.cwl",
695                      "inputs": [
696                          {
697                              "id": "#secret_job.cwl/pw",
698                              "type": "string"
699                          }
700                      ],
701                      "outputs": [
702                      ],
703                      "requirements": [
704                          {
705                              "class": "InitialWorkDirRequirement",
706                              "listing": [
707                                  {
708                                      "entry": "username: user\npassword: $(inputs.pw)\n",
709                                      "entryname": "example.conf"
710                                  }
711                              ]
712                          }
713                      ]})
714
715         loadingContext, runtimeContext = self.helper(runner)
716         runtimeContext.name = "test_secrets"
717
718         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
719         arvtool.formatgraph = None
720
721         job_order = {"pw": "blorp"}
722         runner.secret_store.store(["pw"], job_order)
723
724         for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
725             j.run(runtimeContext)
726             runner.api.container_requests().create.assert_called_with(
727                 body=JsonDiffMatcher({
728                     'environment': {
729                         'HOME': '/var/spool/cwl',
730                         'TMPDIR': '/tmp'
731                     },
732                     'name': 'test_secrets',
733                     'runtime_constraints': {
734                         'vcpus': 1,
735                         'ram': 1073741824
736                     },
737                     'use_existing': True,
738                     'priority': 500,
739                     'mounts': {
740                         '/tmp': {'kind': 'tmp',
741                                  "capacity": 1073741824
742                              },
743                         '/var/spool/cwl': {'kind': 'tmp',
744                                            "capacity": 1073741824 }
745                     },
746                     'state': 'Committed',
747                     'output_name': 'Output for step test_secrets',
748                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
749                     'output_path': '/var/spool/cwl',
750                     'output_ttl': 0,
751                     'container_image': '99999999999999999999999999999993+99',
752                     'command': ['md5sum', 'example.conf'],
753                     'cwd': '/var/spool/cwl',
754                     'scheduling_parameters': {},
755                     'properties': {},
756                     "secret_mounts": {
757                         "/var/spool/cwl/example.conf": {
758                             "content": "username: user\npassword: blorp\n",
759                             "kind": "text"
760                         }
761                     }
762                 }))
763
764     # The test passes no builder.resources
765     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
766     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
767     def test_timelimit(self, keepdocker):
768         arv_docker_clear_cache()
769
770         runner = mock.MagicMock()
771         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
772         runner.ignore_docker_for_reuse = False
773         runner.intermediate_output_ttl = 0
774         runner.secret_store = cwltool.secrets.SecretStore()
775
776         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
777         runner.api.collections().get().execute.return_value = {
778             "portable_data_hash": "99999999999999999999999999999993+99"}
779
780         tool = cmap({
781             "inputs": [],
782             "outputs": [],
783             "baseCommand": "ls",
784             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
785             "id": "#",
786             "class": "CommandLineTool",
787             "hints": [
788                 {
789                     "class": "http://commonwl.org/cwltool#TimeLimit",
790                     "timelimit": 42
791                 }
792             ]
793         })
794
795         loadingContext, runtimeContext = self.helper(runner)
796         runtimeContext.name = "test_timelimit"
797
798         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
799         arvtool.formatgraph = None
800
801         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
802             j.run(runtimeContext)
803
804         _, kwargs = runner.api.container_requests().create.call_args
805         self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))