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