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 from schema_salad.ref_resolver import Loader
14 from schema_salad.sourceline import cmap
16 from .matcher import JsonDiffMatcher
18 if not os.getenv('ARVADOS_DEBUG'):
19 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
20 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
23 class TestContainer(unittest.TestCase):
25 # The test passes no builder.resources
26 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
27 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
28 def test_run(self, keepdocker):
29 for enable_reuse in (True, False):
30 arv_docker_clear_cache()
32 runner = mock.MagicMock()
33 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
34 runner.ignore_docker_for_reuse = False
35 runner.intermediate_output_ttl = 0
37 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
38 runner.api.collections().get().execute.return_value = {
39 "portable_data_hash": "99999999999999999999999999999993+99"}
41 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
47 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
49 "class": "CommandLineTool"
51 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
52 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
53 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
54 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
55 arvtool.formatgraph = None
56 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_"+str(enable_reuse),
57 make_fs_access=make_fs_access, tmpdir="/tmp"):
58 j.run(enable_reuse=enable_reuse)
59 runner.api.container_requests().create.assert_called_with(
60 body=JsonDiffMatcher({
62 'HOME': '/var/spool/cwl',
65 'name': 'test_run_'+str(enable_reuse),
66 'runtime_constraints': {
70 'use_existing': enable_reuse,
73 '/tmp': {'kind': 'tmp',
74 "capacity": 1073741824
76 '/var/spool/cwl': {'kind': 'tmp',
77 "capacity": 1073741824 }
80 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
81 'output_path': '/var/spool/cwl',
83 'container_image': 'arvados/jobs',
84 'command': ['ls', '/var/spool/cwl'],
85 'cwd': '/var/spool/cwl',
86 'scheduling_parameters': {},
90 # The test passes some fields in builder.resources
91 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
92 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
93 def test_resource_requirements(self, keepdocker):
94 arv_docker_clear_cache()
95 runner = mock.MagicMock()
96 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
97 runner.ignore_docker_for_reuse = False
98 runner.intermediate_output_ttl = 3600
99 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
101 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
102 runner.api.collections().get().execute.return_value = {
103 "portable_data_hash": "99999999999999999999999999999993+99"}
109 "class": "ResourceRequirement",
115 "class": "http://arvados.org/cwl#RuntimeConstraints",
118 "class": "http://arvados.org/cwl#APIRequirement",
120 "class": "http://arvados.org/cwl#PartitionRequirement",
123 "class": "http://arvados.org/cwl#IntermediateOutput",
126 "class": "http://arvados.org/cwl#ReuseRequirement",
131 "class": "CommandLineTool"
133 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
134 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
135 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
136 avsc_names=avsc_names, make_fs_access=make_fs_access,
138 arvtool.formatgraph = None
139 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements",
140 make_fs_access=make_fs_access, tmpdir="/tmp"):
141 j.run(enable_reuse=True)
143 call_args, call_kwargs = runner.api.container_requests().create.call_args
145 call_body_expected = {
147 'HOME': '/var/spool/cwl',
150 'name': 'test_resource_requirements',
151 'runtime_constraints': {
154 'keep_cache_ram': 536870912,
157 'use_existing': False,
160 '/tmp': {'kind': 'tmp',
161 "capacity": 4194304000 },
162 '/var/spool/cwl': {'kind': 'tmp',
163 "capacity": 5242880000 }
165 'state': 'Committed',
166 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
167 'output_path': '/var/spool/cwl',
169 'container_image': 'arvados/jobs',
171 'cwd': '/var/spool/cwl',
172 'scheduling_parameters': {
173 'partitions': ['blurb']
178 call_body = call_kwargs.get('body', None)
179 self.assertNotEqual(None, call_body)
180 for key in call_body:
181 self.assertEqual(call_body_expected.get(key), call_body.get(key))
184 # The test passes some fields in builder.resources
185 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
186 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
187 @mock.patch("arvados.collection.Collection")
188 def test_initial_work_dir(self, collection_mock, keepdocker):
189 arv_docker_clear_cache()
190 runner = mock.MagicMock()
191 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
192 runner.ignore_docker_for_reuse = False
193 runner.intermediate_output_ttl = 0
194 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
196 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
197 runner.api.collections().get().execute.return_value = {
198 "portable_data_hash": "99999999999999999999999999999993+99"}
200 sourcemock = mock.MagicMock()
201 def get_collection_mock(p):
203 return (sourcemock, p.split("/", 1)[1])
205 return (sourcemock, "")
206 runner.fs_access.get_collection.side_effect = get_collection_mock
208 vwdmock = mock.MagicMock()
209 collection_mock.return_value = vwdmock
210 vwdmock.portable_data_hash.return_value = "99999999999999999999999999999996+99"
216 "class": "InitialWorkDirRequirement",
220 "location": "keep:99999999999999999999999999999995+99/bar"
223 "class": "Directory",
225 "location": "keep:99999999999999999999999999999995+99"
229 "basename": "filename",
230 "location": "keep:99999999999999999999999999999995+99/baz/filename"
233 "class": "Directory",
234 "basename": "subdir",
235 "location": "keep:99999999999999999999999999999995+99/subdir"
240 "class": "CommandLineTool"
242 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
243 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
244 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
245 avsc_names=avsc_names, make_fs_access=make_fs_access,
247 arvtool.formatgraph = None
248 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_initial_work_dir",
249 make_fs_access=make_fs_access, tmpdir="/tmp"):
252 call_args, call_kwargs = runner.api.container_requests().create.call_args
254 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
255 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
256 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
257 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
259 call_body_expected = {
261 'HOME': '/var/spool/cwl',
264 'name': 'test_initial_work_dir',
265 'runtime_constraints': {
269 'use_existing': True,
272 '/tmp': {'kind': 'tmp',
273 "capacity": 1073741824 },
274 '/var/spool/cwl': {'kind': 'tmp',
275 "capacity": 1073741824 },
276 '/var/spool/cwl/foo': {
277 'kind': 'collection',
279 'portable_data_hash': '99999999999999999999999999999996+99'
281 '/var/spool/cwl/foo2': {
282 'kind': 'collection',
284 'portable_data_hash': '99999999999999999999999999999996+99'
286 '/var/spool/cwl/filename': {
287 'kind': 'collection',
289 'portable_data_hash': '99999999999999999999999999999996+99'
291 '/var/spool/cwl/subdir': {
292 'kind': 'collection',
294 'portable_data_hash': '99999999999999999999999999999996+99'
297 'state': 'Committed',
298 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
299 'output_path': '/var/spool/cwl',
301 'container_image': 'arvados/jobs',
303 'cwd': '/var/spool/cwl',
304 'scheduling_parameters': {
309 call_body = call_kwargs.get('body', None)
310 self.assertNotEqual(None, call_body)
311 for key in call_body:
312 self.assertEqual(call_body_expected.get(key), call_body.get(key))
315 # Test redirecting stdin/stdout/stderr
316 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
317 def test_redirects(self, keepdocker):
318 arv_docker_clear_cache()
320 runner = mock.MagicMock()
321 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
322 runner.ignore_docker_for_reuse = False
323 runner.intermediate_output_ttl = 0
325 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
326 runner.api.collections().get().execute.return_value = {
327 "portable_data_hash": "99999999999999999999999999999993+99"}
329 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
335 "stdout": "stdout.txt",
336 "stderr": "stderr.txt",
337 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
338 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
340 "class": "CommandLineTool"
342 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
343 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
344 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
345 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
346 arvtool.formatgraph = None
347 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_redirect",
348 make_fs_access=make_fs_access, tmpdir="/tmp"):
350 runner.api.container_requests().create.assert_called_with(
351 body=JsonDiffMatcher({
353 'HOME': '/var/spool/cwl',
356 'name': 'test_run_redirect',
357 'runtime_constraints': {
361 'use_existing': True,
364 '/tmp': {'kind': 'tmp',
365 "capacity": 1073741824 },
366 '/var/spool/cwl': {'kind': 'tmp',
367 "capacity": 1073741824 },
370 "path": "/var/spool/cwl/stderr.txt"
373 "kind": "collection",
375 "portable_data_hash": "99999999999999999999999999999996+99"
379 "path": "/var/spool/cwl/stdout.txt"
382 'state': 'Committed',
383 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
384 'output_path': '/var/spool/cwl',
386 'container_image': 'arvados/jobs',
387 'command': ['ls', '/var/spool/cwl'],
388 'cwd': '/var/spool/cwl',
389 'scheduling_parameters': {},
393 @mock.patch("arvados.collection.Collection")
394 def test_done(self, col):
395 api = mock.MagicMock()
397 runner = mock.MagicMock()
399 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
400 runner.num_retries = 0
401 runner.ignore_docker_for_reuse = False
402 runner.intermediate_output_ttl = 0
404 runner.api.containers().get().execute.return_value = {"state":"Complete",
408 col().open.return_value = []
410 arvjob = arvados_cwl.ArvadosContainer(runner)
411 arvjob.name = "testjob"
412 arvjob.builder = mock.MagicMock()
413 arvjob.output_callback = mock.MagicMock()
414 arvjob.collect_outputs = mock.MagicMock()
415 arvjob.successCodes = [0]
416 arvjob.outdir = "/var/spool/cwl"
417 arvjob.output_ttl = 3600
419 arvjob.collect_outputs.return_value = {"out": "stuff"}
423 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
424 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
425 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
426 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
427 "modified_at": "2017-05-26T12:01:22Z"
430 self.assertFalse(api.collections().create.called)
432 arvjob.collect_outputs.assert_called_with("keep:abc+123")
433 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
434 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
436 # The test passes no builder.resources
437 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
438 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
439 def test_mounts(self, keepdocker):
440 arv_docker_clear_cache()
442 runner = mock.MagicMock()
443 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
444 runner.ignore_docker_for_reuse = False
445 runner.intermediate_output_ttl = 0
447 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
448 runner.api.collections().get().execute.return_value = {
449 "portable_data_hash": "99999999999999999999999999999993+99"}
451 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
460 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
462 "class": "CommandLineTool"
464 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
465 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
466 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
467 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
468 arvtool.formatgraph = None
471 "class": "Directory",
472 "location": "keep:99999999999999999999999999999994+44",
476 "location": "keep:99999999999999999999999999999994+44/file1",
480 "location": "keep:99999999999999999999999999999994+44/file2",
485 for j in arvtool.job(job_order, mock.MagicMock(), basedir="", name="test_run_mounts",
486 make_fs_access=make_fs_access, tmpdir="/tmp"):
488 runner.api.container_requests().create.assert_called_with(
489 body=JsonDiffMatcher({
491 'HOME': '/var/spool/cwl',
494 'name': 'test_run_mounts',
495 'runtime_constraints': {
499 'use_existing': True,
502 "/keep/99999999999999999999999999999994+44": {
503 "kind": "collection",
504 "portable_data_hash": "99999999999999999999999999999994+44"
506 '/tmp': {'kind': 'tmp',
507 "capacity": 1073741824 },
508 '/var/spool/cwl': {'kind': 'tmp',
509 "capacity": 1073741824 }
511 'state': 'Committed',
512 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
513 'output_path': '/var/spool/cwl',
515 'container_image': 'arvados/jobs',
516 'command': ['ls', '/var/spool/cwl'],
517 'cwd': '/var/spool/cwl',
518 'scheduling_parameters': {},