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)"}],
50 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
51 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
52 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
53 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
54 arvtool.formatgraph = None
55 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_"+str(enable_reuse),
56 make_fs_access=make_fs_access, tmpdir="/tmp"):
57 j.run(enable_reuse=enable_reuse)
58 runner.api.container_requests().create.assert_called_with(
59 body=JsonDiffMatcher({
61 'HOME': '/var/spool/cwl',
64 'name': 'test_run_'+str(enable_reuse),
65 'runtime_constraints': {
69 'use_existing': enable_reuse,
72 '/tmp': {'kind': 'tmp',
73 "capacity": 1073741824
75 '/var/spool/cwl': {'kind': 'tmp',
76 "capacity": 1073741824 }
79 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
80 'output_path': '/var/spool/cwl',
82 'container_image': 'arvados/jobs',
83 'command': ['ls', '/var/spool/cwl'],
84 'cwd': '/var/spool/cwl',
85 'scheduling_parameters': {},
89 # The test passes some fields in builder.resources
90 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
91 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
92 def test_resource_requirements(self, keepdocker):
93 arv_docker_clear_cache()
94 runner = mock.MagicMock()
95 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
96 runner.ignore_docker_for_reuse = False
97 runner.intermediate_output_ttl = 3600
98 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
100 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
101 runner.api.collections().get().execute.return_value = {
102 "portable_data_hash": "99999999999999999999999999999993+99"}
108 "class": "ResourceRequirement",
114 "class": "http://arvados.org/cwl#RuntimeConstraints",
117 "class": "http://arvados.org/cwl#APIRequirement",
119 "class": "http://arvados.org/cwl#PartitionRequirement",
122 "class": "http://arvados.org/cwl#IntermediateOutput",
125 "class": "http://arvados.org/cwl#ReuseRequirement",
131 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
132 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
133 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
134 avsc_names=avsc_names, make_fs_access=make_fs_access,
136 arvtool.formatgraph = None
137 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_resource_requirements",
138 make_fs_access=make_fs_access, tmpdir="/tmp"):
139 j.run(enable_reuse=True)
141 call_args, call_kwargs = runner.api.container_requests().create.call_args
143 call_body_expected = {
145 'HOME': '/var/spool/cwl',
148 'name': 'test_resource_requirements',
149 'runtime_constraints': {
152 'keep_cache_ram': 536870912,
155 'use_existing': False,
158 '/tmp': {'kind': 'tmp',
159 "capacity": 4194304000 },
160 '/var/spool/cwl': {'kind': 'tmp',
161 "capacity": 5242880000 }
163 'state': 'Committed',
164 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
165 'output_path': '/var/spool/cwl',
167 'container_image': 'arvados/jobs',
169 'cwd': '/var/spool/cwl',
170 'scheduling_parameters': {
171 'partitions': ['blurb']
176 call_body = call_kwargs.get('body', None)
177 self.assertNotEqual(None, call_body)
178 for key in call_body:
179 self.assertEqual(call_body_expected.get(key), call_body.get(key))
182 # The test passes some fields in builder.resources
183 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
184 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
185 @mock.patch("arvados.collection.Collection")
186 def test_initial_work_dir(self, collection_mock, keepdocker):
187 arv_docker_clear_cache()
188 runner = mock.MagicMock()
189 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
190 runner.ignore_docker_for_reuse = False
191 runner.intermediate_output_ttl = 0
192 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
194 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
195 runner.api.collections().get().execute.return_value = {
196 "portable_data_hash": "99999999999999999999999999999993+99"}
198 sourcemock = mock.MagicMock()
199 def get_collection_mock(p):
201 return (sourcemock, p.split("/", 1)[1])
203 return (sourcemock, "")
204 runner.fs_access.get_collection.side_effect = get_collection_mock
206 vwdmock = mock.MagicMock()
207 collection_mock.return_value = vwdmock
208 vwdmock.portable_data_hash.return_value = "99999999999999999999999999999996+99"
214 "class": "InitialWorkDirRequirement",
218 "location": "keep:99999999999999999999999999999995+99/bar"
221 "class": "Directory",
223 "location": "keep:99999999999999999999999999999995+99"
227 "basename": "filename",
228 "location": "keep:99999999999999999999999999999995+99/baz/filename"
231 "class": "Directory",
232 "basename": "subdir",
233 "location": "keep:99999999999999999999999999999995+99/subdir"
239 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
240 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
241 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers",
242 avsc_names=avsc_names, make_fs_access=make_fs_access,
244 arvtool.formatgraph = None
245 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_initial_work_dir",
246 make_fs_access=make_fs_access, tmpdir="/tmp"):
249 call_args, call_kwargs = runner.api.container_requests().create.call_args
251 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
252 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
253 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
254 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
256 call_body_expected = {
258 'HOME': '/var/spool/cwl',
261 'name': 'test_initial_work_dir',
262 'runtime_constraints': {
266 'use_existing': True,
269 '/tmp': {'kind': 'tmp',
270 "capacity": 1073741824 },
271 '/var/spool/cwl': {'kind': 'tmp',
272 "capacity": 1073741824 },
273 '/var/spool/cwl/foo': {
274 'kind': 'collection',
276 'portable_data_hash': '99999999999999999999999999999996+99'
278 '/var/spool/cwl/foo2': {
279 'kind': 'collection',
281 'portable_data_hash': '99999999999999999999999999999996+99'
283 '/var/spool/cwl/filename': {
284 'kind': 'collection',
286 'portable_data_hash': '99999999999999999999999999999996+99'
288 '/var/spool/cwl/subdir': {
289 'kind': 'collection',
291 'portable_data_hash': '99999999999999999999999999999996+99'
294 'state': 'Committed',
295 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
296 'output_path': '/var/spool/cwl',
298 'container_image': 'arvados/jobs',
300 'cwd': '/var/spool/cwl',
301 'scheduling_parameters': {
306 call_body = call_kwargs.get('body', None)
307 self.assertNotEqual(None, call_body)
308 for key in call_body:
309 self.assertEqual(call_body_expected.get(key), call_body.get(key))
312 # Test redirecting stdin/stdout/stderr
313 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
314 def test_redirects(self, keepdocker):
315 arv_docker_clear_cache()
317 runner = mock.MagicMock()
318 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
319 runner.ignore_docker_for_reuse = False
320 runner.intermediate_output_ttl = 0
322 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
323 runner.api.collections().get().execute.return_value = {
324 "portable_data_hash": "99999999999999999999999999999993+99"}
326 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
332 "stdout": "stdout.txt",
333 "stderr": "stderr.txt",
334 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
335 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
338 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
339 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
340 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
341 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
342 arvtool.formatgraph = None
343 for j in arvtool.job({}, mock.MagicMock(), basedir="", name="test_run_redirect",
344 make_fs_access=make_fs_access, tmpdir="/tmp"):
346 runner.api.container_requests().create.assert_called_with(
347 body=JsonDiffMatcher({
349 'HOME': '/var/spool/cwl',
352 'name': 'test_run_redirect',
353 'runtime_constraints': {
357 'use_existing': True,
360 '/tmp': {'kind': 'tmp',
361 "capacity": 1073741824 },
362 '/var/spool/cwl': {'kind': 'tmp',
363 "capacity": 1073741824 },
366 "path": "/var/spool/cwl/stderr.txt"
369 "kind": "collection",
371 "portable_data_hash": "99999999999999999999999999999996+99"
375 "path": "/var/spool/cwl/stdout.txt"
378 'state': 'Committed',
379 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
380 'output_path': '/var/spool/cwl',
382 'container_image': 'arvados/jobs',
383 'command': ['ls', '/var/spool/cwl'],
384 'cwd': '/var/spool/cwl',
385 'scheduling_parameters': {},
389 @mock.patch("arvados.collection.Collection")
390 def test_done(self, col):
391 api = mock.MagicMock()
393 runner = mock.MagicMock()
395 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
396 runner.num_retries = 0
397 runner.ignore_docker_for_reuse = False
398 runner.intermediate_output_ttl = 0
400 runner.api.containers().get().execute.return_value = {"state":"Complete",
404 col().open.return_value = []
406 arvjob = arvados_cwl.ArvadosContainer(runner)
407 arvjob.name = "testjob"
408 arvjob.builder = mock.MagicMock()
409 arvjob.output_callback = mock.MagicMock()
410 arvjob.collect_outputs = mock.MagicMock()
411 arvjob.successCodes = [0]
412 arvjob.outdir = "/var/spool/cwl"
413 arvjob.output_ttl = 3600
415 arvjob.collect_outputs.return_value = {"out": "stuff"}
419 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
420 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
421 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
422 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
423 "modified_at": "2017-05-26T12:01:22Z"
426 self.assertFalse(api.collections().create.called)
428 arvjob.collect_outputs.assert_called_with("keep:abc+123")
429 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
430 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
432 # The test passes no builder.resources
433 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
434 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
435 def test_mounts(self, keepdocker):
436 arv_docker_clear_cache()
438 runner = mock.MagicMock()
439 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
440 runner.ignore_docker_for_reuse = False
441 runner.intermediate_output_ttl = 0
443 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
444 runner.api.collections().get().execute.return_value = {
445 "portable_data_hash": "99999999999999999999999999999993+99"}
447 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
456 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
459 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
460 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
461 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="containers", avsc_names=avsc_names,
462 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
463 arvtool.formatgraph = None
466 "class": "Directory",
467 "location": "keep:99999999999999999999999999999994+44",
471 "location": "keep:99999999999999999999999999999994+44/file1",
475 "location": "keep:99999999999999999999999999999994+44/file2",
480 for j in arvtool.job(job_order, mock.MagicMock(), basedir="", name="test_run_mounts",
481 make_fs_access=make_fs_access, tmpdir="/tmp"):
483 runner.api.container_requests().create.assert_called_with(
484 body=JsonDiffMatcher({
486 'HOME': '/var/spool/cwl',
489 'name': 'test_run_mounts',
490 'runtime_constraints': {
494 'use_existing': True,
497 "/keep/99999999999999999999999999999994+44": {
498 "kind": "collection",
499 "portable_data_hash": "99999999999999999999999999999994+44"
501 '/tmp': {'kind': 'tmp',
502 "capacity": 1073741824 },
503 '/var/spool/cwl': {'kind': 'tmp',
504 "capacity": 1073741824 }
506 'state': 'Committed',
507 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
508 'output_path': '/var/spool/cwl',
510 'container_image': 'arvados/jobs',
511 'command': ['ls', '/var/spool/cwl'],
512 'cwd': '/var/spool/cwl',
513 'scheduling_parameters': {},