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