Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test for orchestrator termination #3023

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions test/e2e/Apps/BasicDotNetIsolated/TerminateOrchestration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.Durable.Tests.E2E
{
public static class LongRunningOrchestration
{
[Function(nameof(LongRunningOrchestrator))]
public static async Task<List<string>> LongRunningOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(HelloCities));
logger.LogInformation("Starting long-running orchestration.");
var outputs = new List<string>();

// Call our fake activity 100,000 times to simulate an orchestration that might run for >= 10,000s (2.7 hours)
for (int i = 0; i < 100000; i++)
{
outputs.Add(await context.CallActivityAsync<string>(nameof(SimulatedWorkActivity), 100));
}

return outputs;
}

[Function(nameof(SimulatedWorkActivity))]
public static string SimulatedWorkActivity([ActivityTrigger]int sleepMs, FunctionContext executionContext)
{
// Sleep the provided number of ms to simulate a long-running activity operation
ILogger logger = executionContext.GetLogger("SimulatedWorkActivity");
logger.LogInformation("Sleeping for {sleepMs}ms.", sleepMs);
Thread.Sleep(sleepMs);
return $"Slept for {sleepMs}ms.";
}

[Function("LongOrchestrator_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("LongOrchestrator_HttpStart");

// Function input comes from the request content.
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(LongRunningOrchestrator));

logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);

// Returns an HTTP 202 response with an instance management payload.
// See https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-http-api#start-orchestration
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

[Function("TerminateInstance")]
public static Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
string instanceId)
{
string reason = "Long-running orchestration was terminated early.";
return client.TerminateInstanceAsync(instanceId, reason);
}
}
}
29 changes: 20 additions & 9 deletions test/e2e/Tests/Helpers/DurableHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,39 @@ public OrchestrationStatusDetails(string statusQueryResponse)
}
}

internal static string ParseStatusQueryGetUri(HttpResponseMessage invocationStartResponse)
private static string TokenizeAndGetValueFromKeyAsString(string? json, string key)
{
string? responseString = invocationStartResponse.Content?.ReadAsStringAsync().Result;

if (string.IsNullOrEmpty(responseString))
if (string.IsNullOrEmpty(json))
{
return string.Empty;
}
JsonNode? responseJsonNode = JsonNode.Parse(responseString);
JsonNode? responseJsonNode = JsonNode.Parse(json);
if (responseJsonNode == null)
{
return string.Empty;
}

string? statusQueryGetUri = responseJsonNode["StatusQueryGetUri"]?.GetValue<string>();
string? statusQueryGetUri = responseJsonNode[key]?.GetValue<string>();
return statusQueryGetUri ?? string.Empty;
}
internal static OrchestrationStatusDetails GetRunningOrchestrationDetails(string statusQueryGetUri)

internal static async Task<string> ParseStatusQueryGetUri(HttpResponseMessage invocationStartResponse)
{
string? responseString = await invocationStartResponse.Content.ReadAsStringAsync();
return TokenizeAndGetValueFromKeyAsString(responseString, "StatusQueryGetUri");
}

internal static async Task<string> ParseInstanceId(HttpResponseMessage invocationStartResponse)
{
string? responseString = await invocationStartResponse.Content.ReadAsStringAsync();
return TokenizeAndGetValueFromKeyAsString(responseString, "Id");
}

internal static async Task<OrchestrationStatusDetails> GetRunningOrchestrationDetails(string statusQueryGetUri)
{
var statusQueryResponse = _httpClient.GetAsync(statusQueryGetUri);
var statusQueryResponse = await _httpClient.GetAsync(statusQueryGetUri);

string? statusQueryResponseString = statusQueryResponse.Result.Content.ReadAsStringAsync().Result;
string? statusQueryResponseString = statusQueryResponse.Content.ReadAsStringAsync().Result;

return new OrchestrationStatusDetails(statusQueryResponseString);
}
Expand Down
14 changes: 7 additions & 7 deletions test/e2e/Tests/Tests/HelloCitiesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public async Task HttpTriggerTests(string functionName, HttpStatusCode expectedS
string actualMessage = await response.Content.ReadAsStringAsync();

Assert.Equal(expectedStatusCode, response.StatusCode);
string statusQueryGetUri = DurableHelpers.ParseStatusQueryGetUri(response);
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUri(response);
Thread.Sleep(1000);
var orchestrationDetails = DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
Assert.Equal("Completed", orchestrationDetails.RuntimeStatus);
Assert.Contains(partialExpectedOutput, orchestrationDetails.Output);
}
Expand All @@ -61,28 +61,28 @@ public async Task ScheduledStartTests(string functionName, int startDelaySeconds
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName, urlQueryString);
string actualMessage = await response.Content.ReadAsStringAsync();

string statusQueryGetUri = DurableHelpers.ParseStatusQueryGetUri(response);
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUri(response);

Assert.Equal(expectedStatusCode, response.StatusCode);

var orchestrationDetails = DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
while (DateTime.UtcNow < scheduledStartTime + TimeSpan.FromSeconds(-1))
{
WriteOutput($"Test scheduled for {scheduledStartTime}, current time {DateTime.Now}");
orchestrationDetails = DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
Assert.Equal("Pending", orchestrationDetails.RuntimeStatus);
Thread.Sleep(1000);
}

// Give a small amount of time for the orchestration to complete, even if scheduled to run immediately
Thread.Sleep(3000);
WriteOutput($"Test scheduled for {scheduledStartTime}, current time {DateTime.Now}, looking for completed");
var finalOrchestrationDetails = DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
var finalOrchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
int retryAttempts = 0;
while (finalOrchestrationDetails.RuntimeStatus != "Completed" && retryAttempts < 10)
{
Thread.Sleep(1000);
finalOrchestrationDetails = DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
finalOrchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
retryAttempts++;
}
Assert.Equal("Completed", finalOrchestrationDetails.RuntimeStatus);
Expand Down
52 changes: 52 additions & 0 deletions test/e2e/Tests/Tests/TerminateOrchestratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Azure.Durable.Tests.DotnetIsolatedE2E;

[Collection(Constants.FunctionAppCollectionName)]
public class TerminateOrchestratorTests
{
private readonly FunctionAppFixture _fixture;
private readonly ITestOutputHelper _output;

public TerminateOrchestratorTests(FunctionAppFixture fixture, ITestOutputHelper testOutputHelper)
{
_fixture = fixture;
_fixture.TestLogs.UseTestLogger(testOutputHelper);
_output = testOutputHelper;
}


[Theory]
[InlineData("LongOrchestrator_HttpStart")]
public async Task TerminateRunningOrchestration_ShouldSucceed(string functionName)
{
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName, "");
string actualMessage = await response.Content.ReadAsStringAsync();

Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
string instanceId = await DurableHelpers.ParseInstanceId(response);
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUri(response);

Thread.Sleep(1000);

var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
Assert.Equal("Running", orchestrationDetails.RuntimeStatus);

using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}");
Assert.Equal(HttpStatusCode.OK, terminateResponse.StatusCode);

string? terminateResponseMessage = await terminateResponse.Content.ReadAsStringAsync();
Assert.NotNull(terminateResponseMessage);
Assert.Empty(terminateResponseMessage);

Thread.Sleep(1000);

orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetails(statusQueryGetUri);
Assert.Equal("Terminated", orchestrationDetails.RuntimeStatus);
}
}
Loading