X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/c2aceca339ec3a6f3d853865cebd0efe348ff518..ae92d144610446849eb568247a44f02ae985c281:/sdk/cwl/tests/test_submit.py diff --git a/sdk/cwl/tests/test_submit.py b/sdk/cwl/tests/test_submit.py index 12daf6b670..5092fc4575 100644 --- a/sdk/cwl/tests/test_submit.py +++ b/sdk/cwl/tests/test_submit.py @@ -47,17 +47,22 @@ _rootDesc = None def stubs(func): @functools.wraps(func) + @mock.patch("uuid.uuid4") @mock.patch("arvados.commands.keepdocker.list_images_in_arv") @mock.patch("arvados.collection.KeepClient") @mock.patch("arvados.keep.KeepClient") @mock.patch("arvados.events.subscribe") - def wrapped(self, events, keep_client1, keep_client2, keepdocker, *args, **kwargs): + def wrapped(self, events, keep_client1, keep_client2, keepdocker, uuid4, *args, **kwargs): class Stubs(object): pass stubs = Stubs() stubs.events = events stubs.keepdocker = keepdocker + uuid4.side_effect = ["df80736f-f14d-4b10-b2e3-03aa27f034bb", "df80736f-f14d-4b10-b2e3-03aa27f034b1", + "df80736f-f14d-4b10-b2e3-03aa27f034b2", "df80736f-f14d-4b10-b2e3-03aa27f034b3", + "df80736f-f14d-4b10-b2e3-03aa27f034b4", "df80736f-f14d-4b10-b2e3-03aa27f034b5"] + def putstub(p, **kwargs): return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p)) keep_client1().put.side_effect = putstub @@ -67,10 +72,10 @@ def stubs(func): stubs.keep_client = keep_client2 stubs.docker_images = { - "arvados/jobs:"+arvados_cwl.__version__: [("zzzzz-4zz18-zzzzzzzzzzzzzd3", "")], - "debian:buster-slim": [("zzzzz-4zz18-zzzzzzzzzzzzzd4", "")], - "arvados/jobs:123": [("zzzzz-4zz18-zzzzzzzzzzzzzd5", "")], - "arvados/jobs:latest": [("zzzzz-4zz18-zzzzzzzzzzzzzd6", "")], + "arvados/jobs:"+arvados_cwl.__version__: [("zzzzz-4zz18-zzzzzzzzzzzzzd3", {})], + "debian:buster-slim": [("zzzzz-4zz18-zzzzzzzzzzzzzd4", {})], + "arvados/jobs:123": [("zzzzz-4zz18-zzzzzzzzzzzzzd5", {})], + "arvados/jobs:latest": [("zzzzz-4zz18-zzzzzzzzzzzzzd6", {})], } def kd(a, b, image_name=None, image_tag=None): return stubs.docker_images.get("%s:%s" % (image_name, image_tag), []) @@ -87,6 +92,7 @@ def stubs(func): stubs.api = mock.MagicMock() stubs.api._rootDesc = get_rootDesc() stubs.api._rootDesc["uuidPrefix"] = "zzzzz" + stubs.api._rootDesc["revision"] = "20210628" stubs.api.users().current().execute.return_value = { "uuid": stubs.fake_user_uuid, @@ -95,6 +101,11 @@ def stubs(func): stubs.api.containers().current().execute.return_value = { "uuid": stubs.fake_container_uuid, } + stubs.api.config()["StorageClasses"].items.return_value = { + "default": { + "Default": True + } + }.items() class CollectionExecute(object): def __init__(self, exe): @@ -342,14 +353,12 @@ class TestSubmit(unittest.TestCase): cwltool.process._names = set() arvados_cwl.arvdocker.arv_docker_clear_cache() - @stubs - def test_error_when_multiple_storage_classes_specified(self, stubs): - storage_classes = "foo,bar" - exited = arvados_cwl.main( - ["--debug", "--storage-classes", storage_classes, - "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], - sys.stdin, sys.stderr, api_client=stubs.api) - self.assertEqual(exited, 1) + def tearDown(self): + root_logger = logging.getLogger('') + + # Remove existing RuntimeStatusLoggingHandlers if they exist + handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)] + root_logger.handlers = handlers @mock.patch("time.sleep") @stubs @@ -449,7 +458,7 @@ class TestSubmit(unittest.TestCase): "enableReuse": False, }, ] - expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = { + expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = { "arv": "http://arvados.org/cwl#", "cwltool": "http://commonwl.org/cwltool#" } @@ -526,6 +535,27 @@ class TestSubmit(unittest.TestCase): stubs.expect_container_request_uuid + '\n') self.assertEqual(exited, 0) + @stubs + def test_submit_multiple_storage_classes(self, stubs): + exited = arvados_cwl.main( + ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo,bar", "--intermediate-storage-classes=baz", + "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + + expect_container = copy.deepcopy(stubs.expect_container_spec) + expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', + '--no-log-timestamps', '--disable-validate', '--disable-color', + '--eval-timeout=20', '--thread-count=0', + '--enable-reuse', "--collection-cache-size=256", "--debug", + "--storage-classes=foo,bar", "--intermediate-storage-classes=baz", '--on-error=continue', + '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'] + + stubs.api.container_requests().create.assert_called_with( + body=JsonDiffMatcher(expect_container)) + self.assertEqual(stubs.capture_stdout.getvalue(), + stubs.expect_container_request_uuid + '\n') + self.assertEqual(exited, 0) + @mock.patch("cwltool.task_queue.TaskQueue") @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job") @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection") @@ -554,6 +584,7 @@ class TestSubmit(unittest.TestCase): def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq): final_output_c = arvados.collection.Collection() make_output.return_value = ({},final_output_c) + stubs.api.config().get.return_value = {"default": {"Default": True}} def set_final_output(job_order, output_callback, runtimeContext): output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success") @@ -568,6 +599,27 @@ class TestSubmit(unittest.TestCase): make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz') self.assertEqual(exited, 0) + @mock.patch("cwltool.task_queue.TaskQueue") + @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job") + @mock.patch("arvados_cwl.executor.ArvCwlExecutor.make_output_collection") + @stubs + def test_storage_class_hint_to_make_output_collection(self, stubs, make_output, job, tq): + final_output_c = arvados.collection.Collection() + make_output.return_value = ({},final_output_c) + + def set_final_output(job_order, output_callback, runtimeContext): + output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success") + return [] + job.side_effect = set_final_output + + exited = arvados_cwl.main( + ["--debug", "--local", + "tests/wf/submit_storage_class_wf.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + + make_output.assert_called_with(u'Output of submit_storage_class_wf.cwl', ['foo', 'bar'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz') + self.assertEqual(exited, 0) + @stubs def test_submit_container_output_ttl(self, stubs): exited = arvados_cwl.main( @@ -853,6 +905,7 @@ class TestSubmit(unittest.TestCase): @stubs def test_submit_container_project(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid, "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], @@ -992,7 +1045,7 @@ class TestSubmit(unittest.TestCase): "keep_cache": 512 } ] - expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = { + expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = { "arv": "http://arvados.org/cwl#", } expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers', @@ -1007,9 +1060,6 @@ class TestSubmit(unittest.TestCase): stubs.expect_container_request_uuid + '\n') self.assertEqual(exited, 0) - def tearDown(self): - arvados_cwl.arvdocker.arv_docker_clear_cache() - @mock.patch("arvados.commands.keepdocker.find_one_image_hash") @mock.patch("cwltool.docker.DockerCommandLineJob.get_image") @mock.patch("arvados.api") @@ -1020,6 +1070,7 @@ class TestSubmit(unittest.TestCase): arvrunner.project_uuid = "" api.return_value = mock.MagicMock() arvrunner.api = api.return_value + arvrunner.runtimeContext.match_local_docker = False arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "", "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb", "link_class": "docker_image_repo+tag", @@ -1086,9 +1137,6 @@ class TestSubmit(unittest.TestCase): "content": { "$graph": [ { - "$namespaces": { - "cwltool": "http://commonwl.org/cwltool#" - }, "arguments": [ "md5sum", "example.conf" @@ -1177,6 +1225,9 @@ class TestSubmit(unittest.TestCase): ] } ], + "$namespaces": { + "cwltool": "http://commonwl.org/cwltool#" + }, "cwlVersion": "v1.0" }, "kind": "json" @@ -1265,12 +1316,14 @@ class TestSubmit(unittest.TestCase): @stubs def test_submit_validate_project_uuid(self, stubs): + # Fails with bad cluster prefix exited = arvados_cwl.main( ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzb-j7d0g-zzzzzzzzzzzzzzz", "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) self.assertEqual(exited, 1) + # Project lookup fails stubs.api.groups().get().execute.side_effect = Exception("Bad project") exited = arvados_cwl.main( ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzz-j7d0g-zzzzzzzzzzzzzzx", @@ -1278,6 +1331,14 @@ class TestSubmit(unittest.TestCase): stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) self.assertEqual(exited, 1) + # It should work this time because it is looking up a user (and only group is stubbed out to fail) + exited = arvados_cwl.main( + ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid=zzzzz-tpzed-zzzzzzzzzzzzzzx", + "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + self.assertEqual(exited, 0) + + @mock.patch("arvados.collection.CollectionReader") @stubs def test_submit_uuid_inputs(self, stubs, collectionReader): @@ -1365,10 +1426,98 @@ class TestSubmit(unittest.TestCase): self.assertEqual(exited, 1) self.assertRegex( capture_stderr.getvalue(), - r"Collection uuid zzzzz-4zz18-zzzzzzzzzzzzzzz not found") + r"Collection\s*uuid\s*zzzzz-4zz18-zzzzzzzzzzzzzzz\s*not\s*found") finally: cwltool_logger.removeHandler(stderr_logger) + @stubs + def test_submit_set_process_properties(self, stubs): + exited = arvados_cwl.main( + ["--submit", "--no-wait", "--api=containers", "--debug", + "tests/wf/submit_wf_process_properties.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + + expect_container = copy.deepcopy(stubs.expect_container_spec) + expect_container["name"] = "submit_wf_process_properties.cwl" + expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [ + { + "class": "http://arvados.org/cwl#ProcessProperties", + "processProperties": [ + {"propertyName": "baz", + "propertyValue": "$(inputs.x.basename)"}, + {"propertyName": "foo", + "propertyValue": "bar"}, + {"propertyName": "quux", + "propertyValue": { + "q1": 1, + "q2": 2 + } + } + ], + } + ] + expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$namespaces"] = { + "arv": "http://arvados.org/cwl#" + } + + expect_container["properties"] = { + "baz": "blorp.txt", + "foo": "bar", + "quux": { + "q1": 1, + "q2": 2 + } + } + + stubs.api.container_requests().create.assert_called_with( + body=JsonDiffMatcher(expect_container)) + self.assertEqual(stubs.capture_stdout.getvalue(), + stubs.expect_container_request_uuid + '\n') + self.assertEqual(exited, 0) + + + @stubs + def test_submit_enable_preemptible(self, stubs): + exited = arvados_cwl.main( + ["--submit", "--no-wait", "--api=containers", "--debug", "--enable-preemptible", + "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + + expect_container = copy.deepcopy(stubs.expect_container_spec) + expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers', + '--no-log-timestamps', '--disable-validate', '--disable-color', + '--eval-timeout=20', '--thread-count=0', + '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue', + '--enable-preemptible', + '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'] + + stubs.api.container_requests().create.assert_called_with( + body=JsonDiffMatcher(expect_container)) + self.assertEqual(stubs.capture_stdout.getvalue(), + stubs.expect_container_request_uuid + '\n') + self.assertEqual(exited, 0) + + @stubs + def test_submit_disable_preemptible(self, stubs): + exited = arvados_cwl.main( + ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-preemptible", + "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"], + stubs.capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client) + + expect_container = copy.deepcopy(stubs.expect_container_spec) + expect_container['command'] = ['arvados-cwl-runner', '--local', '--api=containers', + '--no-log-timestamps', '--disable-validate', '--disable-color', + '--eval-timeout=20', '--thread-count=0', + '--enable-reuse', "--collection-cache-size=256", '--debug', '--on-error=continue', + '--disable-preemptible', + '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'] + + stubs.api.container_requests().create.assert_called_with( + body=JsonDiffMatcher(expect_container)) + self.assertEqual(stubs.capture_stdout.getvalue(), + stubs.expect_container_request_uuid + '\n') + self.assertEqual(exited, 0) + class TestCreateWorkflow(unittest.TestCase): existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml" @@ -1379,9 +1528,17 @@ class TestCreateWorkflow(unittest.TestCase): cwltool.process._names = set() arvados_cwl.arvdocker.arv_docker_clear_cache() + def tearDown(self): + root_logger = logging.getLogger('') + + # Remove existing RuntimeStatusLoggingHandlers if they exist + handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)] + root_logger.handlers = handlers + @stubs def test_create(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--create-workflow", "--debug", @@ -1411,6 +1568,7 @@ class TestCreateWorkflow(unittest.TestCase): @stubs def test_create_name(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--create-workflow", "--debug", @@ -1461,6 +1619,7 @@ class TestCreateWorkflow(unittest.TestCase): self.existing_workflow_uuid + '\n') self.assertEqual(exited, 0) + @stubs def test_update_name(self, stubs): exited = arvados_cwl.main( @@ -1486,6 +1645,7 @@ class TestCreateWorkflow(unittest.TestCase): @stubs def test_create_collection_per_tool(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--create-workflow", "--debug", @@ -1515,6 +1675,7 @@ class TestCreateWorkflow(unittest.TestCase): @stubs def test_create_with_imports(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--create-workflow", "--debug", @@ -1533,6 +1694,7 @@ class TestCreateWorkflow(unittest.TestCase): @stubs def test_create_with_no_input(self, stubs): project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz' + stubs.api.groups().get().execute.return_value = {"group_class": "project"} exited = arvados_cwl.main( ["--create-workflow", "--debug",