Skip to content

Commit

Permalink
Add suspend/resume tests (#3028)
Browse files Browse the repository at this point in the history
* Add suspend/resume tests
  • Loading branch information
andystaples authored Feb 6, 2025
1 parent c838aa0 commit 76eea66
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 199 deletions.
109 changes: 54 additions & 55 deletions test/e2e/Apps/BasicDotNetIsolated/HelloCities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,73 @@
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.Durable.Tests.E2E
namespace Microsoft.Azure.Durable.Tests.E2E;

public static class HelloCities
{
public static class HelloCities
[Function(nameof(HelloCities))]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
[Function(nameof(HelloCities))]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(HelloCities));
logger.LogInformation("Saying hello.");
var outputs = new List<string>();
ILogger logger = context.CreateReplaySafeLogger(nameof(HelloCities));
logger.LogInformation("Saying hello.");
var outputs = new List<string>();

// Replace name and input with values relevant for your Durable Functions Activity
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"));
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "London"));
// Replace name and input with values relevant for your Durable Functions Activity
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"));
outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "London"));

// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}

[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("SayHello");
logger.LogInformation("Saying hello to {name}.", name);
return $"Hello {name}!";
}
[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("SayHello");
logger.LogInformation("Saying hello to {name}.", name);
return $"Hello {name}!";
}

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

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

logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
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);
}
// 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("HelloCities_HttpStart_Scheduled")]
public static async Task<HttpResponseData> HttpStartScheduled(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
DateTime scheduledStartTime)
{
ILogger logger = executionContext.GetLogger("HelloCities_HttpStart");
[Function("HelloCities_HttpStart_Scheduled")]
public static async Task<HttpResponseData> HttpStartScheduled(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
DateTime scheduledStartTime)
{
ILogger logger = executionContext.GetLogger("HelloCities_HttpStart");

var startOptions = new StartOrchestrationOptions(StartAt: scheduledStartTime);
var startOptions = new StartOrchestrationOptions(StartAt: scheduledStartTime);

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

logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
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);
}
// 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);
}
}
1 change: 1 addition & 0 deletions test/e2e/Apps/BasicDotNetIsolated/OrchestrationQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.DurableTask.Client;

namespace Microsoft.Azure.Durable.Tests.E2E;

public static class OrchestrationQueryFunctions
{
[Function(nameof(GetAllInstances))]
Expand Down
65 changes: 32 additions & 33 deletions test/e2e/Apps/BasicDotNetIsolated/PurgeOrchestrationHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,43 @@
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.Durable.Tests.E2E
namespace Microsoft.Azure.Durable.Tests.E2E;

public static class PurgeOrchestrationHistory
{
public static class PurgeOrchestrationHistory
[Function(nameof(PurgeOrchestrationHistory))]
public static async Task<HttpResponseData> PurgeHistory(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
DateTime? purgeStartTime=null,
DateTime? purgeEndTime=null)
{
[Function(nameof(PurgeOrchestrationHistory))]
public static async Task<HttpResponseData> PurgeHistory(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
DateTime? purgeStartTime=null,
DateTime? purgeEndTime=null)
{
ILogger logger = executionContext.GetLogger("HelloCities_HttpStart");
ILogger logger = executionContext.GetLogger("HelloCities_HttpStart");

logger.LogInformation("Starting purge all instance history");
try
{
var requestPurgeResult = await client.PurgeAllInstancesAsync(new PurgeInstancesFilter(purgeStartTime, purgeEndTime, new List<OrchestrationRuntimeStatus>{
OrchestrationRuntimeStatus.Completed,
OrchestrationRuntimeStatus.Failed,
OrchestrationRuntimeStatus.Terminated
}));
logger.LogInformation("Starting purge all instance history");
try
{
var requestPurgeResult = await client.PurgeAllInstancesAsync(new PurgeInstancesFilter(purgeStartTime, purgeEndTime, new List<OrchestrationRuntimeStatus>{
OrchestrationRuntimeStatus.Completed,
OrchestrationRuntimeStatus.Failed,
OrchestrationRuntimeStatus.Terminated
}));

logger.LogInformation("Finished purge all instance history");
logger.LogInformation("Finished purge all instance history");

var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain");
await response.WriteStringAsync($"Purged {requestPurgeResult.PurgedInstanceCount} records");
return response;
}
catch (RpcException ex)
{
logger.LogError(ex, "Failed to purge all instance history");
var response = req.CreateResponse(HttpStatusCode.InternalServerError);
response.Headers.Add("Content-Type", "text/plain");
await response.WriteStringAsync($"Failed to purge all instance history: {ex.Message}");
return response;
}
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain");
await response.WriteStringAsync($"Purged {requestPurgeResult.PurgedInstanceCount} records");
return response;
}
catch (RpcException ex)
{
logger.LogError(ex, "Failed to purge all instance history");
var response = req.CreateResponse(HttpStatusCode.InternalServerError);
response.Headers.Add("Content-Type", "text/plain");
await response.WriteStringAsync($"Failed to purge all instance history: {ex.Message}");
return response;
}
}
}
55 changes: 55 additions & 0 deletions test/e2e/Apps/BasicDotNetIsolated/SuspendResumeOrchestration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.Client;

namespace Microsoft.Azure.Durable.Tests.E2E;

public static class SuspendResumeOrchestration
{
[Function("SuspendInstance")]
public static async Task<HttpResponseData> Suspend(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
string instanceId)
{
string suspendReason = "Suspending the instance for test.";
try
{
await client.SuspendInstanceAsync(instanceId, suspendReason);
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;
}
}

[Function("ResumeInstance")]
public static async Task<HttpResponseData> Resume(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
string instanceId)
{
string resumeReason = "Resuming the instance for test.";
try
{
await client.ResumeInstanceAsync(instanceId, resumeReason);
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;
}
}
}
109 changes: 54 additions & 55 deletions test/e2e/Apps/BasicDotNetIsolated/TerminateOrchestration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,71 @@
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.Durable.Tests.E2E
namespace Microsoft.Azure.Durable.Tests.E2E;

public static class LongRunningOrchestration
{
public static class LongRunningOrchestration
[Function(nameof(LongRunningOrchestrator))]
public static async Task<List<string>> LongRunningOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
[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++)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(HelloCities));
logger.LogInformation("Starting long-running orchestration.");
var outputs = new List<string>();
outputs.Add(await context.CallActivityAsync<string>(nameof(SimulatedWorkActivity), 100));
}

// 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;
}

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(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("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));

string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(LongRunningOrchestrator));
logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);

return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

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);
}

[Function("TerminateInstance")]
public static async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
string instanceId)
catch (RpcException ex)
{
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;
}
var response = req.CreateResponse(HttpStatusCode.BadRequest);
response.Headers.Add("Content-Type", "text/plain");
await response.WriteStringAsync(ex.Message);
return response;
}
}
}
Loading

0 comments on commit 76eea66

Please sign in to comment.