Skip to content
Merged
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## v1.2.0
* Add `thenAccept` and `thenApply` to `Task` interface ([#148](https://github.com/microsoft/durabletask-java/pull/148))

* Add `thenAccept` and `thenApply` to `Task` interface ([#148](https://github.com/microsoft/durabletask-java/pull/148))
* Support Suspend and Resume Client APIs ([#151](https://github.com/microsoft/durabletask-java/pull/151))
* Support restartInstance and pass restartPostUri in HttpManagementPayload ([#108](https://github.com/microsoft/durabletask-java/issues/108))

## v1.1.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class HttpManagementPayload {
private final String sendEventPostUri;
private final String statusQueryGetUri;
private final String terminatePostUri;
private final String resumePostUri;
private final String suspendPostUri;

/**
* Creates a {@link HttpManagementPayload} to manage orchestration instances
Expand All @@ -34,6 +36,8 @@ public HttpManagementPayload(
this.sendEventPostUri = instanceStatusURL + "/raiseEvent/{eventName}?" + requiredQueryStringParameters;
this.statusQueryGetUri = instanceStatusURL + "?" + requiredQueryStringParameters;
this.terminatePostUri = instanceStatusURL + "/terminate?reason={text}&" + requiredQueryStringParameters;
this.resumePostUri = instanceStatusURL + "/resume?reason={text}&" + requiredQueryStringParameters;
this.suspendPostUri = instanceStatusURL + "/suspend?reason={text}&" + requiredQueryStringParameters;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,33 +292,33 @@ public abstract OrchestrationMetadata waitForInstanceCompletion(
*/
public abstract String restartInstance(String instanceId, boolean restartWithNewInstanceId);

// /**
// * Suspends a running orchestration instance.
// * @param instanceId the ID of the orchestration instance to suspend
// */
// public void suspendInstance (String instanceId) {
// this.suspendInstance(instanceId, null);
// }
//
// /**
// * Resumes a running orchestration instance.
// * @param instanceId the ID of the orchestration instance to resume
// */
// public void resumeInstance(String instanceId) {
// this.resumeInstance(instanceId, null);
// }
//
// /**
// * Suspends a running orchestration instance.
// * @param instanceId the ID of the orchestration instance to suspend
// * @param reason the reason for suspending the orchestration instance
// */
// public abstract void suspendInstance(String instanceId, @Nullable String reason);
//
// /**
// * Resumes a running orchestration instance.
// * @param instanceId the ID of the orchestration instance to resume
// * @param reason the reason for resuming the orchestration instance
// */
// public abstract void resumeInstance(String instanceId, @Nullable String reason);
/**
* Suspends a running orchestration instance.
* @param instanceId the ID of the orchestration instance to suspend
*/
public void suspendInstance (String instanceId) {
this.suspendInstance(instanceId, null);
}

/**
* Resumes a running orchestration instance.
* @param instanceId the ID of the orchestration instance to resume
*/
public void resumeInstance(String instanceId) {
this.resumeInstance(instanceId, null);
}

/**
* Suspends a running orchestration instance.
* @param instanceId the ID of the orchestration instance to suspend
* @param reason the reason for suspending the orchestration instance
*/
public abstract void suspendInstance(String instanceId, @Nullable String reason);

/**
* Resumes a running orchestration instance.
* @param instanceId the ID of the orchestration instance to resume
* @param reason the reason for resuming the orchestration instance
*/
public abstract void resumeInstance(String instanceId, @Nullable String reason);
}
Original file line number Diff line number Diff line change
Expand Up @@ -283,25 +283,25 @@ public PurgeResult purgeInstances(PurgeInstanceCriteria purgeInstanceCriteria) t
}
}

// @Override
// public void suspendInstance(String instanceId, @Nullable String reason) {
// SuspendRequest.Builder suspendRequestBuilder = SuspendRequest.newBuilder();
// suspendRequestBuilder.setInstanceId(instanceId);
// if (reason != null) {
// suspendRequestBuilder.setReason(StringValue.of(reason));
// }
// this.sidecarClient.suspendInstance(suspendRequestBuilder.build());
// }
//
// @Override
// public void resumeInstance(String instanceId, @Nullable String reason) {
// ResumeRequest.Builder resumeRequestBuilder = ResumeRequest.newBuilder();
// resumeRequestBuilder.setInstanceId(instanceId);
// if (reason != null) {
// resumeRequestBuilder.setReason(StringValue.of(reason));
// }
// this.sidecarClient.resumeInstance(resumeRequestBuilder.build());
// }
@Override
public void suspendInstance(String instanceId, @Nullable String reason) {
SuspendRequest.Builder suspendRequestBuilder = SuspendRequest.newBuilder();
suspendRequestBuilder.setInstanceId(instanceId);
if (reason != null) {
suspendRequestBuilder.setReason(StringValue.of(reason));
}
this.sidecarClient.suspendInstance(suspendRequestBuilder.build());
}

@Override
public void resumeInstance(String instanceId, @Nullable String reason) {
ResumeRequest.Builder resumeRequestBuilder = ResumeRequest.newBuilder();
resumeRequestBuilder.setInstanceId(instanceId);
if (reason != null) {
resumeRequestBuilder.setReason(StringValue.of(reason));
}
this.sidecarClient.resumeInstance(resumeRequestBuilder.build());
}

@Override
public String restartInstance(String instanceId, boolean restartWithNewInstanceId) {
Expand Down
130 changes: 65 additions & 65 deletions client/src/test/java/com/microsoft/durabletask/IntegrationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -457,71 +457,71 @@ void restartOrchestrationThrowsException() {

}

// @Test
// void suspendResumeOrchestration() throws TimeoutException, InterruptedException {
// final String orchestratorName = "suspend";
// final String eventName = "MyEvent";
// final String eventPayload = "testPayload";
// final Duration suspendTimeout = Duration.ofSeconds(5);
//
// DurableTaskGrpcWorker worker = this.createWorkerBuilder()
// .addOrchestrator(orchestratorName, ctx -> {
// String payload = ctx.waitForExternalEvent(eventName, String.class).await();
// ctx.complete(payload);
// })
// .buildAndStart();
//
// DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
// try (worker; client) {
// String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName);
// client.suspendInstance(instanceId);
// OrchestrationMetadata instance = client.waitForInstanceStart(instanceId, defaultTimeout);
// assertNotNull(instance);
// assertEquals(OrchestrationRuntimeStatus.SUSPENDED, instance.getRuntimeStatus());
//
// client.raiseEvent(instanceId, eventName, eventPayload);
//
// assertThrows(
// TimeoutException.class,
// () -> client.waitForInstanceCompletion(instanceId, suspendTimeout, false),
// "Expected to throw TimeoutException, but it didn't"
// );
//
// String resumeReason = "Resume for testing.";
// client.resumeInstance(instanceId, resumeReason);
// instance = client.waitForInstanceCompletion(instanceId, defaultTimeout, true);
// assertNotNull(instance);
// assertEquals(instanceId, instance.getInstanceId());
// assertEquals(eventPayload, instance.readOutputAs(String.class));
// assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus());
// }
// }
//
// @Test
// void terminateSuspendOrchestration() throws TimeoutException, InterruptedException {
// final String orchestratorName = "suspendResume";
// final String eventName = "MyEvent";
// final String eventPayload = "testPayload";
//
// DurableTaskGrpcWorker worker = this.createWorkerBuilder()
// .addOrchestrator(orchestratorName, ctx -> {
// String payload = ctx.waitForExternalEvent(eventName, String.class).await();
// ctx.complete(payload);
// })
// .buildAndStart();
//
// DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
// try (worker; client) {
// String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName);
// String suspendReason = "Suspend for testing.";
// client.suspendInstance(instanceId, suspendReason);
// client.terminate(instanceId, null);
// OrchestrationMetadata instance = client.waitForInstanceCompletion(instanceId, defaultTimeout, false);
// assertNotNull(instance);
// assertEquals(instanceId, instance.getInstanceId());
// assertEquals(OrchestrationRuntimeStatus.TERMINATED, instance.getRuntimeStatus());
// }
// }
@Test
void suspendResumeOrchestration() throws TimeoutException, InterruptedException {
final String orchestratorName = "suspend";
final String eventName = "MyEvent";
final String eventPayload = "testPayload";
final Duration suspendTimeout = Duration.ofSeconds(5);

DurableTaskGrpcWorker worker = this.createWorkerBuilder()
.addOrchestrator(orchestratorName, ctx -> {
String payload = ctx.waitForExternalEvent(eventName, String.class).await();
ctx.complete(payload);
})
.buildAndStart();

DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
try (worker; client) {
String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName);
client.suspendInstance(instanceId);
OrchestrationMetadata instance = client.waitForInstanceStart(instanceId, defaultTimeout);
assertNotNull(instance);
assertEquals(OrchestrationRuntimeStatus.SUSPENDED, instance.getRuntimeStatus());

client.raiseEvent(instanceId, eventName, eventPayload);

assertThrows(
TimeoutException.class,
() -> client.waitForInstanceCompletion(instanceId, suspendTimeout, false),
"Expected to throw TimeoutException, but it didn't"
);

String resumeReason = "Resume for testing.";
client.resumeInstance(instanceId, resumeReason);
instance = client.waitForInstanceCompletion(instanceId, defaultTimeout, true);
assertNotNull(instance);
assertEquals(instanceId, instance.getInstanceId());
assertEquals(eventPayload, instance.readOutputAs(String.class));
assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus());
}
}

@Test
void terminateSuspendOrchestration() throws TimeoutException, InterruptedException {
final String orchestratorName = "suspendResume";
final String eventName = "MyEvent";
final String eventPayload = "testPayload";

DurableTaskGrpcWorker worker = this.createWorkerBuilder()
.addOrchestrator(orchestratorName, ctx -> {
String payload = ctx.waitForExternalEvent(eventName, String.class).await();
ctx.complete(payload);
})
.buildAndStart();

DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
try (worker; client) {
String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName);
String suspendReason = "Suspend for testing.";
client.suspendInstance(instanceId, suspendReason);
client.terminate(instanceId, null);
OrchestrationMetadata instance = client.waitForInstanceCompletion(instanceId, defaultTimeout, false);
assertNotNull(instance);
assertEquals(instanceId, instance.getInstanceId());
assertEquals(OrchestrationRuntimeStatus.TERMINATED, instance.getRuntimeStatus());
}
}

@Test
void activityFanOut() throws IOException, TimeoutException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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.DurableClientContext;
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;

import java.util.Optional;

public class ResumeSuspend {

@FunctionName("StartResumeSuspendOrchestration")
public HttpResponseMessage startResumeSuspendOrchestration(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");

DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("ResumeSuspend");
context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId);
return durableContext.createCheckStatusResponse(request, instanceId);
}

@FunctionName("ResumeSuspend")
public void resumeSuspend(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
ctx.waitForExternalEvent("test").await();
return;
}
}
Loading