-
Notifications
You must be signed in to change notification settings - Fork 273
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f3694e1
Add test for orchestrator termination
andystaples f1b0b25
Minor cleanup
andystaples f6a9665
Use async/await for async operations
andystaples 73b8161
Bugfix TerminateOrchestratorTests async/await
andystaples 35215cc
Bugfix async/await
andystaples a47a008
Add additional scenarios
andystaples ac9f18a
Implement PR suggestions
andystaples ce30a03
PR Suggestions
andystaples cc3e76a
Final PR comment
andystaples File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
test/e2e/Apps/BasicDotNetIsolated/TerminateOrchestration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// 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 Grpc.Core; | ||
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"); | ||
|
||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync( | ||
nameof(LongRunningOrchestrator)); | ||
|
||
logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId); | ||
|
||
return await client.CreateCheckStatusResponseAsync(req, instanceId); | ||
} | ||
|
||
[Function("TerminateInstance")] | ||
public static async Task<HttpResponseData> Run( | ||
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, | ||
[DurableClient] DurableTaskClient client, | ||
string instanceId) | ||
{ | ||
string reason = "Long-running orchestration was terminated early."; | ||
try | ||
{ | ||
await client.TerminateInstanceAsync(instanceId, reason); | ||
return req.CreateResponse(HttpStatusCode.OK); | ||
} | ||
catch (RpcException ex) | ||
{ | ||
var response = req.CreateResponse(HttpStatusCode.BadRequest); | ||
response.Headers.Add("Content-Type", "text/plain"); | ||
await response.WriteStringAsync(ex.Message); | ||
return response; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// 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; | ||
} | ||
|
||
|
||
[Fact] | ||
public async Task TerminateRunningOrchestration_ShouldSucceed() | ||
{ | ||
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("LongOrchestrator_HttpStart", ""); | ||
|
||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); | ||
string instanceId = await DurableHelpers.ParseInstanceIdAsync(response); | ||
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response); | ||
|
||
Thread.Sleep(1000); | ||
|
||
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Running", orchestrationDetails.RuntimeStatus); | ||
|
||
using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}"); | ||
await AssertTerminateRequestSucceedsAsync(terminateResponse); | ||
|
||
Thread.Sleep(1000); | ||
|
||
orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Terminated", orchestrationDetails.RuntimeStatus); | ||
} | ||
|
||
|
||
[Fact(Skip = "Will enable when https://github.com/Azure/azure-functions-durable-extension/issues/3025 is fixed")] | ||
public async Task TerminateScheduledOrchestration_ShouldSucceed() | ||
{ | ||
DateTime scheduledStartTime = DateTime.UtcNow + TimeSpan.FromMinutes(1); | ||
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("HelloCities_HttpStart_Scheduled", $"?scheduledStartTime={scheduledStartTime.ToString("o")}"); | ||
|
||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); | ||
string instanceId = await DurableHelpers.ParseInstanceIdAsync(response); | ||
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response); | ||
|
||
Thread.Sleep(1000); | ||
|
||
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Pending", orchestrationDetails.RuntimeStatus); | ||
|
||
using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}"); | ||
await AssertTerminateRequestSucceedsAsync(terminateResponse); | ||
|
||
Thread.Sleep(1000); | ||
|
||
orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Terminated", orchestrationDetails.RuntimeStatus); | ||
} | ||
|
||
|
||
[Fact] | ||
public async Task TerminateTerminatedOrchestration_ShouldFail() | ||
{ | ||
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("LongOrchestrator_HttpStart", ""); | ||
|
||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); | ||
string instanceId = await DurableHelpers.ParseInstanceIdAsync(response); | ||
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response); | ||
|
||
Thread.Sleep(1000); | ||
|
||
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Running", orchestrationDetails.RuntimeStatus); | ||
|
||
using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}"); | ||
await AssertTerminateRequestSucceedsAsync(terminateResponse); | ||
|
||
Thread.Sleep(1000); | ||
using HttpResponseMessage terminateAgainResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}"); | ||
await AssertTerminateRequestFailsAsync(terminateAgainResponse); | ||
|
||
// Give some time for Core Tools to write logs out | ||
Thread.Sleep(500); | ||
|
||
Assert.Contains(_fixture.TestLogs.CoreToolsLogs, x => x.Contains("Cannot terminate orchestration instance in the Terminated state.") && | ||
x.Contains(instanceId)); | ||
|
||
orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Terminated", orchestrationDetails.RuntimeStatus); | ||
} | ||
|
||
|
||
[Fact] | ||
public async Task TerminateCompletedOrchestration_ShouldFail() | ||
{ | ||
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("HelloCities_HttpStart", ""); | ||
|
||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); | ||
string instanceId = await DurableHelpers.ParseInstanceIdAsync(response); | ||
string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response); | ||
|
||
Thread.Sleep(1000); | ||
|
||
var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); | ||
Assert.Equal("Completed", orchestrationDetails.RuntimeStatus); | ||
|
||
using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={instanceId}"); | ||
await AssertTerminateRequestFailsAsync(terminateResponse); | ||
|
||
// Give some time for Core Tools to write logs out | ||
Thread.Sleep(500); | ||
|
||
Assert.Contains(_fixture.TestLogs.CoreToolsLogs, x => x.Contains("Cannot terminate orchestration instance in the Completed state.") && | ||
x.Contains(instanceId)); | ||
} | ||
|
||
[Fact] | ||
public async Task TerminateNonExistantOrchestration_ShouldFail() | ||
{ | ||
using HttpResponseMessage terminateResponse = await HttpHelpers.InvokeHttpTrigger("TerminateInstance", $"?instanceId={Guid.NewGuid().ToString()}"); | ||
await AssertTerminateRequestFailsAsync(terminateResponse); | ||
} | ||
|
||
private static async Task AssertTerminateRequestFailsAsync(HttpResponseMessage terminateResponse) | ||
{ | ||
Assert.Equal(HttpStatusCode.BadRequest, terminateResponse.StatusCode); | ||
|
||
string? terminateResponseMessage = await terminateResponse.Content.ReadAsStringAsync(); | ||
Assert.NotNull(terminateResponseMessage); | ||
// Unclear error message - see https://github.com/Azure/azure-functions-durable-extension/issues/3027, will update this code when that bug is fixed | ||
Assert.Equal("Status(StatusCode=\"Unknown\", Detail=\"Exception was thrown by handler.\")", terminateResponseMessage); | ||
andystaples marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private static async Task AssertTerminateRequestSucceedsAsync(HttpResponseMessage terminateResponse) | ||
{ | ||
Assert.Equal(HttpStatusCode.OK, terminateResponse.StatusCode); | ||
|
||
string? terminateResponseMessage = await terminateResponse.Content.ReadAsStringAsync(); | ||
Assert.NotNull(terminateResponseMessage); | ||
Assert.Empty(terminateResponseMessage); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry that a large fan-out like this may make the test unstable on the low-powered CI machines due to the memory and I/O requirements. Do we need a fan-out at all or could we just use an orchestration that sleeps for a long time or waits for an external event?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a fan-out though, the activities are called sequentially. I agree, it's overkill though since most of the tests run in well under a minute, I can shorten it, the idea behind writing the orchestrator this way was to demonstrate what might cause an orchestration with problematic runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry, I misread the code. OK, I'm less worried about this then.