From 65ae95e34248a9bb3415b2aee03997bbff1717df Mon Sep 17 00:00:00 2001 From: Shreyas Gopalakrishna Date: Tue, 7 Mar 2023 14:59:55 -0600 Subject: [PATCH 1/4] Added support for rewinding a failed instance --- .../azurefunctions/DurableClientContext.java | 34 ++++++- .../durabletask/DurableTaskClient.java | 15 +++ .../durabletask/DurableTaskGrpcClient.java | 10 ++ samples-azure-functions/extensions.csproj | 12 +++ samples-azure-functions/host.json | 4 - .../java/com/functions/RewindInstance.java | 91 +++++++++++++++++++ .../java/com/functions/EndToEndTests.java | 39 +++++++- 7 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 samples-azure-functions/extensions.csproj create mode 100644 samples-azure-functions/src/main/java/com/functions/RewindInstance.java diff --git a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java index a952db6..2fc5323 100644 --- a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java +++ b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java @@ -146,6 +146,38 @@ private String getInstanceStatusURL(HttpRequestMessage request, String instan throw new IllegalArgumentException("Failed to encode the instance ID: " + instanceId, ex); } - return baseUrl + "/runtime/webhooks/durabletask/instances/" + encodedInstanceId; + String instanceStatusURL = baseUrl + "/runtime/webhooks/durabletask/instances/" + encodedInstanceId; + + // Construct the response as an HTTP 202 with a JSON object payload + return request.createResponseBuilder(HttpStatus.ACCEPTED) + .header("Location", instanceStatusURL + "?" + this.requiredQueryStringParameters) + .header("Content-Type", "application/json") + .body(new HttpCreateCheckStatusResponse( + instanceId, + instanceStatusURL, + this.requiredQueryStringParameters)) + .build(); + } + + private static class HttpCreateCheckStatusResponse { + // These fields are serialized to JSON + public final String id; + public final String purgeHistoryDeleteUri; + public final String sendEventPostUri; + public final String statusQueryGetUri; + public final String terminatePostUri; + public final String rewindPostUri; + + public HttpCreateCheckStatusResponse( + String instanceId, + String instanceStatusURL, + String requiredQueryStringParameters) { + this.id = instanceId; + this.purgeHistoryDeleteUri = instanceStatusURL + "?" + requiredQueryStringParameters; + this.sendEventPostUri = instanceStatusURL + "/raiseEvent/{eventName}?" + requiredQueryStringParameters; + this.statusQueryGetUri = instanceStatusURL + "?" + requiredQueryStringParameters; + this.terminatePostUri = instanceStatusURL + "/terminate?reason={text}&" + requiredQueryStringParameters; + this.rewindPostUri = instanceStatusURL + "/rewind?reason={text}&" + requiredQueryStringParameters; + } } } diff --git a/client/src/main/java/com/microsoft/durabletask/DurableTaskClient.java b/client/src/main/java/com/microsoft/durabletask/DurableTaskClient.java index 4590277..3c8188f 100644 --- a/client/src/main/java/com/microsoft/durabletask/DurableTaskClient.java +++ b/client/src/main/java/com/microsoft/durabletask/DurableTaskClient.java @@ -321,4 +321,19 @@ public void resumeInstance(String instanceId) { * @param reason the reason for resuming the orchestration instance */ public abstract void resumeInstance(String instanceId, @Nullable String reason); + + /** + * Rewinds a failed orchestration instance. + * @param instanceId the ID of the orchestration instance to rewind + * @param reason the reason for rewinding the orchestration instance + */ + public abstract void rewindInstance(String instanceId, @Nullable String reason); + + /** + * Rewinds a failed orchestration instance. + * @param instanceId the ID of the orchestration instance to rewind + */ + public void rewindInstance(String instanceId) { + this.rewindInstance(instanceId, null); + } } \ No newline at end of file diff --git a/client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java b/client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java index 95bb984..5a8be4e 100644 --- a/client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java +++ b/client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java @@ -321,6 +321,16 @@ public String restartInstance(String instanceId, boolean restartWithNewInstanceI } } + @Override + public void rewindInstance(String instanceId, @Nullable String reason) { + RewindInstanceRequest.Builder rewindInstanceRequestBuilder = RewindInstanceRequest.newBuilder(); + rewindInstanceRequestBuilder.setInstanceId(instanceId); + if (reason != null) { + rewindInstanceRequestBuilder.setReason(StringValue.of(reason)); + } + this.sidecarClient.rewindInstance(rewindInstanceRequestBuilder.build()); + } + private PurgeResult toPurgeResult(PurgeInstancesResponse response){ return new PurgeResult(response.getDeletedInstanceCount()); } diff --git a/samples-azure-functions/extensions.csproj b/samples-azure-functions/extensions.csproj new file mode 100644 index 0000000..62b5a6c --- /dev/null +++ b/samples-azure-functions/extensions.csproj @@ -0,0 +1,12 @@ + + + net6.0 + + ** + + + + + + + diff --git a/samples-azure-functions/host.json b/samples-azure-functions/host.json index 9d59145..097ed03 100644 --- a/samples-azure-functions/host.json +++ b/samples-azure-functions/host.json @@ -15,9 +15,5 @@ "durableTask": { "hubName": "DFJavaSmokeTest" } - }, - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[4.*, 5.0.0)" } } \ No newline at end of file diff --git a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java new file mode 100644 index 0000000..443b6a9 --- /dev/null +++ b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java @@ -0,0 +1,91 @@ +package com.functions; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.TaskOrchestrationContext; +import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger; +import com.microsoft.durabletask.azurefunctions.DurableClientContext; +import com.microsoft.durabletask.azurefunctions.DurableClientInput; +import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger; + +import java.util.Optional; + +/** + * Azure Durable Functions with HTTP trigger - Rewind instance sample. + */ +public class RewindInstance { + private static int approvalFlag = 0; + + /** + * This HTTP-triggered function starts the approval orchestration. + */ + @FunctionName("ApprovalWorkflowOrchestration") + public HttpResponseMessage approvalWorkflowOrchestration( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @DurableClientInput(name = "durableContext") DurableClientContext durableContext, + final ExecutionContext context) throws InterruptedException { + context.getLogger().info("Java HTTP trigger processed a request."); + + DurableTaskClient client = durableContext.getClient(); + String instanceId = client.scheduleNewOrchestrationInstance("ApprovalWorkflow"); + context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId); + return durableContext.createCheckStatusResponse(request, instanceId); + } + + @FunctionName("ApprovalWorkflow") + public int approvalWorkflow( + @DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) { + int result = 0; + result += ctx.callActivity("RequestPrimaryApproval", 1, Integer.class).await(); + result += ctx.callActivity("RequestSecondaryApproval", 1, Integer.class).await(); + return result; + } + + /** + * This is the activity function that gets invoked by the approval orchestration. + */ + @FunctionName("RequestPrimaryApproval") + public int requestPrimaryApproval( + @DurableActivityTrigger(name = "name") int number, + final ExecutionContext context) { + return 1; + } + + /** + * This is the activity function that fails the first try and is then revived. + */ + @FunctionName("RequestSecondaryApproval") + public int requestSecondaryApproval( + @DurableActivityTrigger(name = "name") int number, + final ExecutionContext context) throws InterruptedException { + System.out.println("Test RequestSecondaryApproval " + approvalFlag); + return number / approvalFlag++; + } + + /** + * This HTTP-triggered function rewinds the orchestration using instanceId. + */ + @FunctionName("RewindInstance") + public String rewindInstance( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @DurableClientInput(name = "durableContext") DurableClientContext durableContext, + final ExecutionContext context) { + String instanceId = request.getQueryParameters().getOrDefault("instanceId", ""); + String reason = "Orchestrator failed and needs to be revived."; + + DurableTaskClient client = durableContext.getClient(); + + try { + client.rewindInstance(instanceId, reason); + return "Failed orchestration instance is revived."; + }catch (Exception e){ + return "Exception when rewinding orchestration instance"; + } + } +} diff --git a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java index 411dbba..653dda1 100644 --- a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java +++ b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java @@ -21,6 +21,10 @@ @Tag("e2e") public class EndToEndTests { + private static final String hostHealthPingPath = "/admin/host/ping"; + private static final String startOrchestrationPath = "/api/StartOrchestration"; + private static final String approvalWorkFlow = "/api/ApprovalWorkflowOrchestration"; + private static JsonPath rewindTestJsonPath = null; @Order(1) @Test @@ -202,4 +206,37 @@ private boolean pollingCheck(String statusQueryGetUri, } return false; } -} \ No newline at end of file + + @Order(2) + @Test + public void approvalWorkFlow() throws InterruptedException { + Response response = post(approvalWorkFlow); + rewindTestJsonPath = response.jsonPath(); + Thread.sleep(3000); + String statusQueryGetUri = rewindTestJsonPath.get("statusQueryGetUri"); + Response statusResponse = get(statusQueryGetUri); + String runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + assertEquals("Failed", runTimeStatus); + } + + @Order(3) + @Test + public void rewindInstance() throws InterruptedException { + String rewindPostUri = rewindTestJsonPath.get("rewindPostUri"); + Response response = post(rewindPostUri); + + Thread.sleep(3000); + + String statusQueryGetUri = rewindTestJsonPath.get("statusQueryGetUri"); + String runTimeStatus = null; + for (int i = 0; i < 5; i++) { + Response statusResponse = get(statusQueryGetUri); + runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + if (!"Completed".equals(runTimeStatus)) { + Thread.sleep(1000); + } else break; + } + assertEquals("Completed", runTimeStatus); + } +} + From f2e6fc267ae9ec3f2ec19a66d6deb840cc821456 Mon Sep 17 00:00:00 2001 From: Shreyas Gopalakrishna Date: Mon, 13 Mar 2023 17:17:29 -0500 Subject: [PATCH 2/4] Simplified test and fixed review comments --- .../java/com/functions/RewindInstance.java | 10 ++------ .../java/com/functions/EndToEndTests.java | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java index 443b6a9..21d80c9 100644 --- a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java +++ b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java @@ -64,7 +64,6 @@ public int requestPrimaryApproval( public int requestSecondaryApproval( @DurableActivityTrigger(name = "name") int number, final ExecutionContext context) throws InterruptedException { - System.out.println("Test RequestSecondaryApproval " + approvalFlag); return number / approvalFlag++; } @@ -80,12 +79,7 @@ public String rewindInstance( String reason = "Orchestrator failed and needs to be revived."; DurableTaskClient client = durableContext.getClient(); - - try { - client.rewindInstance(instanceId, reason); - return "Failed orchestration instance is revived."; - }catch (Exception e){ - return "Exception when rewinding orchestration instance"; - } + client.rewindInstance(instanceId, reason); + return "Failed orchestration instance is scheduled for rewind."; } } diff --git a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java index 653dda1..f9924f3 100644 --- a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java +++ b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java @@ -24,7 +24,7 @@ public class EndToEndTests { private static final String hostHealthPingPath = "/admin/host/ping"; private static final String startOrchestrationPath = "/api/StartOrchestration"; private static final String approvalWorkFlow = "/api/ApprovalWorkflowOrchestration"; - private static JsonPath rewindTestJsonPath = null; + private static final String rewindInstance = "/api/RewindInstance"; @Order(1) @Test @@ -209,28 +209,29 @@ private boolean pollingCheck(String statusQueryGetUri, @Order(2) @Test - public void approvalWorkFlow() throws InterruptedException { + public void testRewindInstanceAPI() throws InterruptedException { Response response = post(approvalWorkFlow); - rewindTestJsonPath = response.jsonPath(); + JsonPath rewindTestJsonPath = response.jsonPath(); + + // Wait for the ApprovalWorkflowOrchestration to fail Thread.sleep(3000); + + String instanceId = rewindTestJsonPath.get("id"); String statusQueryGetUri = rewindTestJsonPath.get("statusQueryGetUri"); Response statusResponse = get(statusQueryGetUri); String runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); assertEquals("Failed", runTimeStatus); - } - @Order(3) - @Test - public void rewindInstance() throws InterruptedException { - String rewindPostUri = rewindTestJsonPath.get("rewindPostUri"); - Response response = post(rewindPostUri); + // Rewind the instance + String rewindPostUri = rewindInstance + "?instanceId=" + instanceId; + response = post(rewindPostUri); + assertEquals("Failed orchestration instance is scheduled for rewind.", response.toString()); + // Wait for orchestration to rewind and complete Thread.sleep(3000); - String statusQueryGetUri = rewindTestJsonPath.get("statusQueryGetUri"); - String runTimeStatus = null; for (int i = 0; i < 5; i++) { - Response statusResponse = get(statusQueryGetUri); + statusResponse = get(statusQueryGetUri); runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); if (!"Completed".equals(runTimeStatus)) { Thread.sleep(1000); From d76307c5db20b34c5de92fc4fc9edee37b7414c2 Mon Sep 17 00:00:00 2001 From: Shreyas Gopalakrishna Date: Tue, 14 Mar 2023 10:44:46 -0500 Subject: [PATCH 3/4] Added test for RewindInstanceHttpAPI and addressed review comments --- .../java/com/functions/RewindInstance.java | 18 ++++- .../java/com/functions/EndToEndTests.java | 81 +++++++++++++++---- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java index 21d80c9..70024a6 100644 --- a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java +++ b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java @@ -1,9 +1,6 @@ package com.functions; -import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.HttpMethod; -import com.microsoft.azure.functions.HttpRequestMessage; -import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.*; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; @@ -82,4 +79,17 @@ public String rewindInstance( client.rewindInstance(instanceId, reason); return "Failed orchestration instance is scheduled for rewind."; } + + /** + * This HTTP-triggered function resets the approvalFlag variable for testing purposes. + */ + @FunctionName("ResetApproval") + public static HttpResponseMessage resetApproval( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + context.getLogger().info("ResetApproval function invoked."); + approvalFlag = 0; + return request.createResponseBuilder(HttpStatus.OK).body(approvalFlag).build(); + } } diff --git a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java index f9924f3..50cd533 100644 --- a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java +++ b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java @@ -21,10 +21,11 @@ @Tag("e2e") public class EndToEndTests { - private static final String hostHealthPingPath = "/admin/host/ping"; - private static final String startOrchestrationPath = "/api/StartOrchestration"; - private static final String approvalWorkFlow = "/api/ApprovalWorkflowOrchestration"; - private static final String rewindInstance = "/api/RewindInstance"; + private static final String hostHealthPingUrl = "/admin/host/ping"; + private static final String startOrchestrationUrl = "/api/StartOrchestration"; + private static final String startApprovalWorkflowUrl = "/api/ApprovalWorkflowOrchestration"; + private static final String rewindInstanceFunctionUrl = "/api/RewindInstance"; + private static final String resetApprovalUrl = "/api/ResetApprovalFlag"; @Order(1) @Test @@ -80,6 +81,15 @@ public void retryTestSuccess() throws InterruptedException { String statusQueryGetUri = jsonPath.get("statusQueryGetUri"); boolean pass = pollingCheck(statusQueryGetUri, "Completed", null, Duration.ofSeconds(10)); assertTrue(pass); + String runtimeStatus = null; + for (int i = 0; i < 15; i++) { + Response statusResponse = get(statusQueryGetUri); + runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + if (!"Completed".equals(runtimeStatus)) { + Thread.sleep(1000); + } else break; + } + assertEquals("Completed", runtimeStatus); } @Test @@ -209,22 +219,22 @@ private boolean pollingCheck(String statusQueryGetUri, @Order(2) @Test - public void testRewindInstanceAPI() throws InterruptedException { - Response response = post(approvalWorkFlow); - JsonPath rewindTestJsonPath = response.jsonPath(); + public void testRewindInstanceJavaAPI() throws InterruptedException { + Response response = post(startApprovalWorkflowUrl); + JsonPath startOrchestrationResponseJson = response.jsonPath(); // Wait for the ApprovalWorkflowOrchestration to fail Thread.sleep(3000); - String instanceId = rewindTestJsonPath.get("id"); - String statusQueryGetUri = rewindTestJsonPath.get("statusQueryGetUri"); + String instanceId = startOrchestrationResponseJson.get("id"); + String statusQueryGetUri = startOrchestrationResponseJson.get("statusQueryGetUri"); Response statusResponse = get(statusQueryGetUri); - String runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); - assertEquals("Failed", runTimeStatus); + String runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + assertEquals("Failed", runtimeStatus); - // Rewind the instance - String rewindPostUri = rewindInstance + "?instanceId=" + instanceId; - response = post(rewindPostUri); + // Rewind the instance using Java API + String rewindInstanceUrl = rewindInstanceFunctionUrl + "?instanceId=" + instanceId; + response = post(rewindInstanceUrl); assertEquals("Failed orchestration instance is scheduled for rewind.", response.toString()); // Wait for orchestration to rewind and complete @@ -232,12 +242,49 @@ public void testRewindInstanceAPI() throws InterruptedException { for (int i = 0; i < 5; i++) { statusResponse = get(statusQueryGetUri); - runTimeStatus = statusResponse.jsonPath().get("runtimeStatus"); - if (!"Completed".equals(runTimeStatus)) { + runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + if (!"Completed".equals(runtimeStatus)) { Thread.sleep(1000); } else break; } - assertEquals("Completed", runTimeStatus); + assertEquals("Completed", runtimeStatus); + + // Reset approval for other test cases + post(resetApprovalUrl); + } + + @Order(3) + @Test + public void testRewindInstanceHttpAPI() throws InterruptedException { + Response response = post(startApprovalWorkflowUrl); + JsonPath startOrchestrationResponseJson = response.jsonPath(); + + // Wait for the ApprovalWorkflowOrchestration to fail + Thread.sleep(3000); + + String statusQueryGetUri = startOrchestrationResponseJson.get("statusQueryGetUri"); + Response statusResponse = get(statusQueryGetUri); + String runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + assertEquals("Failed", runtimeStatus); + + // Rewind the instance using Http API + String rewindPostUri = startOrchestrationResponseJson.get("rewindPostUri"); + post(rewindPostUri); + + // Wait for orchestration to rewind and complete + Thread.sleep(3000); + + for (int i = 0; i < 5; i++) { + statusResponse = get(statusQueryGetUri); + runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); + if (!"Completed".equals(runtimeStatus)) { + Thread.sleep(1000); + } else break; + } + assertEquals("Completed", runtimeStatus); + + // Reset approval for other test cases + post(resetApprovalUrl); } } From 55502ab4b916c3894e4b300619db622007360008 Mon Sep 17 00:00:00 2001 From: Shreyas Gopalakrishna Date: Wed, 25 Oct 2023 15:54:46 -0500 Subject: [PATCH 4/4] Rebased with latest main --- .../azurefunctions/DurableClientContext.java | 34 +------------------ .../azurefunctions/HttpManagementPayload.java | 2 ++ samples-azure-functions/extensions.csproj | 12 ------- samples-azure-functions/host.json | 4 +++ .../java/com/functions/RewindInstance.java | 2 +- .../java/com/functions/EndToEndTests.java | 16 ++------- 6 files changed, 11 insertions(+), 59 deletions(-) delete mode 100644 samples-azure-functions/extensions.csproj diff --git a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java index 2fc5323..a952db6 100644 --- a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java +++ b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java @@ -146,38 +146,6 @@ private String getInstanceStatusURL(HttpRequestMessage request, String instan throw new IllegalArgumentException("Failed to encode the instance ID: " + instanceId, ex); } - String instanceStatusURL = baseUrl + "/runtime/webhooks/durabletask/instances/" + encodedInstanceId; - - // Construct the response as an HTTP 202 with a JSON object payload - return request.createResponseBuilder(HttpStatus.ACCEPTED) - .header("Location", instanceStatusURL + "?" + this.requiredQueryStringParameters) - .header("Content-Type", "application/json") - .body(new HttpCreateCheckStatusResponse( - instanceId, - instanceStatusURL, - this.requiredQueryStringParameters)) - .build(); - } - - private static class HttpCreateCheckStatusResponse { - // These fields are serialized to JSON - public final String id; - public final String purgeHistoryDeleteUri; - public final String sendEventPostUri; - public final String statusQueryGetUri; - public final String terminatePostUri; - public final String rewindPostUri; - - public HttpCreateCheckStatusResponse( - String instanceId, - String instanceStatusURL, - String requiredQueryStringParameters) { - this.id = instanceId; - this.purgeHistoryDeleteUri = instanceStatusURL + "?" + requiredQueryStringParameters; - this.sendEventPostUri = instanceStatusURL + "/raiseEvent/{eventName}?" + requiredQueryStringParameters; - this.statusQueryGetUri = instanceStatusURL + "?" + requiredQueryStringParameters; - this.terminatePostUri = instanceStatusURL + "/terminate?reason={text}&" + requiredQueryStringParameters; - this.rewindPostUri = instanceStatusURL + "/rewind?reason={text}&" + requiredQueryStringParameters; - } + return baseUrl + "/runtime/webhooks/durabletask/instances/" + encodedInstanceId; } } diff --git a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/HttpManagementPayload.java b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/HttpManagementPayload.java index f78c00f..b210e57 100644 --- a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/HttpManagementPayload.java +++ b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/HttpManagementPayload.java @@ -18,6 +18,7 @@ public class HttpManagementPayload { private final String terminatePostUri; private final String resumePostUri; private final String suspendPostUri; + public final String rewindPostUri; /** * Creates a {@link HttpManagementPayload} to manage orchestration instances @@ -38,6 +39,7 @@ public HttpManagementPayload( this.terminatePostUri = instanceStatusURL + "/terminate?reason={text}&" + requiredQueryStringParameters; this.resumePostUri = instanceStatusURL + "/resume?reason={text}&" + requiredQueryStringParameters; this.suspendPostUri = instanceStatusURL + "/suspend?reason={text}&" + requiredQueryStringParameters; + this.rewindPostUri = instanceStatusURL + "/rewind?reason={text}&" + requiredQueryStringParameters; } /** diff --git a/samples-azure-functions/extensions.csproj b/samples-azure-functions/extensions.csproj deleted file mode 100644 index 62b5a6c..0000000 --- a/samples-azure-functions/extensions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - net6.0 - - ** - - - - - - - diff --git a/samples-azure-functions/host.json b/samples-azure-functions/host.json index 097ed03..9d59145 100644 --- a/samples-azure-functions/host.json +++ b/samples-azure-functions/host.json @@ -15,5 +15,9 @@ "durableTask": { "hubName": "DFJavaSmokeTest" } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" } } \ No newline at end of file diff --git a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java index 70024a6..8ac39bb 100644 --- a/samples-azure-functions/src/main/java/com/functions/RewindInstance.java +++ b/samples-azure-functions/src/main/java/com/functions/RewindInstance.java @@ -92,4 +92,4 @@ public static HttpResponseMessage resetApproval( approvalFlag = 0; return request.createResponseBuilder(HttpStatus.OK).body(approvalFlag).build(); } -} +} \ No newline at end of file diff --git a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java index 50cd533..549ef44 100644 --- a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java +++ b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java @@ -21,6 +21,7 @@ @Tag("e2e") public class EndToEndTests { + private static final String hostHealthPingUrl = "/admin/host/ping"; private static final String startOrchestrationUrl = "/api/StartOrchestration"; private static final String startApprovalWorkflowUrl = "/api/ApprovalWorkflowOrchestration"; @@ -30,8 +31,7 @@ public class EndToEndTests { @Order(1) @Test public void setupHost() { - String hostHealthPingPath = "/admin/host/ping"; - post(hostHealthPingPath).then().statusCode(200); + post(hostHealthPingUrl).then().statusCode(200); } @ParameterizedTest @@ -81,15 +81,6 @@ public void retryTestSuccess() throws InterruptedException { String statusQueryGetUri = jsonPath.get("statusQueryGetUri"); boolean pass = pollingCheck(statusQueryGetUri, "Completed", null, Duration.ofSeconds(10)); assertTrue(pass); - String runtimeStatus = null; - for (int i = 0; i < 15; i++) { - Response statusResponse = get(statusQueryGetUri); - runtimeStatus = statusResponse.jsonPath().get("runtimeStatus"); - if (!"Completed".equals(runtimeStatus)) { - Thread.sleep(1000); - } else break; - } - assertEquals("Completed", runtimeStatus); } @Test @@ -286,5 +277,4 @@ public void testRewindInstanceHttpAPI() throws InterruptedException { // Reset approval for other test cases post(resetApprovalUrl); } -} - +} \ No newline at end of file