12 import cwltool.process
13 from schema_salad.ref_resolver import Loader
14 from schema_salad.sourceline import cmap
15 from .mock_discovery import get_rootDesc
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)
22 class TestJob(unittest.TestCase):
24 # The test passes no builder.resources
25 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
26 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
27 def test_run(self, list_images_in_arv):
28 for enable_reuse in (True, False):
29 runner = mock.MagicMock()
30 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
31 runner.ignore_docker_for_reuse = False
32 runner.num_retries = 0
33 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
35 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
36 runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
42 "arguments": [{"valueFrom": "$(runtime.outdir)"}]
44 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
45 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
46 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names,
47 basedir="", make_fs_access=make_fs_access, loader=Loader({}))
48 arvtool.formatgraph = None
49 for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
50 j.run(enable_reuse=enable_reuse)
51 runner.api.jobs().create.assert_called_with(
52 body=JsonDiffMatcher({
53 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
54 'runtime_constraints': {},
55 'script_parameters': {
57 'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
58 'command': ['ls', '$(task.outdir)']
61 'script_version': 'master',
62 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
63 'repository': 'arvados',
64 'script': 'crunchrunner',
65 'runtime_constraints': {
66 'docker_image': 'arvados/jobs',
67 'min_cores_per_node': 1,
68 'min_ram_mb_per_node': 1024,
69 'min_scratch_mb_per_node': 2048 # tmpdirSize + outdirSize
72 find_or_create=enable_reuse,
73 filters=[['repository', '=', 'arvados'],
74 ['script', '=', 'crunchrunner'],
75 ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
76 ['docker_image_locator', 'in docker', 'arvados/jobs']]
79 # The test passes some fields in builder.resources
80 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
81 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
82 def test_resource_requirements(self, list_images_in_arv):
83 runner = mock.MagicMock()
84 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
85 runner.ignore_docker_for_reuse = False
86 runner.num_retries = 0
87 arvados_cwl.add_arv_hints()
89 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
90 runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
92 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
99 "class": "ResourceRequirement",
104 "class": "http://arvados.org/cwl#RuntimeConstraints",
106 "outputDirType": "keep_output_dir"
108 "class": "http://arvados.org/cwl#APIRequirement",
112 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
113 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
114 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names,
115 make_fs_access=make_fs_access, loader=Loader({}))
116 arvtool.formatgraph = None
117 for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
119 runner.api.jobs().create.assert_called_with(
120 body=JsonDiffMatcher({
121 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
122 'runtime_constraints': {},
123 'script_parameters': {
125 'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
126 'task.keepTmpOutput': True,
130 'script_version': 'master',
131 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
132 'repository': 'arvados',
133 'script': 'crunchrunner',
134 'runtime_constraints': {
135 'docker_image': 'arvados/jobs',
136 'min_cores_per_node': 3,
137 'min_ram_mb_per_node': 3000,
138 'min_scratch_mb_per_node': 5024, # tmpdirSize + outdirSize
139 'keep_cache_mb_per_task': 512
143 filters=[['repository', '=', 'arvados'],
144 ['script', '=', 'crunchrunner'],
145 ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
146 ['docker_image_locator', 'in docker', 'arvados/jobs']])
148 @mock.patch("arvados.collection.CollectionReader")
149 def test_done(self, reader):
150 api = mock.MagicMock()
152 runner = mock.MagicMock()
154 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
155 runner.num_retries = 0
156 runner.ignore_docker_for_reuse = False
158 reader().open.return_value = StringIO.StringIO(
159 """2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.tmpdir)=/tmp/crunch-job-task-work/compute3.1/tmpdir
160 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.outdir)=/tmp/crunch-job-task-work/compute3.1/outdir
161 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.keep)=/keep
163 api.collections().list().execute.side_effect = ({"items": []},
164 {"items": [{"manifest_text": "XYZ"}]})
166 arvjob = arvados_cwl.ArvadosJob(runner)
167 arvjob.name = "testjob"
168 arvjob.builder = mock.MagicMock()
169 arvjob.output_callback = mock.MagicMock()
170 arvjob.collect_outputs = mock.MagicMock()
171 arvjob.collect_outputs.return_value = {"out": "stuff"}
175 "output": "99999999999999999999999999999993+99",
176 "log": "99999999999999999999999999999994+99",
177 "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
180 api.collections().list.assert_has_calls([
182 mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
183 ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
184 ['name', '=', 'Output 9999999 of testjob']]),
185 mock.call().execute(num_retries=0),
186 mock.call(limit=1, filters=[['portable_data_hash', '=', '99999999999999999999999999999993+99']],
187 select=['manifest_text']),
188 mock.call().execute(num_retries=0)])
190 api.collections().create.assert_called_with(
191 ensure_unique_name=True,
192 body={'portable_data_hash': '99999999999999999999999999999993+99',
193 'manifest_text': 'XYZ',
194 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
195 'name': 'Output 9999999 of testjob'})
197 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
199 @mock.patch("arvados.collection.CollectionReader")
200 def test_done_use_existing_collection(self, reader):
201 api = mock.MagicMock()
203 runner = mock.MagicMock()
205 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
206 runner.num_retries = 0
208 reader().open.return_value = StringIO.StringIO(
209 """2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.tmpdir)=/tmp/crunch-job-task-work/compute3.1/tmpdir
210 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.outdir)=/tmp/crunch-job-task-work/compute3.1/outdir
211 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.keep)=/keep
214 api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2"}]},)
216 arvjob = arvados_cwl.ArvadosJob(runner)
217 arvjob.name = "testjob"
218 arvjob.builder = mock.MagicMock()
219 arvjob.output_callback = mock.MagicMock()
220 arvjob.collect_outputs = mock.MagicMock()
221 arvjob.collect_outputs.return_value = {"out": "stuff"}
225 "output": "99999999999999999999999999999993+99",
226 "log": "99999999999999999999999999999994+99",
227 "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
230 api.collections().list.assert_has_calls([
232 mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
233 ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
234 ['name', '=', 'Output 9999999 of testjob']]),
235 mock.call().execute(num_retries=0)])
237 self.assertFalse(api.collections().create.called)
239 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
242 class TestWorkflow(unittest.TestCase):
243 # The test passes no builder.resources
244 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
245 @mock.patch("arvados.collection.Collection")
246 @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
247 def test_run(self, list_images_in_arv, mockcollection):
248 arvados_cwl.add_arv_hints()
250 api = mock.MagicMock()
251 api._rootDesc = get_rootDesc()
253 runner = arvados_cwl.ArvCwlRunner(api)
254 self.assertEqual(runner.work_api, 'jobs')
256 list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
257 runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
259 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
260 runner.ignore_docker_for_reuse = False
261 runner.num_retries = 0
262 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
264 tool, metadata = document_loader.resolve_ref("tests/wf/scatter2.cwl")
265 metadata["cwlVersion"] = tool["cwlVersion"]
267 mockcollection().portable_data_hash.return_value = "99999999999999999999999999999999+118"
269 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
270 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
271 arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, work_api="jobs", avsc_names=avsc_names,
272 basedir="", make_fs_access=make_fs_access, loader=document_loader,
273 makeTool=runner.arv_make_tool, metadata=metadata)
274 arvtool.formatgraph = None
275 it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access)
279 with open("tests/wf/scatter2_subwf.cwl") as f:
282 runner.api.jobs().create.assert_called_with(
283 body=JsonDiffMatcher({
284 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
285 'repository': 'arvados',
286 'script_version': 'master',
287 'script': 'crunchrunner',
288 'script_parameters': {
289 'tasks': [{'task.env': {
290 'HOME': '$(task.outdir)',
291 'TMPDIR': '$(task.tmpdir)'},
293 'workflow.cwl': '$(task.keep)/99999999999999999999999999999999+118/workflow.cwl',
294 'cwl.input.yml': '$(task.keep)/99999999999999999999999999999999+118/cwl.input.yml'
296 'command': [u'cwltool', u'--no-container', u'--move-outputs', u'--preserve-entire-environment', u'workflow.cwl#main', u'cwl.input.yml'],
297 'task.stdout': 'cwl.output.json'}]},
298 'runtime_constraints': {
299 'min_scratch_mb_per_node': 2048,
300 'min_cores_per_node': 1,
301 'docker_image': 'arvados/jobs',
302 'min_ram_mb_per_node': 1024
304 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}),
305 filters=[['repository', '=', 'arvados'],
306 ['script', '=', 'crunchrunner'],
307 ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
308 ['docker_image_locator', 'in docker', 'arvados/jobs']],
311 mockcollection().open().__enter__().write.assert_has_calls([mock.call(subwf)])
312 mockcollection().open().__enter__().write.assert_has_calls([mock.call(
317 def test_default_work_api(self):
318 arvados_cwl.add_arv_hints()
320 api = mock.MagicMock()
321 api._rootDesc = copy.deepcopy(get_rootDesc())
322 del api._rootDesc.get('resources')['jobs']['methods']['create']
323 runner = arvados_cwl.ArvCwlRunner(api)
324 self.assertEqual(runner.work_api, 'containers')