1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
6 from arvados_cwl.arvdocker import arv_docker_clear_cache
12 import cwltool.process
13 import cwltool.secrets
14 from schema_salad.ref_resolver import Loader
15 from schema_salad.sourceline import cmap
17 from .matcher import JsonDiffMatcher
19 if not os.getenv('ARVADOS_DEBUG'):
20 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
21 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
24 class TestContainer(unittest.TestCase):
26 # The test passes no builder.resources
27 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
28 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
29 def test_run(self, keepdocker):
30 for enable_reuse in (True, False):
31 arv_docker_clear_cache()
33 runner = mock.MagicMock()
34 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
35 runner.ignore_docker_for_reuse = False
36 runner.intermediate_output_ttl = 0
37 runner.secret_store = cwltool.secrets.SecretStore()
39 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
40 runner.api.collections().get().execute.return_value = {
41 "portable_data_hash": "99999999999999999999999999999993+99"}
43 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
49 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
51 "class": "CommandLineTool"
53 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
54 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
55 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
56 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
57 arvtool.formatgraph = None
58 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_"+str(enable_reuse),
59 make_fs_access=make_fs_access, tmpdir="/tmp"):
60 j.run(enable_reuse=enable_reuse, priority=500)
61 runner.api.container_requests().create.assert_called_with(
62 body=JsonDiffMatcher({
64 'HOME': '/var/spool/cwl',
67 'name': 'test_run_'+str(enable_reuse),
68 'runtime_constraints': {
72 'use_existing': enable_reuse,
75 '/tmp': {'kind': 'tmp',
76 "capacity": 1073741824
78 '/var/spool/cwl': {'kind': 'tmp',
79 "capacity": 1073741824 }
82 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
83 'output_path': '/var/spool/cwl',
85 'container_image': 'arvados/jobs',
86 'command': ['ls', '/var/spool/cwl'],
87 'cwd': '/var/spool/cwl',
88 'scheduling_parameters': {},
93 # The test passes some fields in builder.resources
94 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
95 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
96 def test_resource_requirements(self, keepdocker):
97 arv_docker_clear_cache()
98 runner = mock.MagicMock()
99 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
100 runner.ignore_docker_for_reuse = False
101 runner.intermediate_output_ttl = 3600
102 runner.secret_store = cwltool.secrets.SecretStore()
104 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
106 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
107 runner.api.collections().get().execute.return_value = {
108 "portable_data_hash": "99999999999999999999999999999993+99"}
114 "class": "ResourceRequirement",
120 "class": "http://arvados.org/cwl#RuntimeConstraints",
123 "class": "http://arvados.org/cwl#APIRequirement",
125 "class": "http://arvados.org/cwl#PartitionRequirement",
128 "class": "http://arvados.org/cwl#IntermediateOutput",
131 "class": "http://arvados.org/cwl#ReuseRequirement",
136 "class": "CommandLineTool"
138 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
139 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
140 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
141 avsc_names=avsc_names, make_fs_access=make_fs_access,
143 arvtool.formatgraph = None
144 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements",
145 make_fs_access=make_fs_access, tmpdir="/tmp"):
146 j.run(enable_reuse=True, priority=500)
148 call_args, call_kwargs = runner.api.container_requests().create.call_args
150 call_body_expected = {
152 'HOME': '/var/spool/cwl',
155 'name': 'test_resource_requirements',
156 'runtime_constraints': {
159 'keep_cache_ram': 536870912,
162 'use_existing': False,
165 '/tmp': {'kind': 'tmp',
166 "capacity": 4194304000 },
167 '/var/spool/cwl': {'kind': 'tmp',
168 "capacity": 5242880000 }
170 'state': 'Committed',
171 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
172 'output_path': '/var/spool/cwl',
174 'container_image': 'arvados/jobs',
176 'cwd': '/var/spool/cwl',
177 'scheduling_parameters': {
178 'partitions': ['blurb']
184 call_body = call_kwargs.get('body', None)
185 self.assertNotEqual(None, call_body)
186 for key in call_body:
187 self.assertEqual(call_body_expected.get(key), call_body.get(key))
190 # The test passes some fields in builder.resources
191 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
192 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
193 @mock.patch("arvados.collection.Collection")
194 def test_initial_work_dir(self, collection_mock, keepdocker):
195 arv_docker_clear_cache()
196 runner = mock.MagicMock()
197 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
198 runner.ignore_docker_for_reuse = False
199 runner.intermediate_output_ttl = 0
200 runner.secret_store = cwltool.secrets.SecretStore()
202 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
204 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
205 runner.api.collections().get().execute.return_value = {
206 "portable_data_hash": "99999999999999999999999999999993+99"}
208 sourcemock = mock.MagicMock()
209 def get_collection_mock(p):
211 return (sourcemock, p.split("/", 1)[1])
213 return (sourcemock, "")
214 runner.fs_access.get_collection.side_effect = get_collection_mock
216 vwdmock = mock.MagicMock()
217 collection_mock.return_value = vwdmock
218 vwdmock.portable_data_hash.return_value = "99999999999999999999999999999996+99"
224 "class": "InitialWorkDirRequirement",
228 "location": "keep:99999999999999999999999999999995+99/bar"
231 "class": "Directory",
233 "location": "keep:99999999999999999999999999999995+99"
237 "basename": "filename",
238 "location": "keep:99999999999999999999999999999995+99/baz/filename"
241 "class": "Directory",
242 "basename": "subdir",
243 "location": "keep:99999999999999999999999999999995+99/subdir"
248 "class": "CommandLineTool"
250 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
251 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
252 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
253 avsc_names=avsc_names, make_fs_access=make_fs_access,
255 arvtool.formatgraph = None
256 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_initial_work_dir",
257 make_fs_access=make_fs_access, tmpdir="/tmp"):
260 call_args, call_kwargs = runner.api.container_requests().create.call_args
262 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
263 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
264 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
265 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
267 call_body_expected = {
269 'HOME': '/var/spool/cwl',
272 'name': 'test_initial_work_dir',
273 'runtime_constraints': {
277 'use_existing': True,
280 '/tmp': {'kind': 'tmp',
281 "capacity": 1073741824 },
282 '/var/spool/cwl': {'kind': 'tmp',
283 "capacity": 1073741824 },
284 '/var/spool/cwl/foo': {
285 'kind': 'collection',
287 'portable_data_hash': '99999999999999999999999999999996+99'
289 '/var/spool/cwl/foo2': {
290 'kind': 'collection',
292 'portable_data_hash': '99999999999999999999999999999996+99'
294 '/var/spool/cwl/filename': {
295 'kind': 'collection',
297 'portable_data_hash': '99999999999999999999999999999996+99'
299 '/var/spool/cwl/subdir': {
300 'kind': 'collection',
302 'portable_data_hash': '99999999999999999999999999999996+99'
305 'state': 'Committed',
306 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
307 'output_path': '/var/spool/cwl',
309 'container_image': 'arvados/jobs',
311 'cwd': '/var/spool/cwl',
312 'scheduling_parameters': {
318 call_body = call_kwargs.get('body', None)
319 self.assertNotEqual(None, call_body)
320 for key in call_body:
321 self.assertEqual(call_body_expected.get(key), call_body.get(key))
324 # Test redirecting stdin/stdout/stderr
325 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
326 def test_redirects(self, keepdocker):
327 arv_docker_clear_cache()
329 runner = mock.MagicMock()
330 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
331 runner.ignore_docker_for_reuse = False
332 runner.intermediate_output_ttl = 0
333 runner.secret_store = cwltool.secrets.SecretStore()
335 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
336 runner.api.collections().get().execute.return_value = {
337 "portable_data_hash": "99999999999999999999999999999993+99"}
339 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
345 "stdout": "stdout.txt",
346 "stderr": "stderr.txt",
347 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
348 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
350 "class": "CommandLineTool"
352 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
353 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
354 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
355 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
356 arvtool.formatgraph = None
357 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_redirect",
358 make_fs_access=make_fs_access, tmpdir="/tmp"):
360 runner.api.container_requests().create.assert_called_with(
361 body=JsonDiffMatcher({
363 'HOME': '/var/spool/cwl',
366 'name': 'test_run_redirect',
367 'runtime_constraints': {
371 'use_existing': True,
374 '/tmp': {'kind': 'tmp',
375 "capacity": 1073741824 },
376 '/var/spool/cwl': {'kind': 'tmp',
377 "capacity": 1073741824 },
380 "path": "/var/spool/cwl/stderr.txt"
383 "kind": "collection",
385 "portable_data_hash": "99999999999999999999999999999996+99"
389 "path": "/var/spool/cwl/stdout.txt"
392 'state': 'Committed',
393 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
394 'output_path': '/var/spool/cwl',
396 'container_image': 'arvados/jobs',
397 'command': ['ls', '/var/spool/cwl'],
398 'cwd': '/var/spool/cwl',
399 'scheduling_parameters': {},
404 @mock.patch("arvados.collection.Collection")
405 def test_done(self, col):
406 api = mock.MagicMock()
408 runner = mock.MagicMock()
410 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
411 runner.num_retries = 0
412 runner.ignore_docker_for_reuse = False
413 runner.intermediate_output_ttl = 0
414 runner.secret_store = cwltool.secrets.SecretStore()
416 runner.api.containers().get().execute.return_value = {"state":"Complete",
420 col().open.return_value = []
422 arvjob = arvados_cwl.ArvadosContainer(runner)
423 arvjob.name = "testjob"
424 arvjob.builder = mock.MagicMock()
425 arvjob.output_callback = mock.MagicMock()
426 arvjob.collect_outputs = mock.MagicMock()
427 arvjob.successCodes = [0]
428 arvjob.outdir = "/var/spool/cwl"
429 arvjob.output_ttl = 3600
431 arvjob.collect_outputs.return_value = {"out": "stuff"}
435 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
436 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
437 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
438 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
439 "modified_at": "2017-05-26T12:01:22Z"
442 self.assertFalse(api.collections().create.called)
444 arvjob.collect_outputs.assert_called_with("keep:abc+123")
445 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
446 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
448 # The test passes no builder.resources
449 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
450 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
451 def test_mounts(self, keepdocker):
452 arv_docker_clear_cache()
454 runner = mock.MagicMock()
455 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
456 runner.ignore_docker_for_reuse = False
457 runner.intermediate_output_ttl = 0
458 runner.secret_store = cwltool.secrets.SecretStore()
460 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
461 runner.api.collections().get().execute.return_value = {
462 "portable_data_hash": "99999999999999999999999999999993+99"}
464 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
473 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
475 "class": "CommandLineTool"
477 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
478 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
479 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
480 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
481 arvtool.formatgraph = None
484 "class": "Directory",
485 "location": "keep:99999999999999999999999999999994+44",
489 "location": "keep:99999999999999999999999999999994+44/file1",
493 "location": "keep:99999999999999999999999999999994+44/file2",
498 for j in arvtool.job(job_order, mock.MagicMock(), basedir="", name="test_run_mounts",
499 make_fs_access=make_fs_access, tmpdir="/tmp"):
501 runner.api.container_requests().create.assert_called_with(
502 body=JsonDiffMatcher({
504 'HOME': '/var/spool/cwl',
507 'name': 'test_run_mounts',
508 'runtime_constraints': {
512 'use_existing': True,
515 "/keep/99999999999999999999999999999994+44": {
516 "kind": "collection",
517 "portable_data_hash": "99999999999999999999999999999994+44"
519 '/tmp': {'kind': 'tmp',
520 "capacity": 1073741824 },
521 '/var/spool/cwl': {'kind': 'tmp',
522 "capacity": 1073741824 }
524 'state': 'Committed',
525 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
526 'output_path': '/var/spool/cwl',
528 'container_image': 'arvados/jobs',
529 'command': ['ls', '/var/spool/cwl'],
530 'cwd': '/var/spool/cwl',
531 'scheduling_parameters': {},
536 # The test passes no builder.resources
537 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
538 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
539 def test_secrets(self, keepdocker):
540 arv_docker_clear_cache()
542 runner = mock.MagicMock()
543 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
544 runner.ignore_docker_for_reuse = False
545 runner.intermediate_output_ttl = 0
546 runner.secret_store = cwltool.secrets.SecretStore()
548 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
549 runner.api.collections().get().execute.return_value = {
550 "portable_data_hash": "99999999999999999999999999999993+99"}
552 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
554 tool = cmap({"arguments": ["md5sum", "example.conf"],
555 "class": "CommandLineTool",
558 "class": "http://commonwl.org/cwltool#Secrets",
564 "id": "#secret_job.cwl",
567 "id": "#secret_job.cwl/pw",
575 "class": "InitialWorkDirRequirement",
578 "entry": "username: user\npassword: $(inputs.pw)\n",
579 "entryname": "example.conf"
584 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
585 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
586 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
587 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
588 arvtool.formatgraph = None
590 job_order = {"pw": "blorp"}
591 runner.secret_store.store(["pw"], job_order)
593 for j in arvtool.job(job_order, mock.MagicMock(), basedir="", name="test_secrets",
594 make_fs_access=make_fs_access, tmpdir="/tmp"):
595 j.run(enable_reuse=True, priority=500)
596 runner.api.container_requests().create.assert_called_with(
597 body=JsonDiffMatcher({
599 'HOME': '/var/spool/cwl',
602 'name': 'test_secrets',
603 'runtime_constraints': {
607 'use_existing': True,
610 '/tmp': {'kind': 'tmp',
611 "capacity": 1073741824
613 '/var/spool/cwl': {'kind': 'tmp',
614 "capacity": 1073741824 }
616 'state': 'Committed',
617 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
618 'output_path': '/var/spool/cwl',
620 'container_image': 'arvados/jobs',
621 'command': ['md5sum', 'example.conf'],
622 'cwd': '/var/spool/cwl',
623 'scheduling_parameters': {},
626 "/var/spool/cwl/example.conf": {
627 "content": "username: user\npassword: blorp\n",