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