You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/10/21 13:57:56 UTC
[brooklyn-server] 04/11: REST API to run workflow, and tidy workflow submission metadata
This is an automated email from the ASF dual-hosted git repository.
heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit ccf3cc8e65b19611702dbfc780b1b0a441ae7686
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Thu Oct 20 15:51:37 2022 +0100
REST API to run workflow, and tidy workflow submission metadata
---
.../core/workflow/WorkflowReplayUtils.java | 10 ++++++-
.../org/apache/brooklyn/rest/api/EntityApi.java | 31 +++++++++++++++++++
.../brooklyn/rest/resources/EntityResource.java | 35 ++++++++++++++++++++++
3 files changed, 75 insertions(+), 1 deletion(-)
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowReplayUtils.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowReplayUtils.java
index dade3d3bdf..579bc1d96a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowReplayUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowReplayUtils.java
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.core.workflow;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.collect.Iterables;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.util.core.task.DynamicTasks;
@@ -48,9 +49,11 @@ public class WorkflowReplayUtils {
// - replays others
}
+ @JsonInclude(JsonInclude.Include.NON_NULL)
public static class WorkflowReplayRecord {
String taskId;
String reasonForReplay;
+ String submittedByTaskId;
long submitTimeUtc;
long startTimeUtc;
long endTimeUtc;
@@ -72,6 +75,7 @@ public class WorkflowReplayUtils {
log.warn("Mismatch in workflow replays for "+ctx+": "+ctx.replayCurrent +" vs "+task);
return;
}
+ ctx.replayCurrent.submittedByTaskId = task.getSubmittedByTaskId();
ctx.replayCurrent.submitTimeUtc = task.getSubmitTimeUtc();
ctx.replayCurrent.startTimeUtc = task.getStartTimeUtc();
ctx.replayCurrent.endTimeUtc = task.getEndTimeUtc();
@@ -85,7 +89,11 @@ public class WorkflowReplayUtils {
ctx.replayCurrent.result = Exceptions.collapseTextInContext(t, task);
}
} else {
- if (ctx.replayCurrent.endTimeUtc <= 0) ctx.replayCurrent.endTimeUtc = System.currentTimeMillis();
+ // when forcing end, we are invoked _by_ the task so we fake the completion information
+ if (ctx.replayCurrent.endTimeUtc <= 0) {
+ ctx.replayCurrent.endTimeUtc = System.currentTimeMillis();
+ ctx.replayCurrent.status = forceEndSuccessOrError ? "Completed" : "Failed";
+ }
ctx.replayCurrent.isError = !forceEndSuccessOrError;
ctx.replayCurrent.result = result;
}
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
index fe05711937..9ad2abfb0a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
@@ -453,6 +453,37 @@ public interface EntityApi {
@ApiParam(value = "Workflow ID", required = true)
@PathParam("workflowId") String workflowId);
+ @POST
+ @ApiOperation(value = "Run a workflow on this entity from a YAML workflow spec",
+ response = org.apache.brooklyn.rest.domain.TaskSummary.class)
+ @Consumes({"application/x-yaml",
+ // per addChildren
+ "text/yaml", "text/x-yaml", "application/yaml", MediaType.APPLICATION_JSON})
+ @Path("/{entity}/workflows")
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Accepted"),
+ @ApiResponse(code = 400, message = "Bad Request"),
+ @ApiResponse(code = 401, message = "Unauthorized"),
+ @ApiResponse(code = 404, message = "Application or entity missing"),
+ @ApiResponse(code = 500, message = "Internal Server Error")
+ })
+ public Response runWorkflow(
+ @PathParam("application") final String application,
+ @PathParam("entity") final String entity,
+
+ @ApiParam(name = "timeout", value = "Delay before server should respond with incomplete activity task, rather than completed task: " +
+ "'never' means block until complete; " +
+ "'0' means return task immediately; " +
+ "and e.g. '20ms' (the default) will wait 20ms for completed task information to be available",
+ required = false, defaultValue = "20ms")
+ @QueryParam("timeout") final String timeout,
+
+ @ApiParam(
+ name = "workflowSpec",
+ value = "Workflow spec in YAML (including 'steps' root element with a list of steps)",
+ required = true)
+ String yaml);
+
@POST
@Path("/{entity}/workflows/{workflowId}/replay/from/{step}")
@ApiOperation(value = "Replays a workflow from the given step, or 'start' to restart or 'end' to resume from last replayable point; the workflow will rollback to the previous replay point unless forced; returns the task ID of the replay")
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
index fb6f5e73de..84495ea37b 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
@@ -22,6 +22,9 @@ import static javax.ws.rs.core.Response.created;
import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.Response.Status.ACCEPTED;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
@@ -46,8 +49,10 @@ import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
import org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
+import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep;
import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors;
import org.apache.brooklyn.rest.api.EntityApi;
import org.apache.brooklyn.rest.domain.*;
@@ -59,10 +64,13 @@ import org.apache.brooklyn.rest.transform.TaskTransformer;
import org.apache.brooklyn.rest.util.EntityRelationUtils;
import org.apache.brooklyn.rest.util.WebResourceUtils;
import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.ClassLoaderUtils;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.ClassLoadingContext;
+import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -400,4 +408,31 @@ public class EntityResource extends AbstractBrooklynRestResource implements Enti
return (String) WebResourceUtils.getValueForDisplay(mapper(), t.getId(), true, true);
}
+ @Override
+ public Response runWorkflow(String applicationToken, String entityToken, String timeoutS, String yaml) {
+ final Entity target = brooklyn().getEntity(applicationToken, entityToken);
+ // TODO new entitlement, here and above
+ if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, target)) {
+ throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+ Entitlements.getEntitlementContext().user(), entityToken);
+ }
+ CustomWorkflowStep workflow;
+ try {
+ workflow = BeanWithTypeUtils.newYamlMapper(mgmt(), true, RegisteredTypes.getClassLoadingContext(target), true)
+ .readerFor(CustomWorkflowStep.class).readValue(yaml);
+ } catch (JsonProcessingException e) {
+ return ApiError.of(e).asBadRequestResponseJson();
+ }
+
+ WorkflowExecutionContext execution = workflow.newWorkflowExecution(target,
+ Strings.firstNonBlank(workflow.getName(), workflow.getId(), "API workflow invocation"), null);
+
+ Task<Object> task = Entities.submit(target, execution.getTask(true).get());
+ task.blockUntilEnded(timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS));
+
+ URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), EntityApi.class, "getWorkflow")
+ .build(target.getApplicationId(), target.getId(), execution.getWorkflowId());
+ ResponseBuilder response = created(ref);
+ return response.entity(TaskTransformer.taskSummary(task, ui.getBaseUriBuilder())).build();
+ }
}