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 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
50 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
51 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
52 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
53 arvtool.formatgraph = None
54 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_"+str(enable_reuse),
55 make_fs_access=make_fs_access, tmpdir="/tmp"):
56 j.run(enable_reuse=enable_reuse)
57 runner.api.container_requests().create.assert_called_with(
58 body=JsonDiffMatcher({
60 'HOME': '/var/spool/cwl',
63 'name': 'test_run_'+str(enable_reuse),
64 'runtime_constraints': {
68 'use_existing': enable_reuse,
71 '/tmp': {'kind': 'tmp',
72 "capacity": 1073741824
74 '/var/spool/cwl': {'kind': 'tmp',
75 "capacity": 1073741824 }
78 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
79 'output_path': '/var/spool/cwl',
81 'container_image': 'arvados/jobs',
82 'command': ['ls', '/var/spool/cwl'],
83 'cwd': '/var/spool/cwl',
84 'scheduling_parameters': {},
88 # The test passes some fields in builder.resources
89 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
90 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
91 def test_resource_requirements(self, keepdocker):
92 arv_docker_clear_cache()
93 runner = mock.MagicMock()
94 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
95 runner.ignore_docker_for_reuse = False
96 runner.intermediate_output_ttl = 3600
97 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
99 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
100 runner.api.collections().get().execute.return_value = {
101 "portable_data_hash": "99999999999999999999999999999993+99"}
107 "class": "ResourceRequirement",
113 "class": "http://arvados.org/cwl#RuntimeConstraints",
116 "class": "http://arvados.org/cwl#APIRequirement",
118 "class": "http://arvados.org/cwl#PartitionRequirement",
121 "class": "http://arvados.org/cwl#IntermediateOutput",
124 "class": "http://arvados.org/cwl#ReuseRequirement",
129 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
130 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
131 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
132 avsc_names=avsc_names, make_fs_access=make_fs_access,
134 arvtool.formatgraph = None
135 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements",
136 make_fs_access=make_fs_access, tmpdir="/tmp"):
137 j.run(enable_reuse=True)
139 call_args, call_kwargs = runner.api.container_requests().create.call_args
141 call_body_expected = {
143 'HOME': '/var/spool/cwl',
146 'name': 'test_resource_requirements',
147 'runtime_constraints': {
150 'keep_cache_ram': 536870912,
153 'use_existing': False,
156 '/tmp': {'kind': 'tmp',
157 "capacity": 4194304000 },
158 '/var/spool/cwl': {'kind': 'tmp',
159 "capacity": 5242880000 }
161 'state': 'Committed',
162 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
163 'output_path': '/var/spool/cwl',
165 'container_image': 'arvados/jobs',
167 'cwd': '/var/spool/cwl',
168 'scheduling_parameters': {
169 'partitions': ['blurb']
174 call_body = call_kwargs.get('body', None)
175 self.assertNotEqual(None, call_body)
176 for key in call_body:
177 self.assertEqual(call_body_expected.get(key), call_body.get(key))
180 # The test passes some fields in builder.resources
181 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
182 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
183 @mock.patch("arvados.collection.Collection")
184 def test_initial_work_dir(self, collection_mock, keepdocker):
185 arv_docker_clear_cache()
186 runner = mock.MagicMock()
187 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
188 runner.ignore_docker_for_reuse = False
189 runner.intermediate_output_ttl = 0
190 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
192 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
193 runner.api.collections().get().execute.return_value = {
194 "portable_data_hash": "99999999999999999999999999999993+99"}
196 sourcemock = mock.MagicMock()
197 def get_collection_mock(p):
199 return (sourcemock, p.split("/", 1)[1])
201 return (sourcemock, "")
202 runner.fs_access.get_collection.side_effect = get_collection_mock
204 vwdmock = mock.MagicMock()
205 collection_mock.return_value = vwdmock
206 vwdmock.portable_data_hash.return_value = "99999999999999999999999999999996+99"
212 "class": "InitialWorkDirRequirement",
216 "location": "keep:99999999999999999999999999999995+99/bar"
219 "class": "Directory",
221 "location": "keep:99999999999999999999999999999995+99"
225 "basename": "filename",
226 "location": "keep:99999999999999999999999999999995+99/baz/filename"
229 "class": "Directory",
230 "basename": "subdir",
231 "location": "keep:99999999999999999999999999999995+99/subdir"
236 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
237 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
238 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
239 avsc_names=avsc_names, make_fs_access=make_fs_access,
241 arvtool.formatgraph = None
242 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_initial_work_dir",
243 make_fs_access=make_fs_access, tmpdir="/tmp"):
246 call_args, call_kwargs = runner.api.container_requests().create.call_args
248 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
249 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
250 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
251 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
253 call_body_expected = {
255 'HOME': '/var/spool/cwl',
258 'name': 'test_initial_work_dir',
259 'runtime_constraints': {
263 'use_existing': True,
266 '/tmp': {'kind': 'tmp',
267 "capacity": 1073741824 },
268 '/var/spool/cwl': {'kind': 'tmp',
269 "capacity": 1073741824 },
270 '/var/spool/cwl/foo': {
271 'kind': 'collection',
273 'portable_data_hash': '99999999999999999999999999999996+99'
275 '/var/spool/cwl/foo2': {
276 'kind': 'collection',
278 'portable_data_hash': '99999999999999999999999999999996+99'
280 '/var/spool/cwl/filename': {
281 'kind': 'collection',
283 'portable_data_hash': '99999999999999999999999999999996+99'
285 '/var/spool/cwl/subdir': {
286 'kind': 'collection',
288 'portable_data_hash': '99999999999999999999999999999996+99'
291 'state': 'Committed',
292 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
293 'output_path': '/var/spool/cwl',
295 'container_image': 'arvados/jobs',
297 'cwd': '/var/spool/cwl',
298 'scheduling_parameters': {
303 call_body = call_kwargs.get('body', None)
304 self.assertNotEqual(None, call_body)
305 for key in call_body:
306 self.assertEqual(call_body_expected.get(key), call_body.get(key))
309 # Test redirecting stdin/stdout/stderr
310 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
311 def test_redirects(self, keepdocker):
312 arv_docker_clear_cache()
314 runner = mock.MagicMock()
315 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
316 runner.ignore_docker_for_reuse = False
317 runner.intermediate_output_ttl = 0
319 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
320 runner.api.collections().get().execute.return_value = {
321 "portable_data_hash": "99999999999999999999999999999993+99"}
323 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
329 "stdout": "stdout.txt",
330 "stderr": "stderr.txt",
331 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
332 "arguments": [{"valueFrom": "$(runtime.outdir)"}]
334 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
335 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
336 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
337 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
338 arvtool.formatgraph = None
339 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_redirect",
340 make_fs_access=make_fs_access, tmpdir="/tmp"):
342 runner.api.container_requests().create.assert_called_with(
343 body=JsonDiffMatcher({
345 'HOME': '/var/spool/cwl',
348 'name': 'test_run_redirect',
349 'runtime_constraints': {
353 'use_existing': True,
356 '/tmp': {'kind': 'tmp',
357 "capacity": 1073741824 },
358 '/var/spool/cwl': {'kind': 'tmp',
359 "capacity": 1073741824 },
362 "path": "/var/spool/cwl/stderr.txt"
365 "kind": "collection",
367 "portable_data_hash": "99999999999999999999999999999996+99"
371 "path": "/var/spool/cwl/stdout.txt"
374 'state': 'Committed',
375 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
376 'output_path': '/var/spool/cwl',
378 'container_image': 'arvados/jobs',
379 'command': ['ls', '/var/spool/cwl'],
380 'cwd': '/var/spool/cwl',
381 'scheduling_parameters': {},
385 @mock.patch("arvados.collection.Collection")
386 def test_done(self, col):
387 api = mock.MagicMock()
389 runner = mock.MagicMock()
391 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
392 runner.num_retries = 0
393 runner.ignore_docker_for_reuse = False
394 runner.intermediate_output_ttl = 0
396 runner.api.containers().get().execute.return_value = {"state":"Complete",
400 col().open.return_value = []
402 arvjob = arvados_cwl.ArvadosContainer(runner)
403 arvjob.name = "testjob"
404 arvjob.builder = mock.MagicMock()
405 arvjob.output_callback = mock.MagicMock()
406 arvjob.collect_outputs = mock.MagicMock()
407 arvjob.successCodes = [0]
408 arvjob.outdir = "/var/spool/cwl"
409 arvjob.output_ttl = 3600
411 arvjob.collect_outputs.return_value = {"out": "stuff"}
415 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
416 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
417 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
418 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
419 "modified_at": "2017-05-26T12:01:22Z"
422 self.assertFalse(api.collections().create.called)
424 arvjob.collect_outputs.assert_called_with("keep:abc+123")
425 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
426 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
428 # The test passes no builder.resources
429 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
430 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
431 def test_mounts(self, keepdocker):
432 arv_docker_clear_cache()
434 runner = mock.MagicMock()
435 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
436 runner.ignore_docker_for_reuse = False
437 runner.intermediate_output_ttl = 0
439 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
440 runner.api.collections().get().execute.return_value = {
441 "portable_data_hash": "99999999999999999999999999999993+99"}
443 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
452 "arguments": [{"valueFrom": "$(runtime.outdir)"}]
454 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
455 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
456 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
457 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
458 arvtool.formatgraph = None
461 "class": "Directory",
462 "location": "keep:99999999999999999999999999999994+44",
466 "location": "keep:99999999999999999999999999999994+44/file1",
470 "location": "keep:99999999999999999999999999999994+44/file2",
475 for j in arvtool.job(job_order, mock.MagicMock(), basedir="", name="test_run_mounts",
476 make_fs_access=make_fs_access, tmpdir="/tmp"):
478 runner.api.container_requests().create.assert_called_with(
479 body=JsonDiffMatcher({
481 'HOME': '/var/spool/cwl',
484 'name': 'test_run_mounts',
485 'runtime_constraints': {
489 'use_existing': True,
492 "/keep/99999999999999999999999999999994+44": {
493 "kind": "collection",
494 "portable_data_hash": "99999999999999999999999999999994+44"
496 '/tmp': {'kind': 'tmp',
497 "capacity": 1073741824 },
498 '/var/spool/cwl': {'kind': 'tmp',
499 "capacity": 1073741824 }
501 'state': 'Committed',
502 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
503 'output_path': '/var/spool/cwl',
505 'container_image': 'arvados/jobs',
506 'command': ['ls', '/var/spool/cwl'],
507 'cwd': '/var/spool/cwl',
508 'scheduling_parameters': {},