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 <[email protected]> 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(); + } }
