From 8c7364acd10df79d9ed9c802b8cf116f4369597d Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 24 Jun 2020 12:50:52 -0700 Subject: [PATCH 1/6] raising exception when trying to start non-existent orchestration function --- azure/durable_functions/models/DurableOrchestrationClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index e9299c6d..67663257 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -74,7 +74,7 @@ async def start_new(self, if response[0] <= 202 and response[1]: return response[1]["id"] else: - return None + raise Exception(f"Failed to start orchestration named \"{orchestration_function_name}\". Did you make a typo?") def create_check_status_response(self, request, instance_id): """Create a HttpResponse that contains useful information for \ From a7b9b5c0d6ccdc337c45a1cc82fc0c47c5710a4c Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 24 Jun 2020 14:07:47 -0700 Subject: [PATCH 2/6] fixed linting errors --- azure/durable_functions/models/DurableOrchestrationClient.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index 67663257..1ac768a5 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -74,7 +74,9 @@ async def start_new(self, if response[0] <= 202 and response[1]: return response[1]["id"] else: - raise Exception(f"Failed to start orchestration named \"{orchestration_function_name}\". Did you make a typo?") + err_message = f"Failed to start orchestration: {orchestration_function_name}."\ + "Did you make a typo?" + raise Exception(err_message) def create_check_status_response(self, request, instance_id): """Create a HttpResponse that contains useful information for \ From fbefa51315c96b5d557628abdf2d440a10f4ddf4 Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 24 Jun 2020 19:37:58 -0700 Subject: [PATCH 3/6] surfacing extension-level error --- .../durable_functions/models/DurableOrchestrationClient.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index 1ac768a5..56a628f9 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -74,9 +74,9 @@ async def start_new(self, if response[0] <= 202 and response[1]: return response[1]["id"] else: - err_message = f"Failed to start orchestration: {orchestration_function_name}."\ - "Did you make a typo?" - raise Exception(err_message) + # Surfacing the durable-extension exception + exception_message = response[1] + raise Exception(exception_message) def create_check_status_response(self, request, instance_id): """Create a HttpResponse that contains useful information for \ From 37794165908ef5e36e9d33b3bba28e8e79757280 Mon Sep 17 00:00:00 2001 From: David Justo Date: Thu, 25 Jun 2020 10:05:24 -0700 Subject: [PATCH 4/6] more fine-grained handling of the exception --- .../models/DurableOrchestrationClient.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index 56a628f9..54026c2b 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -73,10 +73,17 @@ async def start_new(self, if response[0] <= 202 and response[1]: return response[1]["id"] + elif response[0] == 400: + # Orchestrator not found + exception_data = response[1] + exception_message = exception_data["ExceptionMessage"] + stack_trace = exception_data["StackTrace"] + exception_message += '\n' + stack_trace + raise Exception(exception_message) else: - # Surfacing the durable-extension exception + # Catch all: simply surfacing the durable-extension exception exception_message = response[1] - raise Exception(exception_message) + raise Exception(exception_message)) def create_check_status_response(self, request, instance_id): """Create a HttpResponse that contains useful information for \ From 25cc234aac466a50461eb4cc20d9e1e3b0fdced0 Mon Sep 17 00:00:00 2001 From: David Justo Date: Thu, 25 Jun 2020 15:37:44 -0700 Subject: [PATCH 5/6] no stack trace for expected error --- .../durable_functions/models/DurableOrchestrationClient.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index 54026c2b..c96f370d 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -74,16 +74,15 @@ async def start_new(self, if response[0] <= 202 and response[1]: return response[1]["id"] elif response[0] == 400: - # Orchestrator not found + # Orchestrator not found, report clean exception exception_data = response[1] exception_message = exception_data["ExceptionMessage"] - stack_trace = exception_data["StackTrace"] - exception_message += '\n' + stack_trace raise Exception(exception_message) else: # Catch all: simply surfacing the durable-extension exception + # we surface the stack trace too, since this may be a more involed exception exception_message = response[1] - raise Exception(exception_message)) + raise Exception(exception_message) def create_check_status_response(self, request, instance_id): """Create a HttpResponse that contains useful information for \ From d9d65353588ad93c0874e6eae0118209330f4d13 Mon Sep 17 00:00:00 2001 From: David Justo Date: Thu, 25 Jun 2020 22:07:08 -0700 Subject: [PATCH 6/6] added unit tests --- .../models/test_DurableOrchestrationClient.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/models/test_DurableOrchestrationClient.py b/tests/models/test_DurableOrchestrationClient.py index 4585c286..49fc9ff3 100644 --- a/tests/models/test_DurableOrchestrationClient.py +++ b/tests/models/test_DurableOrchestrationClient.py @@ -19,6 +19,13 @@ MESSAGE_500 = 'instance failed with unhandled exception' MESSAGE_501 = "well we didn't expect that" +TEST_ORCHESTRATOR = "MyDurableOrchestrator" +EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE = "The function doesn't exist,"\ + " is disabled, or is not an orchestrator function. Additional info: "\ + "the following are the known orchestrator functions: " +EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE = "One or more of the arguments submitted is incorrect" +EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND = "System.ArgumentException" +STACK_TRACE = "' at Microsoft.Azure.WebJobs.Extensions.DurableTask..." class MockRequest: def __init__(self, expected_url: str, response: [int, any]): @@ -497,3 +504,39 @@ async def test_wait_or_response_check_status_response(binding_string): with pytest.raises(Exception): await client.wait_for_completion_or_create_check_status_response( None, TEST_INSTANCE_ID, timeout_in_milliseconds=500) + +@pytest.mark.asyncio +async def test_start_new_orchestrator_not_found(binding_string): + """Test that we throw the right exception when the orchestrator is not found. + """ + status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE, + StackTrace=STACK_TRACE, + Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE, + ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND) + mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}", + response=[400, status]) + client = DurableOrchestrationClient(binding_string) + client._post_async_request = mock_request.post + + with pytest.raises(Exception) as ex: + await client.start_new(TEST_ORCHESTRATOR) + ex.match(EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE) + + +@pytest.mark.asyncio +async def test_start_new_orchestrator_internal_exception(binding_string): + """Test that we throw the right exception when the extension fails internally. + """ + status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE, + StackTrace=STACK_TRACE, + Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE, + ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND) + mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}", + response=[500, status]) + client = DurableOrchestrationClient(binding_string) + client._post_async_request = mock_request.post + + status_str = str(status) + with pytest.raises(Exception) as ex: + await client.start_new(TEST_ORCHESTRATOR) + ex.match(status_str)