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