a34d550f62ad2b24a1ff156d7b740377c811cab5
[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              "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
76             })
77
78         return loadingContext, runtimeContext
79
80     # The test passes no builder.resources
81     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
82     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
83     def test_run(self, keepdocker):
84         for enable_reuse in (True, False):
85             arv_docker_clear_cache()
86
87             runner = mock.MagicMock()
88             runner.ignore_docker_for_reuse = False
89             runner.intermediate_output_ttl = 0
90             runner.secret_store = cwltool.secrets.SecretStore()
91
92             keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
93             runner.api.collections().get().execute.return_value = {
94                 "portable_data_hash": "99999999999999999999999999999993+99"}
95
96             tool = cmap({
97                 "inputs": [],
98                 "outputs": [],
99                 "baseCommand": "ls",
100                 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
101                 "id": "#",
102                 "class": "CommandLineTool"
103             })
104
105             loadingContext, runtimeContext = self.helper(runner, enable_reuse)
106
107             arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
108             arvtool.formatgraph = None
109
110             for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
111                 j.run(runtimeContext)
112                 runner.api.container_requests().create.assert_called_with(
113                     body=JsonDiffMatcher({
114                         'environment': {
115                             'HOME': '/var/spool/cwl',
116                             'TMPDIR': '/tmp'
117                         },
118                         'name': 'test_run_'+str(enable_reuse),
119                         'runtime_constraints': {
120                             'vcpus': 1,
121                             'ram': 1073741824
122                         },
123                         'use_existing': enable_reuse,
124                         'priority': 500,
125                         'mounts': {
126                             '/tmp': {'kind': 'tmp',
127                                      "capacity": 1073741824
128                                  },
129                             '/var/spool/cwl': {'kind': 'tmp',
130                                                "capacity": 1073741824 }
131                         },
132                         'state': 'Committed',
133                         'output_name': 'Output for step test_run_'+str(enable_reuse),
134                         'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
135                         'output_path': '/var/spool/cwl',
136                         'output_ttl': 0,
137                         'container_image': '99999999999999999999999999999993+99',
138                         'command': ['ls', '/var/spool/cwl'],
139                         'cwd': '/var/spool/cwl',
140                         'scheduling_parameters': {},
141                         'properties': {},
142                         'secret_mounts': {}
143                     }))
144
145     # The test passes some fields in builder.resources
146     # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
147     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
148     def test_resource_requirements(self, keepdocker):
149         arv_docker_clear_cache()
150         runner = mock.MagicMock()
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.ignore_docker_for_reuse = False
247         runner.intermediate_output_ttl = 0
248         runner.secret_store = cwltool.secrets.SecretStore()
249
250         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
251         runner.api.collections().get().execute.return_value = {
252             "portable_data_hash": "99999999999999999999999999999993+99"}
253
254         sourcemock = mock.MagicMock()
255         def get_collection_mock(p):
256             if "/" in p:
257                 return (sourcemock, p.split("/", 1)[1])
258             else:
259                 return (sourcemock, "")
260         runner.fs_access.get_collection.side_effect = get_collection_mock
261
262         vwdmock = mock.MagicMock()
263         collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
264
265         tool = cmap({
266             "inputs": [],
267             "outputs": [],
268             "hints": [{
269                 "class": "InitialWorkDirRequirement",
270                 "listing": [{
271                     "class": "File",
272                     "basename": "foo",
273                     "location": "keep:99999999999999999999999999999995+99/bar"
274                 },
275                 {
276                     "class": "Directory",
277                     "basename": "foo2",
278                     "location": "keep:99999999999999999999999999999995+99"
279                 },
280                 {
281                     "class": "File",
282                     "basename": "filename",
283                     "location": "keep:99999999999999999999999999999995+99/baz/filename"
284                 },
285                 {
286                     "class": "Directory",
287                     "basename": "subdir",
288                     "location": "keep:99999999999999999999999999999995+99/subdir"
289                 }                        ]
290             }],
291             "baseCommand": "ls",
292             "id": "#",
293             "class": "CommandLineTool"
294         })
295
296         loadingContext, runtimeContext = self.helper(runner)
297         runtimeContext.name = "test_initial_work_dir"
298
299         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
300         arvtool.formatgraph = None
301         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
302             j.run(runtimeContext)
303
304         call_args, call_kwargs = runner.api.container_requests().create.call_args
305
306         vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
307         vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
308         vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
309         vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
310
311         call_body_expected = {
312             'environment': {
313                 'HOME': '/var/spool/cwl',
314                 'TMPDIR': '/tmp'
315             },
316             'name': 'test_initial_work_dir',
317             'runtime_constraints': {
318                 'vcpus': 1,
319                 'ram': 1073741824
320             },
321             'use_existing': True,
322             'priority': 500,
323             'mounts': {
324                 '/tmp': {'kind': 'tmp',
325                          "capacity": 1073741824 },
326                 '/var/spool/cwl': {'kind': 'tmp',
327                                    "capacity": 1073741824 },
328                 '/var/spool/cwl/foo': {
329                     'kind': 'collection',
330                     'path': 'foo',
331                     'portable_data_hash': '99999999999999999999999999999996+99'
332                 },
333                 '/var/spool/cwl/foo2': {
334                     'kind': 'collection',
335                     'path': 'foo2',
336                     'portable_data_hash': '99999999999999999999999999999996+99'
337                 },
338                 '/var/spool/cwl/filename': {
339                     'kind': 'collection',
340                     'path': 'filename',
341                     'portable_data_hash': '99999999999999999999999999999996+99'
342                 },
343                 '/var/spool/cwl/subdir': {
344                     'kind': 'collection',
345                     'path': 'subdir',
346                     'portable_data_hash': '99999999999999999999999999999996+99'
347                 }
348             },
349             'state': 'Committed',
350             'output_name': 'Output for step test_initial_work_dir',
351             'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
352             'output_path': '/var/spool/cwl',
353             'output_ttl': 0,
354             'container_image': '99999999999999999999999999999993+99',
355             'command': ['ls'],
356             'cwd': '/var/spool/cwl',
357             'scheduling_parameters': {
358             },
359             'properties': {},
360             'secret_mounts': {}
361         }
362
363         call_body = call_kwargs.get('body', None)
364         self.assertNotEqual(None, call_body)
365         for key in call_body:
366             self.assertEqual(call_body_expected.get(key), call_body.get(key))
367
368
369     # Test redirecting stdin/stdout/stderr
370     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
371     def test_redirects(self, keepdocker):
372         arv_docker_clear_cache()
373
374         runner = mock.MagicMock()
375         runner.ignore_docker_for_reuse = False
376         runner.intermediate_output_ttl = 0
377         runner.secret_store = cwltool.secrets.SecretStore()
378
379         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
380         runner.api.collections().get().execute.return_value = {
381             "portable_data_hash": "99999999999999999999999999999993+99"}
382
383         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
384
385         tool = cmap({
386             "inputs": [],
387             "outputs": [],
388             "baseCommand": "ls",
389             "stdout": "stdout.txt",
390             "stderr": "stderr.txt",
391             "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
392             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
393             "id": "#",
394             "class": "CommandLineTool"
395         })
396
397         loadingContext, runtimeContext = self.helper(runner)
398         runtimeContext.name = "test_run_redirect"
399
400         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
401         arvtool.formatgraph = None
402         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
403             j.run(runtimeContext)
404             runner.api.container_requests().create.assert_called_with(
405                 body=JsonDiffMatcher({
406                     'environment': {
407                         'HOME': '/var/spool/cwl',
408                         'TMPDIR': '/tmp'
409                     },
410                     'name': 'test_run_redirect',
411                     'runtime_constraints': {
412                         'vcpus': 1,
413                         'ram': 1073741824
414                     },
415                     'use_existing': True,
416                     'priority': 500,
417                     'mounts': {
418                         '/tmp': {'kind': 'tmp',
419                                  "capacity": 1073741824 },
420                         '/var/spool/cwl': {'kind': 'tmp',
421                                            "capacity": 1073741824 },
422                         "stderr": {
423                             "kind": "file",
424                             "path": "/var/spool/cwl/stderr.txt"
425                         },
426                         "stdin": {
427                             "kind": "collection",
428                             "path": "file.txt",
429                             "portable_data_hash": "99999999999999999999999999999996+99"
430                         },
431                         "stdout": {
432                             "kind": "file",
433                             "path": "/var/spool/cwl/stdout.txt"
434                         },
435                     },
436                     'state': 'Committed',
437                     "output_name": "Output for step test_run_redirect",
438                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
439                     'output_path': '/var/spool/cwl',
440                     'output_ttl': 0,
441                     'container_image': '99999999999999999999999999999993+99',
442                     'command': ['ls', '/var/spool/cwl'],
443                     'cwd': '/var/spool/cwl',
444                     'scheduling_parameters': {},
445                     'properties': {},
446                     'secret_mounts': {}
447                 }))
448
449     @mock.patch("arvados.collection.Collection")
450     def test_done(self, col):
451         api = mock.MagicMock()
452
453         runner = mock.MagicMock()
454         runner.api = api
455         runner.num_retries = 0
456         runner.ignore_docker_for_reuse = False
457         runner.intermediate_output_ttl = 0
458         runner.secret_store = cwltool.secrets.SecretStore()
459
460         runner.api.containers().get().execute.return_value = {"state":"Complete",
461                                                               "output": "abc+123",
462                                                               "exit_code": 0}
463
464         col().open.return_value = []
465
466         loadingContext, runtimeContext = self.helper(runner)
467
468         arvjob = arvados_cwl.ArvadosContainer(runner,
469                                               runtimeContext,
470                                               mock.MagicMock(),
471                                               {},
472                                               None,
473                                               [],
474                                               [],
475                                               "testjob")
476         arvjob.output_callback = mock.MagicMock()
477         arvjob.collect_outputs = mock.MagicMock()
478         arvjob.successCodes = [0]
479         arvjob.outdir = "/var/spool/cwl"
480         arvjob.output_ttl = 3600
481
482         arvjob.collect_outputs.return_value = {"out": "stuff"}
483
484         arvjob.done({
485             "state": "Final",
486             "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
487             "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
488             "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
489             "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
490             "modified_at": "2017-05-26T12:01:22Z"
491         })
492
493         self.assertFalse(api.collections().create.called)
494         self.assertFalse(runner.runtime_status_error.called)
495
496         arvjob.collect_outputs.assert_called_with("keep:abc+123")
497         arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
498         runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
499
500     @mock.patch("arvados_cwl.util.get_current_container")
501     @mock.patch("arvados.collection.CollectionReader")
502     @mock.patch("arvados.collection.Collection")
503     def test_child_failure(self, col, reader, gcc_mock):
504         api = mock.MagicMock()
505         api._rootDesc = copy.deepcopy(get_rootDesc())
506         del api._rootDesc.get('resources')['jobs']['methods']['create']
507
508         # Set up runner with mocked runtime_status_update()
509         self.assertFalse(gcc_mock.called)
510         runtime_status_update = mock.MagicMock()
511         arvados_cwl.ArvCwlExecutor.runtime_status_update = runtime_status_update
512         runner = arvados_cwl.ArvCwlExecutor(api)
513         self.assertEqual(runner.work_api, 'containers')
514
515         # Make sure ArvCwlExecutor thinks it's running inside a container so it
516         # adds the logging handler that will call runtime_status_update() mock
517         gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
518         self.assertTrue(gcc_mock.called)
519         root_logger = logging.getLogger('')
520         handlerClasses = [h.__class__ for h in root_logger.handlers]
521         self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
522
523         runner.num_retries = 0
524         runner.ignore_docker_for_reuse = False
525         runner.intermediate_output_ttl = 0
526         runner.secret_store = cwltool.secrets.SecretStore()
527         runner.label = mock.MagicMock()
528         runner.label.return_value = '[container testjob]'
529
530         runner.api.containers().get().execute.return_value = {
531             "state":"Complete",
532             "output": "abc+123",
533             "exit_code": 1,
534             "log": "def+234"
535         }
536
537         col().open.return_value = []
538
539         loadingContext, runtimeContext = self.helper(runner)
540
541         arvjob = arvados_cwl.ArvadosContainer(runner,
542                                               runtimeContext,
543                                               mock.MagicMock(),
544                                               {},
545                                               None,
546                                               [],
547                                               [],
548                                               "testjob")
549         arvjob.output_callback = mock.MagicMock()
550         arvjob.collect_outputs = mock.MagicMock()
551         arvjob.successCodes = [0]
552         arvjob.outdir = "/var/spool/cwl"
553         arvjob.output_ttl = 3600
554         arvjob.collect_outputs.return_value = {"out": "stuff"}
555
556         arvjob.done({
557             "state": "Final",
558             "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
559             "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
560             "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
561             "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
562             "modified_at": "2017-05-26T12:01:22Z"
563         })
564
565         runtime_status_update.assert_called_with(
566             'error',
567             'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
568             '  ** log is empty **'
569         )
570         arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
571
572     # The test passes no builder.resources
573     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
574     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
575     def test_mounts(self, keepdocker):
576         arv_docker_clear_cache()
577
578         runner = mock.MagicMock()
579         runner.ignore_docker_for_reuse = False
580         runner.intermediate_output_ttl = 0
581         runner.secret_store = cwltool.secrets.SecretStore()
582
583         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
584         runner.api.collections().get().execute.return_value = {
585             "portable_data_hash": "99999999999999999999999999999994+99",
586             "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
587
588         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
589
590         tool = cmap({
591             "inputs": [
592                 {"id": "p1",
593                  "type": "Directory"}
594             ],
595             "outputs": [],
596             "baseCommand": "ls",
597             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
598             "id": "#",
599             "class": "CommandLineTool"
600         })
601
602         loadingContext, runtimeContext = self.helper(runner)
603         runtimeContext.name = "test_run_mounts"
604
605         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
606         arvtool.formatgraph = None
607         job_order = {
608             "p1": {
609                 "class": "Directory",
610                 "location": "keep:99999999999999999999999999999994+44",
611                 "listing": [
612                     {
613                         "class": "File",
614                         "location": "keep:99999999999999999999999999999994+44/file1",
615                     },
616                     {
617                         "class": "File",
618                         "location": "keep:99999999999999999999999999999994+44/file2",
619                     }
620                 ]
621             }
622         }
623         for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
624             j.run(runtimeContext)
625             runner.api.container_requests().create.assert_called_with(
626                 body=JsonDiffMatcher({
627                     'environment': {
628                         'HOME': '/var/spool/cwl',
629                         'TMPDIR': '/tmp'
630                     },
631                     'name': 'test_run_mounts',
632                     'runtime_constraints': {
633                         'vcpus': 1,
634                         'ram': 1073741824
635                     },
636                     'use_existing': True,
637                     'priority': 500,
638                     'mounts': {
639                         "/keep/99999999999999999999999999999994+44": {
640                             "kind": "collection",
641                             "portable_data_hash": "99999999999999999999999999999994+44"
642                         },
643                         '/tmp': {'kind': 'tmp',
644                                  "capacity": 1073741824 },
645                         '/var/spool/cwl': {'kind': 'tmp',
646                                            "capacity": 1073741824 }
647                     },
648                     'state': 'Committed',
649                     'output_name': 'Output for step test_run_mounts',
650                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
651                     'output_path': '/var/spool/cwl',
652                     'output_ttl': 0,
653                     'container_image': '99999999999999999999999999999994+99',
654                     'command': ['ls', '/var/spool/cwl'],
655                     'cwd': '/var/spool/cwl',
656                     'scheduling_parameters': {},
657                     'properties': {},
658                     'secret_mounts': {}
659                 }))
660
661     # The test passes no builder.resources
662     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
663     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
664     def test_secrets(self, keepdocker):
665         arv_docker_clear_cache()
666
667         runner = mock.MagicMock()
668         runner.ignore_docker_for_reuse = False
669         runner.intermediate_output_ttl = 0
670         runner.secret_store = cwltool.secrets.SecretStore()
671
672         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
673         runner.api.collections().get().execute.return_value = {
674             "portable_data_hash": "99999999999999999999999999999993+99"}
675
676         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
677
678         tool = cmap({"arguments": ["md5sum", "example.conf"],
679                      "class": "CommandLineTool",
680                      "hints": [
681                          {
682                              "class": "http://commonwl.org/cwltool#Secrets",
683                              "secrets": [
684                                  "#secret_job.cwl/pw"
685                              ]
686                          }
687                      ],
688                      "id": "#secret_job.cwl",
689                      "inputs": [
690                          {
691                              "id": "#secret_job.cwl/pw",
692                              "type": "string"
693                          }
694                      ],
695                      "outputs": [
696                      ],
697                      "requirements": [
698                          {
699                              "class": "InitialWorkDirRequirement",
700                              "listing": [
701                                  {
702                                      "entry": "username: user\npassword: $(inputs.pw)\n",
703                                      "entryname": "example.conf"
704                                  }
705                              ]
706                          }
707                      ]})
708
709         loadingContext, runtimeContext = self.helper(runner)
710         runtimeContext.name = "test_secrets"
711
712         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
713         arvtool.formatgraph = None
714
715         job_order = {"pw": "blorp"}
716         runner.secret_store.store(["pw"], job_order)
717
718         for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
719             j.run(runtimeContext)
720             runner.api.container_requests().create.assert_called_with(
721                 body=JsonDiffMatcher({
722                     'environment': {
723                         'HOME': '/var/spool/cwl',
724                         'TMPDIR': '/tmp'
725                     },
726                     'name': 'test_secrets',
727                     'runtime_constraints': {
728                         'vcpus': 1,
729                         'ram': 1073741824
730                     },
731                     'use_existing': True,
732                     'priority': 500,
733                     'mounts': {
734                         '/tmp': {'kind': 'tmp',
735                                  "capacity": 1073741824
736                              },
737                         '/var/spool/cwl': {'kind': 'tmp',
738                                            "capacity": 1073741824 }
739                     },
740                     'state': 'Committed',
741                     'output_name': 'Output for step test_secrets',
742                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
743                     'output_path': '/var/spool/cwl',
744                     'output_ttl': 0,
745                     'container_image': '99999999999999999999999999999993+99',
746                     'command': ['md5sum', 'example.conf'],
747                     'cwd': '/var/spool/cwl',
748                     'scheduling_parameters': {},
749                     'properties': {},
750                     "secret_mounts": {
751                         "/var/spool/cwl/example.conf": {
752                             "content": "username: user\npassword: blorp\n",
753                             "kind": "text"
754                         }
755                     }
756                 }))
757
758     # The test passes no builder.resources
759     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
760     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
761     def test_timelimit(self, keepdocker):
762         arv_docker_clear_cache()
763
764         runner = mock.MagicMock()
765         runner.ignore_docker_for_reuse = False
766         runner.intermediate_output_ttl = 0
767         runner.secret_store = cwltool.secrets.SecretStore()
768
769         keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
770         runner.api.collections().get().execute.return_value = {
771             "portable_data_hash": "99999999999999999999999999999993+99"}
772
773         tool = cmap({
774             "inputs": [],
775             "outputs": [],
776             "baseCommand": "ls",
777             "arguments": [{"valueFrom": "$(runtime.outdir)"}],
778             "id": "#",
779             "class": "CommandLineTool",
780             "hints": [
781                 {
782                     "class": "http://commonwl.org/cwltool#TimeLimit",
783                     "timelimit": 42
784                 }
785             ]
786         })
787
788         loadingContext, runtimeContext = self.helper(runner)
789         runtimeContext.name = "test_timelimit"
790
791         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
792         arvtool.formatgraph = None
793
794         for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
795             j.run(runtimeContext)
796
797         _, kwargs = runner.api.container_requests().create.call_args
798         self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))