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 ba15bed0f984c2542fcd08fd7e486073114ab5e9 Author: Alex Heneveld <[email protected]> AuthorDate: Fri May 31 15:12:26 2024 +0100 workflow flow control - labels, output, end semantics - add a label step to more easily set an id - support let output to set the output but without returning - goto end will go to the end of the containing list (parent context if inline subworkflow in shorthand) --- .../camp/brooklyn/WorkflowYamlRebindTest.java | 39 +- .../persisted-entity-with-workflow-next-fields.xml | 678 +++++++++++++++++++++ .../core/workflow/WorkflowExecutionContext.java | 47 +- .../WorkflowStepInstanceExecutionContext.java | 17 +- .../core/workflow/steps/CustomWorkflowStep.java | 46 +- .../core/workflow/steps/flow/IfWorkflowStep.java | 5 +- ...urnWorkflowStep.java => LabelWorkflowStep.java} | 37 +- .../workflow/steps/flow/ReturnWorkflowStep.java | 1 - .../core/workflow/steps/flow/SubWorkflowStep.java | 25 +- .../steps/variables/SetVariableWorkflowStep.java | 2 + .../brooklyn/core/workflow/WorkflowBasicTest.java | 2 + .../workflow/WorkflowParsingEdgeCasesTest.java | 72 --- .../workflow/WorkflowPersistReplayErrorsTest.java | 4 +- .../WorkflowSubIfAndCustomExtensionEdgeTest.java | 91 ++- karaf/init/src/main/resources/catalog.bom | 5 + 15 files changed, 924 insertions(+), 147 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlRebindTest.java index 697428ccec..4fc10f1639 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlRebindTest.java @@ -25,6 +25,7 @@ import java.util.Set; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; @@ -45,6 +46,8 @@ import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.workflow.WorkflowBasicTest; import org.apache.brooklyn.core.workflow.WorkflowEffector; +import org.apache.brooklyn.core.workflow.WorkflowExecutionContext; +import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors; import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; @@ -54,9 +57,11 @@ import org.apache.brooklyn.tasks.kubectl.ContainerWorkflowStep; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeMethod; @@ -194,7 +199,7 @@ public class WorkflowYamlRebindTest extends AbstractYamlRebindTest { } @Test - void testWorkflowSensorRebind() throws Exception { + public void testWorkflowSensorRebind() throws Exception { Entity app = createAndStartApplication( "services:", "- type: " + BasicEntity.class.getName(), @@ -242,7 +247,7 @@ public class WorkflowYamlRebindTest extends AbstractYamlRebindTest { } @Test - void testWorkflowSensorWithMutexRebind() throws Exception { + public void testWorkflowSensorWithMutexRebind() throws Exception { Entity app = createAndStartApplication( "services:", "- type: " + BasicEntity.class.getName(), @@ -295,4 +300,32 @@ public class WorkflowYamlRebindTest extends AbstractYamlRebindTest { Asserts.assertThat(tt, ts -> ts.stream().anyMatch(ti -> ti.getDisplayName().contains("Workflow for sensor"))); } -} + @Test + public void testRebindsHistoricNextIsReturnLocalWorkflow() throws Exception { + Application app; +// app = mgmt().getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); +// WorkflowExecutionContext wc1 = WorkflowBasicTest.runWorkflow(app, Strings.lines( +// "steps:", +// "- let x = 1", +// "- steps:", +// " - if ${x} == 1 then goto " + WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END, // used to not go to end of workflow +// " - let x = 2", +// " - goto end", +// " - let x = no1", +// "- steps:", +// " - return ${x}"), "test"); +// Asserts.assertEquals(wc1.getTask(true).get().getUnchecked(), "2"); + + ResourceUtils resourceUtils = ResourceUtils.create(this); + String persistedXmlForAbove = resourceUtils.getResourceAsString("classpath://org/apache/brooklyn/camp/brooklyn/persisted-entity-with-workflow-next-fields.xml"); + /* We used to store nextIsReturn and isLocalSubWorkflow; these are migrating, but we want to make sure persistence/rebind works. + * It is mapped to the right place. + */ + FileUtils.write(new File(mementoDir, "entities/zxg2xnpxur"), persistedXmlForAbove); + + app = rebind(); + WorkflowExecutionContext wc2 = WorkflowStatePersistenceViaSensors.get(mgmt()).getWorkflows(app).values().iterator().next(); + Asserts.assertEquals(wc2.getOutput(), "2"); + } + +} \ No newline at end of file diff --git a/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/persisted-entity-with-workflow-next-fields.xml b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/persisted-entity-with-workflow-next-fields.xml new file mode 100644 index 0000000000..e890505f4d --- /dev/null +++ b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/persisted-entity-with-workflow-next-fields.xml @@ -0,0 +1,678 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<entity> + <brooklynVersion>1.2.0-SNAPSHOT</brooklynVersion> + <type>org.apache.brooklyn.entity.stock.BasicApplicationImpl</type> + <id>zxg2xnpxur</id> + <displayName>Application (zxg2xnpxur)</displayName> + <searchPath class="ImmutableList"/> + <attributes> + <service.notUp.indicators> + <MutableMap> + <service.state>Application created but not yet started, at 2024-05-31 13:27:34.488</service.state> + </MutableMap> + </service.notUp.indicators> + <entity.id>zxg2xnpxur</entity.id> + <application.id>zxg2xnpxur</application.id> + <catalog.id> + <null/> + </catalog.id> + <service.isUp type="boolean">false</service.isUp> + <internals.brooklyn.workflow> + <MutableMap> + <wLJtGOKE> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + <name>test</name> + <entity>zxg2xnpxur</entity> + <status>SUCCESS</status> + <lastStatusChangeTime>2024-05-31T12:27:34.994Z</lastStatusChangeTime> + <stepsDefinition> + <string>let x = 1</string> + <map> + <steps> + <list> + <string>if ${x} == 1 then goto end</string> + <string>let x = 2</string> + <string>goto end</string> + <string>let x = no1</string> + </list> + </steps> + </map> + <map> + <steps> + <list> + <string>return ${x}</string> + </list> + </steps> + </map> + </stepsDefinition> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <output class="string">2</output> + <onError class="MutableList"/> + <workflowId>wLJtGOKE</workflowId> + <taskId>wLJtGOKE</taskId> + <retention> + <expiryResolved>parent</expiryResolved> + <softExpiryResolved>parent</softExpiryResolved> + </retention> + <replays class="MutableSet"> + <org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + <taskId>wLJtGOKE</taskId> + <reasonForReplay>initial run</reasonForReplay> + <submitTimeUtc>1717158454664</submitTimeUtc> + <startTimeUtc>1717158454664</startTimeUtc> + <endTimeUtc>1717158454994</endTimeUtc> + <status>Completed</status> + <isError>false</isError> + <result class="string">2</result> + </org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + </replays> + <replayableLastStep>-2</replayableLastStep> + <currentStepIndex>3</currentStepIndex> + <previousStepIndex>2</previousStepIndex> + <previousStepTaskId>cFQn0u52</previousStepTaskId> + <currentStepInstance> + <stepIndex>2</stepIndex> + <taskId>cFQn0u52</taskId> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <next class="string">end</next> + <nextIsReturn>true</nextIsReturn> + <isLocalSubworkflow>true</isLocalSubworkflow> + <stepState class="org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep$StepState"> + <wasList>false</wasList> + <reducing> + <x>2</x> + </reducing> + </stepState> + <subWorkflows class="MutableSet"> + <org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>Z13KmPUw</workflowId> + <workflowName>subworkflow (sub-workflow)</workflowName> + </org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + </subWorkflows> + <output class="string">2</output> + <otherMetadata class="MutableMap"/> + </currentStepInstance> + <oldStepInfo class="MutableMap"> + <entry> + <int>0</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context> + <stepIndex>0</stepIndex> + <taskId>bD5oq3Mc</taskId> + <input class="MutableMap"> + <variable> + <MutableMap> + <name>x</name> + </MutableMap> + </variable> + <value>1</value> + <interpolation__mode type="org.apache.brooklyn.core.workflow.steps.variables.SetVariableWorkflowStep$InterpolationMode">WORDS</interpolation__mode> + </input> + <inputResolved class="MutableMap"> + <variable> + <org.apache.brooklyn.core.workflow.steps.variables.TypedValueToSet> + <name>x</name> + </org.apache.brooklyn.core.workflow.steps.variables.TypedValueToSet> + </variable> + </inputResolved> + <subWorkflows class="MutableSet"/> + <otherMetadata class="MutableMap"/> + </context> + <workflowScratchUpdates class="MutableMap"> + <x>1</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>-1</int> + </previous> + <next class="MutableSet"> + <int>1</int> + </next> + <nextTaskId>pLDBANkQ</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <next class="MutableSet"> + <int>0</int> + </next> + <nextTaskId>bD5oq3Mc</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context> + <stepIndex>1</stepIndex> + <taskId>pLDBANkQ</taskId> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <isLocalSubworkflow>true</isLocalSubworkflow> + <stepState class="org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep$StepState"> + <wasList>false</wasList> + <reducing> + <x>1</x> + </reducing> + </stepState> + <subWorkflows class="MutableSet"> + <org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>VSXPiHV0</workflowId> + <workflowName>subworkflow (sub-workflow)</workflowName> + </org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + </subWorkflows> + <otherMetadata class="MutableMap"/> + </context> + <workflowScratchUpdates class="MutableMap"> + <target> + <null/> + </target> + <x>2</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>0</int> + </previous> + <next class="MutableSet"> + <int>2</int> + </next> + <previousTaskId>bD5oq3Mc</previousTaskId> + <nextTaskId>cFQn0u52</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context reference="../../../../currentStepInstance"/> + <workflowScratchUpdates class="MutableMap"> + <target> + <null/> + </target> + <x>2</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>1</int> + </previous> + <next class="MutableSet"> + <int>-2</int> + </next> + <previousTaskId>pLDBANkQ</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <previous class="MutableSet"> + <int>2</int> + </previous> + <previousTaskId>cFQn0u52</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + </oldStepInfo> + <retryRecords class="MutableMap"/> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + </wLJtGOKE> + <VSXPiHV0> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + <name>subworkflow (sub-workflow)</name> + <entity reference="../../../wLJtGOKE/org.apache.brooklyn.core.workflow.WorkflowExecutionContext/entity"/> + <status>SUCCESS</status> + <lastStatusChangeTime>2024-05-31T12:27:34.969Z</lastStatusChangeTime> + <parentTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>wLJtGOKE</workflowId> + <workflowName>test</workflowName> + </parentTag> + <stepsDefinition> + <string>if ${x} == 1 then goto end</string> + <string>let x = 2</string> + <string>goto end</string> + <string>let x = no1</string> + </stepsDefinition> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <onError class="MutableList"/> + <workflowId>VSXPiHV0</workflowId> + <taskId>VSXPiHV0</taskId> + <retention> + <expiryResolved>parent</expiryResolved> + <softExpiryResolved>parent</softExpiryResolved> + </retention> + <replays class="MutableSet"> + <org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + <taskId>VSXPiHV0</taskId> + <reasonForReplay>initial run</reasonForReplay> + <submittedByTaskId>pLDBANkQ</submittedByTaskId> + <submitTimeUtc>1717158454846</submitTimeUtc> + <startTimeUtc>1717158454846</startTimeUtc> + <endTimeUtc>1717158454969</endTimeUtc> + <status>Completed</status> + <isError>false</isError> + </org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + </replays> + <replayableLastStep>-2</replayableLastStep> + <currentStepIndex>4</currentStepIndex> + <previousStepIndex>2</previousStepIndex> + <previousStepTaskId>ZLsgFVKo</previousStepTaskId> + <currentStepInstance> + <stepIndex>2</stepIndex> + <taskId>ZLsgFVKo</taskId> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <next class="string">end</next> + <subWorkflows class="MutableSet"/> + <otherMetadata class="MutableMap"/> + </currentStepInstance> + <oldStepInfo class="MutableMap"> + <entry> + <int>0</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context> + <stepIndex>0</stepIndex> + <taskId>hG8llNey</taskId> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <isLocalSubworkflow>true</isLocalSubworkflow> + <stepState class="org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep$StepState"> + <wasList>false</wasList> + <reducing> + <x>1</x> + </reducing> + </stepState> + <subWorkflows class="MutableSet"> + <org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>S3LzIy3C</workflowId> + <workflowName>if (sub-workflow)</workflowName> + </org.apache.brooklyn.core.mgmt.BrooklynTaskTags_-WorkflowTaskTag> + </subWorkflows> + <otherMetadata class="MutableMap"/> + </context> + <workflowScratchUpdates class="MutableMap"> + <target> + <null/> + </target> + <x>1</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>-1</int> + </previous> + <next class="MutableSet"> + <int>1</int> + </next> + <nextTaskId>tMQjSI9g</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <workflowScratch class="MutableMap"> + <target> + <null/> + </target> + <x>1</x> + </workflowScratch> + <next class="MutableSet"> + <int>0</int> + </next> + <nextTaskId>hG8llNey</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context> + <stepIndex>1</stepIndex> + <taskId>tMQjSI9g</taskId> + <input class="MutableMap"> + <variable> + <MutableMap> + <name>x</name> + </MutableMap> + </variable> + <value>2</value> + <interpolation__mode type="org.apache.brooklyn.core.workflow.steps.variables.SetVariableWorkflowStep$InterpolationMode">WORDS</interpolation__mode> + </input> + <inputResolved class="MutableMap"> + <variable> + <org.apache.brooklyn.core.workflow.steps.variables.TypedValueToSet> + <name>x</name> + </org.apache.brooklyn.core.workflow.steps.variables.TypedValueToSet> + </variable> + </inputResolved> + <subWorkflows class="MutableSet"/> + <otherMetadata class="MutableMap"/> + </context> + <workflowScratchUpdates class="MutableMap"> + <x>2</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>0</int> + </previous> + <next class="MutableSet"> + <int>2</int> + </next> + <previousTaskId>hG8llNey</previousTaskId> + <nextTaskId>ZLsgFVKo</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context reference="../../../../currentStepInstance"/> + <previous class="MutableSet"> + <int>1</int> + </previous> + <next class="MutableSet"> + <int>-2</int> + </next> + <previousTaskId>tMQjSI9g</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <previous class="MutableSet"> + <int>2</int> + </previous> + <previousTaskId>ZLsgFVKo</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + </oldStepInfo> + <retryRecords class="MutableMap"/> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + </VSXPiHV0> + <S3LzIy3C> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + <name>if (sub-workflow)</name> + <entity reference="../../../wLJtGOKE/org.apache.brooklyn.core.workflow.WorkflowExecutionContext/entity"/> + <status>SUCCESS</status> + <lastStatusChangeTime>2024-05-31T12:27:34.952Z</lastStatusChangeTime> + <parentTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>VSXPiHV0</workflowId> + <workflowName>subworkflow (sub-workflow)</workflowName> + </parentTag> + <stepsDefinition> + <string>goto end</string> + </stepsDefinition> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <onError class="MutableList"/> + <workflowId>S3LzIy3C</workflowId> + <taskId>S3LzIy3C</taskId> + <retention> + <expiryResolved>parent</expiryResolved> + <softExpiryResolved>parent</softExpiryResolved> + </retention> + <replays class="MutableSet"> + <org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + <taskId>S3LzIy3C</taskId> + <reasonForReplay>initial run</reasonForReplay> + <submittedByTaskId>hG8llNey</submittedByTaskId> + <submitTimeUtc>1717158454948</submitTimeUtc> + <startTimeUtc>1717158454949</startTimeUtc> + <endTimeUtc>1717158454954</endTimeUtc> + <status>Completed</status> + <isError>false</isError> + </org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + </replays> + <replayableLastStep>-2</replayableLastStep> + <currentStepIndex>1</currentStepIndex> + <previousStepIndex>0</previousStepIndex> + <previousStepTaskId>KkS9BhqU</previousStepTaskId> + <currentStepInstance> + <stepIndex>0</stepIndex> + <taskId>KkS9BhqU</taskId> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <next class="string">end</next> + <subWorkflows class="MutableSet"/> + <otherMetadata class="MutableMap"/> + </currentStepInstance> + <oldStepInfo class="MutableMap"> + <entry> + <int>0</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context reference="../../../../currentStepInstance"/> + <workflowScratchUpdates class="MutableMap"> + <target> + <null/> + </target> + <x>1</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>-1</int> + </previous> + <next class="MutableSet"> + <int>-2</int> + </next> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <workflowScratch class="MutableMap"> + <target> + <null/> + </target> + <x>1</x> + </workflowScratch> + <next class="MutableSet"> + <int>0</int> + </next> + <nextTaskId>KkS9BhqU</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <previous class="MutableSet"> + <int>0</int> + </previous> + <previousTaskId>KkS9BhqU</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + </oldStepInfo> + <retryRecords class="MutableMap"/> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + </S3LzIy3C> + <Z13KmPUw> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + <name>subworkflow (sub-workflow)</name> + <entity reference="../../../wLJtGOKE/org.apache.brooklyn.core.workflow.WorkflowExecutionContext/entity"/> + <status>SUCCESS</status> + <lastStatusChangeTime>2024-05-31T12:27:34.991Z</lastStatusChangeTime> + <parentTag> + <applicationId>zxg2xnpxur</applicationId> + <entityId>zxg2xnpxur</entityId> + <workflowId>wLJtGOKE</workflowId> + <workflowName>test</workflowName> + </parentTag> + <stepsDefinition> + <string>return ${x}</string> + </stepsDefinition> + <input class="MutableMap"/> + <inputResolved class="MutableMap"/> + <output class="string">2</output> + <onError class="MutableList"/> + <workflowId>Z13KmPUw</workflowId> + <taskId>Z13KmPUw</taskId> + <retention> + <expiryResolved>parent</expiryResolved> + <softExpiryResolved>parent</softExpiryResolved> + </retention> + <replays class="MutableSet"> + <org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + <taskId>Z13KmPUw</taskId> + <reasonForReplay>initial run</reasonForReplay> + <submittedByTaskId>cFQn0u52</submittedByTaskId> + <submitTimeUtc>1717158454987</submitTimeUtc> + <startTimeUtc>1717158454987</startTimeUtc> + <endTimeUtc>1717158454991</endTimeUtc> + <status>Completed</status> + <isError>false</isError> + <result class="string">2</result> + </org.apache.brooklyn.core.workflow.WorkflowReplayUtils_-WorkflowReplayRecord> + </replays> + <replayableLastStep>-2</replayableLastStep> + <currentStepIndex>1</currentStepIndex> + <previousStepIndex>0</previousStepIndex> + <previousStepTaskId>i0CXCnCH</previousStepTaskId> + <currentStepInstance> + <stepIndex>0</stepIndex> + <taskId>i0CXCnCH</taskId> + <input class="MutableMap"> + <value>${x}</value> + </input> + <inputResolved class="MutableMap"> + <value>2</value> + </inputResolved> + <next class="string">end</next> + <nextIsReturn>true</nextIsReturn> + <subWorkflows class="MutableSet"/> + <output class="string">2</output> + <otherMetadata class="MutableMap"/> + </currentStepInstance> + <oldStepInfo class="MutableMap"> + <entry> + <int>0</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>1</countStarted> + <countCompleted>1</countCompleted> + <context reference="../../../../currentStepInstance"/> + <workflowScratchUpdates class="MutableMap"> + <target> + <null/> + </target> + <x>2</x> + </workflowScratchUpdates> + <previous class="MutableSet"> + <int>-1</int> + </previous> + <next class="MutableSet"> + <int>-2</int> + </next> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-1</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <workflowScratch class="MutableMap"> + <target> + <null/> + </target> + <x>2</x> + </workflowScratch> + <next class="MutableSet"> + <int>0</int> + </next> + <nextTaskId>i0CXCnCH</nextTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + <entry> + <int>-2</int> + <org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + <countStarted>0</countStarted> + <countCompleted>0</countCompleted> + <previous class="MutableSet"> + <int>0</int> + </previous> + <previousTaskId>i0CXCnCH</previousTaskId> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext_-OldStepRecord> + </entry> + </oldStepInfo> + <retryRecords class="MutableMap"/> + </org.apache.brooklyn.core.workflow.WorkflowExecutionContext> + </Z13KmPUw> + </MutableMap> + </internals.brooklyn.workflow> + </attributes> + <attributeKeys> + <service.notUp.indicators> + <attributeSensor> + <typeToken class="org.apache.brooklyn.core.entity.Attributes$1" resolves-to="com.google.common.reflect.TypeToken$SimpleTypeToken"> + <runtimeType class="com.google.common.reflect.Types$ParameterizedTypeImpl"> + <argumentsList> + <java-class>java.lang.String</java-class> + <java-class>java.lang.Object</java-class> + </argumentsList> + <rawType>java.util.Map</rawType> + </runtimeType> + </typeToken> + <name>service.notUp.indicators</name> + <description>A map of namespaced indicators that the service is not up</description> + <persistence>REQUIRED</persistence> + </attributeSensor> + </service.notUp.indicators> + <internals.brooklyn.workflow> + <attributeSensor> + <typeToken class="org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors$1" resolves-to="com.google.common.reflect.TypeToken$SimpleTypeToken"> + <runtimeType class="com.google.common.reflect.Types$ParameterizedTypeImpl"> + <argumentsList> + <java-class>java.lang.String</java-class> + <java-class>org.apache.brooklyn.core.workflow.WorkflowExecutionContext</java-class> + </argumentsList> + <rawType>java.util.Map</rawType> + </runtimeType> + </typeToken> + <name>internals.brooklyn.workflow</name> + <description>internals.brooklyn.workflow</description> + <persistence>REQUIRED</persistence> + </attributeSensor> + </internals.brooklyn.workflow> + </attributeKeys> +</entity> diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java index 522a6d1c71..786435433f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java @@ -18,6 +18,25 @@ */ package org.apache.brooklyn.core.workflow; +import java.time.Instant; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nullable; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -42,6 +61,7 @@ import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.resolve.jackson.JsonPassThroughDeserializer; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext.SubworkflowLocality; import org.apache.brooklyn.core.workflow.store.WorkflowRetentionAndExpiration; import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors; import org.apache.brooklyn.core.workflow.utils.WorkflowRetentionParser; @@ -67,20 +87,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - import static org.apache.brooklyn.core.workflow.WorkflowReplayUtils.ReplayResumeDepthCheck.RESUMABLE_WHENEVER_NESTED_WORKFLOWS_PRESENT; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -1600,10 +1606,17 @@ public class WorkflowExecutionContext { log.debug(prefix + "no further steps: Workflow completed"); } } else if (specialNext instanceof String) { - boolean isInLocalSubworkflow = getParent()!=null && getParent().currentStepInstance!=null && Boolean.TRUE.equals(getParent().currentStepInstance.isLocalSubworkflow); - if (isInLocalSubworkflow && Boolean.TRUE.equals(currentStepInstance.nextIsReturn)) { + SubworkflowLocality subworkflowLocality = getParent() != null && getParent().currentStepInstance != null ? getParent().currentStepInstance.subworkflowLocality : null; + boolean isInLocalSubworkflow = subworkflowLocality!=null && subworkflowLocality.ordinal()>=SubworkflowLocality.LOCAL_STEPS_SHARED_CONTEXT.ordinal(); + if (isInLocalSubworkflow && STEP_TARGET_NAME_FOR_END.equals(specialNext)) { // parent of local subworkflow should return also - getParent().currentStepInstance.next = specialNext; + if (Boolean.TRUE.equals(currentStepInstance.nextIsReturn)) { + getParent().currentStepInstance.next = specialNext; + getParent().currentStepInstance.nextIsReturn = true; + } else if (SubworkflowLocality.INLINE_SHARED_CONTEXT.equals(subworkflowLocality)) { + // if statement as shorthand should go to end of calling workflow + getParent().currentStepInstance.next = specialNext; + } } String explicitNext = (String)specialNext; diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java index 3d1a4d19ce..bf07e2cf74 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonSetter; import com.google.common.reflect.TypeToken; import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamOmitField; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.config.ConfigKey; @@ -42,6 +43,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Supplier; +import javax.annotation.Nullable; @JsonInclude(JsonInclude.Include.NON_NULL) public class WorkflowStepInstanceExecutionContext { @@ -84,7 +86,20 @@ public class WorkflowStepInstanceExecutionContext { // replay instructions or a string explicit next step identifier public Object next; public Boolean nextIsReturn; - public Boolean isLocalSubworkflow; + @XStreamAlias("isLocalSubworkflow") @XStreamOmitField @JsonIgnore + private Boolean _isLocalSubworkflow_KeptForLegacyRebind; // remove after Brooklyn 1.2 released; replaced by subworkflowLocality + + public enum SubworkflowLocality { + /** least local; workflow is a separate saved type */ + ENCAPSULATED, + /** defined in same context, but in such a way it should run in a separate context eg foreach */ + LOCAL_SEPARATE_CONTEXT, + /** defined in same context, in such a way it shares context with parent, but as steps */ + LOCAL_STEPS_SHARED_CONTEXT, + /** defined in same context, in such a way it shares context with parent, and inline so it should feel like outer context */ + INLINE_SHARED_CONTEXT, + } + @Nullable public SubworkflowLocality subworkflowLocality; /** Return any error we are handling, if the step is in an error handler, * or an unhandled error if the step is not in an error handler, diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/CustomWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/CustomWorkflowStep.java index 259ec06df9..f8cfb4435c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/CustomWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/CustomWorkflowStep.java @@ -53,6 +53,7 @@ import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution; import org.apache.brooklyn.core.workflow.WorkflowReplayUtils; import org.apache.brooklyn.core.workflow.WorkflowStepDefinition; import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext; +import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext.SubworkflowLocality; import org.apache.brooklyn.core.workflow.WorkflowStepResolution; import org.apache.brooklyn.core.workflow.utils.WorkflowConcurrencyParser; import org.apache.brooklyn.core.workflow.utils.WorkflowRetentionParser; @@ -85,15 +86,23 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl private static final String WORKFLOW_SETTING_SHORTHAND = "[ \"replayable\" ${replayable...} ] [ \"retention\" ${retention...} ] "; /* fields which are only permitted in the registered type definition */ - protected static final Set<String> FORBIDDEN_IN_WORKFLOW_STEP = MutableSet.copyOf(Arrays.asList(WorkflowCommonConfig.PARAMETER_DEFS) - .stream().map(ConfigKey::getName).collect(Collectors.toSet())).asUnmodifiable(); - - protected static final Set<String> FORBIDDEN_IN_WORKFLOW_STEP_IN_SUBCLASSES = MutableSet.copyOf(Arrays.asList(WorkflowCommonConfig.STEPS) - .stream().map(ConfigKey::getName).collect(Collectors.toSet())).put("target").asUnmodifiable(); + protected static final Set<String> FORBIDDEN_ON_ALL_WORKFLOW_STEP_TYPES_MAP = MutableSet.of( + "subworkflowLocality") + .asUnmodifiable(); + protected static final Set<String> FORBIDDEN_ON_NORMAL_WORKFLOW_STEP_MAP = MutableSet.copyOf(FORBIDDEN_ON_ALL_WORKFLOW_STEP_TYPES_MAP) + .putAll(Arrays.asList(WorkflowCommonConfig.PARAMETER_DEFS).stream().map(ConfigKey::getName).collect(Collectors.toSet())) + .asUnmodifiable(); public static final boolean CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE = false; - protected static final Set<String> FORBIDDEN_IN_REGISTERED_TYPE_EXTENSIONS = - (CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE ? MutableSet.<String>of() : MutableSet.of("reducing")).asUnmodifiable(); + protected static final Set<String> FORBIDDEN_ON_REGISTERED_TYPE_EXTENSIONS_MAP = MutableSet.<String>of() + .putAll(CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE ? MutableSet.<String>of() : MutableSet.of("reducing")) + .put("target") + .put(WorkflowCommonConfig.STEPS.getName()) + .asUnmodifiable(); + protected static final Set<String> FORBIDDEN_ON_REGISTERED_TYPE_EXTENSIONS_FIELDS = MutableSet.<String>of() + .putAll(CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE ? MutableSet.<String>of() : MutableSet.of("reducing")) + .put("target") + .asUnmodifiable(); public CustomWorkflowStep() {} public CustomWorkflowStep(String name, List<Object> steps) { @@ -250,6 +259,7 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl if (retention!=null) { context.getWorkflowExectionContext().updateRetentionFrom(WorkflowRetentionParser.parse(retention, context.getWorkflowExectionContext()).init(context.getWorkflowExectionContext())); } + context.subworkflowLocality = getSubworkflowLocality(); if (steps==null) { return context.getPreviousStepOutput(); @@ -577,25 +587,23 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl protected void checkCallerSuppliedDefinition(String typeBestGuess, Map m) { // caller (workflow author) cannot set parameters, that makes no sense - FORBIDDEN_IN_WORKFLOW_STEP.stream().filter(m::containsKey).forEach(forbiddenKey -> { + FORBIDDEN_ON_NORMAL_WORKFLOW_STEP_MAP.stream().filter(m::containsKey).forEach(forbiddenKey -> { throw new IllegalArgumentException("Not permitted to override '" + forbiddenKey + "' when using a workflow step"); }); - if (!CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE && !isInternalClassNotExtendedAndUserAllowedToSetMostThings(typeBestGuess)) { + boolean isInternalClassNotRegisteredBeanSubtype = isInternalClassNotExtendedAndUserAllowedToSetMostThings(typeBestGuess); + if (!isInternalClassNotRegisteredBeanSubtype) { + subworkflowLocality = SubworkflowLocality.ENCAPSULATED; + // caller can't specify these - FORBIDDEN_IN_REGISTERED_TYPE_EXTENSIONS.stream().filter(m::containsKey).forEach(forbiddenKey -> { + FORBIDDEN_ON_REGISTERED_TYPE_EXTENSIONS_MAP.stream().filter(m::containsKey).forEach(forbiddenKey -> { throw new IllegalArgumentException("Not permitted to set '" + forbiddenKey + "' when using a custom workflow step"); }); // neither should the custom registered type itself! - FORBIDDEN_IN_REGISTERED_TYPE_EXTENSIONS.stream().filter(k -> (Reflections.getFieldValueMaybe(this, k).isPresentAndNonNull())).forEach(forbiddenKey -> { + FORBIDDEN_ON_REGISTERED_TYPE_EXTENSIONS_FIELDS.stream().filter(k -> (Reflections.getFieldValueMaybe(this, k).isPresentAndNonNull())).forEach(forbiddenKey -> { throw new IllegalArgumentException("Not permitted for a custom workflow step to use '" + forbiddenKey + "'"); }); } - if (!isInternalClassNotExtendedAndUserAllowedToSetMostThings(typeBestGuess)) { - FORBIDDEN_IN_WORKFLOW_STEP_IN_SUBCLASSES.stream().filter(m::containsKey).forEach(forbiddenKey -> { - throw new IllegalArgumentException("Not permitted to override '" + forbiddenKey + "' when using a custom workflow step"); - }); - } } protected boolean isInternalClassNotExtendedAndUserAllowedToSetMostThings(String typeBestGuess) { @@ -605,6 +613,12 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl return typeBestGuess!=null && !shorthandDefault.equals(typeBestGuess) && !clazz.getName().equals(typeBestGuess); } + protected SubworkflowLocality subworkflowLocality; + protected SubworkflowLocality getSubworkflowLocality() { + if (subworkflowLocality!=null) return subworkflowLocality; + return SubworkflowLocality.LOCAL_SEPARATE_CONTEXT; + } + protected WorkflowExecutionContext newWorkflow(WorkflowStepInstanceExecutionContext context, Object target, Integer targetIndexOrNullIfNotList) { if (steps==null) throw new IllegalArgumentException("Cannot make new workflow with no steps"); diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java index c269616080..325fef1b86 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java @@ -27,6 +27,7 @@ import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WorkflowEx import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WrappedResolvedExpression; import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WrappingMode; import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext; +import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext.SubworkflowLocality; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.predicates.DslPredicates.DslEntityPredicateDefault; @@ -48,16 +49,18 @@ public class IfWorkflowStep extends SubWorkflowStep { Object condition_target; Object condition_equals; + Boolean shorthand_step; @Override public void populateFromShorthand(String value) { - if (input==null) input = MutableMap.of(); populateFromShorthandTemplate(SHORTHAND, value, true, true); if (input.containsKey("step")) { + if (subworkflowLocality==null) subworkflowLocality = SubworkflowLocality.INLINE_SHARED_CONTEXT; Object step = input.remove("step"); if (this.steps!=null) throw new IllegalArgumentException("Cannot set step in shorthand and steps in body"); this.steps = MutableList.of(step); + shorthand_step = true; } else if (steps==null) throw new IllegalArgumentException("'if' step requires a step or steps"); if (input.containsKey("condition_target")) { diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/LabelWorkflowStep.java similarity index 52% copy from core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java copy to core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/LabelWorkflowStep.java index 6316d4521b..fee75a9c7e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/LabelWorkflowStep.java @@ -18,32 +18,47 @@ */ package org.apache.brooklyn.core.workflow.steps.flow; -import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.Objects; + import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution; import org.apache.brooklyn.core.workflow.WorkflowStepDefinition; import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext; +import org.apache.brooklyn.core.workflow.WorkflowStepResolution; +import org.apache.brooklyn.util.text.Strings; -import static org.apache.brooklyn.core.workflow.WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END; - -public class ReturnWorkflowStep extends WorkflowStepDefinition { +public class LabelWorkflowStep extends WorkflowStepDefinition { - public static final String SHORTHAND = "${value...}"; + public static final String SHORTHAND = "${id}"; - public static final ConfigKey<Object> VALUE = ConfigKeys.newConfigKey(Object.class, "value"); + public static final ConfigKey<String> ID = ConfigKeys.newStringConfigKey("id"); @Override public void populateFromShorthand(String expression) { populateFromShorthandTemplate(SHORTHAND, expression); + if (!(input.get(ID.getName()) instanceof String)) throw new IllegalArgumentException("ID is required and must be a string"); } @Override - protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) { - if (next==null) { - context.next = STEP_TARGET_NAME_FOR_END; - context.nextIsReturn = true; + public void validateStep(WorkflowStepResolution workflowStepResolution) { + super.validateStep(workflowStepResolution); + String newId = (String) input.get(ID.getName()); + if (Strings.isNonBlank(newId)) { + if (Strings.isNonBlank(id)) { + if (!Objects.equals(id, newId)) throw new IllegalArgumentException("Incompatible ID's set on step"); + } else { + id = newId; + } } - return context.getInput(VALUE); + } + + @Override + protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) { + //// don't allow resolving dynamically; it doesn't make sense to have multiple labels for the same step, + //// as the goto won't go to that specific instance anyway, and it causes a lot of complexity + // this.id = context.getInput(ID); + return context.getPreviousStepOutput(); } @Override protected Boolean isDefaultIdempotent() { return true; } diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java index 6316d4521b..2ae88b106e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java @@ -18,7 +18,6 @@ */ package org.apache.brooklyn.core.workflow.steps.flow; -import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.workflow.WorkflowStepDefinition; diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java index 7d0735e211..dedcb024ce 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java @@ -18,20 +18,15 @@ */ package org.apache.brooklyn.core.workflow.steps.flow; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import com.google.common.base.MoreObjects; import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.workflow.WorkflowCommonConfig; import org.apache.brooklyn.core.workflow.WorkflowExecutionContext; -import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution; import org.apache.brooklyn.core.workflow.WorkflowStepDefinition; import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext; +import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext.SubworkflowLocality; import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -41,8 +36,12 @@ public class SubWorkflowStep extends CustomWorkflowStep { public static final String SHORTHAND_TYPE_NAME_DEFAULT = "subworkflow"; - protected static final Set<String> FORBIDDEN_IN_SUBWORKFLOW_STEP_ALWAYS = MutableSet.copyOf(FORBIDDEN_IN_REGISTERED_TYPE_EXTENSIONS) - .putAll(MutableSet.of("target", "concurrency")).asUnmodifiable(); + protected static MutableSet<String> X = MutableSet.of("target", "concurrency"); + protected static final Set<String> FORBIDDEN_ON_SUBWORKFLOW_STEP_FIELDS = MutableSet.<String>of() + .putAll(X).asUnmodifiable(); + protected static final Set<String> FORBIDDEN_ON_SUBWORKFLOW_STEP_MAP = MutableSet.copyOf(FORBIDDEN_ON_NORMAL_WORKFLOW_STEP_MAP) + .putAll(FORBIDDEN_ON_ALL_WORKFLOW_STEP_TYPES_MAP) + .putAll(X).asUnmodifiable(); public SubWorkflowStep() {} @@ -54,15 +53,20 @@ public class SubWorkflowStep extends CustomWorkflowStep { return !isRegisteredTypeExtensionToClass(SubWorkflowStep.class, SHORTHAND_TYPE_NAME_DEFAULT, typeBestGuess); } + protected SubworkflowLocality getSubworkflowLocality() { + if (subworkflowLocality!=null) return subworkflowLocality; + return SubworkflowLocality.LOCAL_STEPS_SHARED_CONTEXT; + } + protected void checkCallerSuppliedDefinition(String typeBestGuess, Map m) { if (!isInternalClassNotExtendedAndUserAllowedToSetMostThings(typeBestGuess)) { throw new IllegalArgumentException("Not permitted to define a custom subworkflow step with this supertype"); } // these can't be set by user or registered type for subworkflow - FORBIDDEN_IN_SUBWORKFLOW_STEP_ALWAYS.stream().filter(m::containsKey).forEach(forbiddenKey -> { + FORBIDDEN_ON_SUBWORKFLOW_STEP_MAP.stream().filter(m::containsKey).forEach(forbiddenKey -> { throw new IllegalArgumentException("Not permitted to set '" + forbiddenKey + "' when using a subworkflow step"); }); - FORBIDDEN_IN_SUBWORKFLOW_STEP_ALWAYS.stream().filter(k -> (Reflections.getFieldValueMaybe(this, k).isPresentAndNonNull())).forEach(forbiddenKey -> { + FORBIDDEN_ON_SUBWORKFLOW_STEP_FIELDS.stream().filter(k -> (Reflections.getFieldValueMaybe(this, k).isPresentAndNonNull())).forEach(forbiddenKey -> { throw new IllegalArgumentException("Not permitted for a subworkflow step to use '" + forbiddenKey + "'"); }); } @@ -75,7 +79,6 @@ public class SubWorkflowStep extends CustomWorkflowStep { @Override protected Map initializeReducingVariables(WorkflowStepInstanceExecutionContext context, Map<String, Object> reducing) { - context.isLocalSubworkflow = true; MutableMap<String, Object> allVarsInScope = MutableMap.copyOf(context.getWorkflowExectionContext().getWorkflowScratchVariables()); // make output visible allVarsInScope.add("output", context.getWorkflowExectionContext().getPreviousStepOutput()); diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java index 06ef869aa3..50d5060883 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java @@ -105,6 +105,8 @@ public class SetVariableWorkflowStep extends WorkflowStepDefinition { Object resolvedValue = new ConfigurableInterpolationEvaluation(context, type, unresolvedValue, context.getInputOrDefault(INTERPOLATION_MODE), context.getInputOrDefault(INTERPOLATION_ERRORS)).evaluate(); + if ("output".equals(name)) return resolvedValue; + setWorkflowScratchVariableDotSeparated(context, name, resolvedValue, // metadata is easily inferred from workflow vars, and they can use a lot of persistence space, so skip false); diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java index f89f79b206..70870f9107 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java @@ -73,6 +73,7 @@ import org.apache.brooklyn.core.workflow.steps.flow.FailWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.ForeachWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.GotoWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.IfWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.LabelWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.NoOpWorkflowStep; import org.apache.brooklyn.core.workflow.steps.flow.RetryWorkflowStep; @@ -142,6 +143,7 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport { addRegisteredTypeBean(mgmt, "clear-workflow-variable", ClearVariableWorkflowStep.class); addRegisteredTypeBean(mgmt, "wait", WaitWorkflowStep.class); addRegisteredTypeBean(mgmt, "return", ReturnWorkflowStep.class); + addRegisteredTypeBean(mgmt, "label", LabelWorkflowStep.class); addRegisteredTypeBean(mgmt, "if", IfWorkflowStep.class); addRegisteredTypeBean(mgmt, "goto", GotoWorkflowStep.class); addRegisteredTypeBean(mgmt, "switch", SwitchWorkflowStep.class); diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java index 1b2a646eda..6e6935aadd 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java @@ -19,82 +19,10 @@ package org.apache.brooklyn.core.workflow; import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.reflect.TypeToken; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.entity.EntitySpec; -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.api.mgmt.Task; -import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; -import org.apache.brooklyn.api.objs.BrooklynObject; -import org.apache.brooklyn.api.policy.Policy; -import org.apache.brooklyn.api.sensor.AttributeSensor; -import org.apache.brooklyn.api.sensor.Sensor; -import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.entity.Dumper; -import org.apache.brooklyn.core.entity.Entities; -import org.apache.brooklyn.core.entity.EntityAsserts; -import org.apache.brooklyn.core.entity.EntityInternal; -import org.apache.brooklyn.core.resolve.jackson.BeanWithTypePlanTransformer; -import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils; -import org.apache.brooklyn.core.sensor.Sensors; -import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; -import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; -import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer; -import org.apache.brooklyn.core.typereg.RegisteredTypes; -import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.AddEntityWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.AddPolicyWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.ApplyInitializerWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.ClearConfigWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.ClearSensorWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.DeleteEntityWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.DeletePolicyWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.DeployApplicationWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.InvokeEffectorWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.ReparentEntityWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.SetConfigWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.SetEntityNameWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.SetSensorWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.appmodel.UpdateChildrenWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.external.HttpWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.external.ShellWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.external.SshWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.FailWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.ForeachWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.GotoWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.NoOpWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.RetryWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.ReturnWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.SleepWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.SwitchWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.variables.ClearVariableWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.variables.LoadWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.variables.SetVariableWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.variables.WaitWorkflowStep; -import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.test.Asserts; -import org.apache.brooklyn.test.ClassLogWatcher; -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.time.Duration; import org.apache.commons.lang3.tuple.Pair; import org.testng.annotations.Test; diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowPersistReplayErrorsTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowPersistReplayErrorsTest.java index 4f5cb3684c..236878afd5 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowPersistReplayErrorsTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowPersistReplayErrorsTest.java @@ -729,11 +729,11 @@ public class WorkflowPersistReplayErrorsTest extends RebindTestFixture<BasicAppl MutableMap.of("step", "log Step created-but-not-logged because of bad variable ${not_available}", "on-error", MutableList.of( MutableMap.of("step", "log Error handler 1-5-1", "output", "from 1-5-1"), - "goto exit", + "goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_EXIT, "log NOT shown after inner exit") ), "log Error handler 1-6", - "goto exit", + "goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_EXIT, "log NOT shown because of earlier exit") ), "log Step 2 has output ${output}" diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java index e411031ac0..7301821200 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java @@ -145,17 +145,17 @@ public class WorkflowSubIfAndCustomExtensionEdgeTest extends RebindTestFixture<T @Test public void testSubWorkflowStep() throws Exception { - Function<Boolean,Object> test = (explicitSubworkflow) -> - runWorkflow(MutableList.of( - "let v1 = V1", - "let v2 = V2", - MutableMap.<String,Object>of("steps", MutableList.of( - "let v0 = ${v0}B", - "let v1 = ${v1}B", - "let v3 = V3B")) - .add(explicitSubworkflow ? MutableMap.of("step", "subworkflow") : null), - "return ${v0}-${v1}-${v2}-${v3}"), - ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0")) ); + Function<Boolean, Object> test = (explicitSubworkflow) -> + runWorkflow(MutableList.of( + "let v1 = V1", + "let v2 = V2", + MutableMap.<String, Object>of("steps", MutableList.of( + "let v0 = ${v0}B", + "let v1 = ${v1}B", + "let v3 = V3B")) + .add(explicitSubworkflow ? MutableMap.of("step", "subworkflow") : null), + "return ${v0}-${v1}-${v2}-${v3}"), + ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0"))); Asserts.assertEquals(test.apply(true), "V0B-V1B-V2-V3B"); // subworkflow is chosen implicitly if step is omitted @@ -166,7 +166,7 @@ public class WorkflowSubIfAndCustomExtensionEdgeTest extends RebindTestFixture<T "let v1 = V1", "goto marker", // prefers inner id 'marker' "let v1 = NOT_V1_1", - MutableMap.of("id", "marker", "step", "goto end"), // goes to end of this subworkflow + MutableMap.of("id", "marker", "step", "goto " + WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END), // goes to end of this subworkflow "let v1 = NOT_V1_2")), "let v2 = V2", MutableMap.of("steps", MutableList.of( @@ -179,6 +179,73 @@ public class WorkflowSubIfAndCustomExtensionEdgeTest extends RebindTestFixture<T "let v4 = NOT_V4")); Asserts.assertEquals(lastInvocation.getUnchecked(), "V1-V2-V3-V4"); } + @Test + public void testSubworkflowReturnsAndGotoEndsAndLabel() { + runWorkflow(MutableList.of( + "let x = 1", + "if ${x} == 1 then return yes_if_returns", + "return no_if_exited with ${yes_if_returns}")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "yes_if_returns"); + + runWorkflow(MutableList.of( + "let y = 0", + "let x = 1", + MutableMap.of("steps", MutableList.of( + "let x = ${x} + 1", + "goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END, // goes to end of this subworkflow, but not outer + "let x = 3" // shouldn't run + )), + "let y = ${x}_1", + "if ${x} == 2 then let y = ${x}_2", + "let output = ${y}", + "goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END, + "return should_skip_this")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "2_2"); + + // return ends all local workflows, but not nested + addBeanWithType("step-with-return", "1-SNAPSHOT", Strings.lines( + "type: workflow", + "steps:", + " - return inner" + )); + runWorkflow(MutableList.of( + MutableMap.of("steps", MutableList.of( + MutableMap.of("steps", MutableList.of( + "step-with-return", + "return ${output}-then-1" + )), + "return no-2")), + "return no-3")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "inner-then-1"); + + // end means local workflow + runWorkflow(MutableList.of( + "let x = 1", + "let output = not_expected", + MutableMap.of("step", "if ${x} == 1", "steps", MutableList.of("goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END)), + "return last_step_should_run")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "last_step_should_run"); + // but if inline, local workflow is the parent + runWorkflow(MutableList.of( + "let x = 1", + "let output = expected", + "if ${x} == 1 then goto "+WorkflowExecutionContext.STEP_TARGET_NAME_FOR_END, + "return last_step_should_not_run_when_inline")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "expected"); + + // explicit label available from local subworkflow + runWorkflow(MutableList.of( + "let x = A", + MutableMap.of( + "steps", MutableList.of( + "goto l1", // better explicit flow + "let x = ${x}_no1" + )), + "let x = ${x}_no2", + "label l1", + "return ${x}_B")); + Asserts.assertEquals(lastInvocation.getUnchecked(), "A_B"); + } @Test public void testIfWorkflowStep() throws Exception { diff --git a/karaf/init/src/main/resources/catalog.bom b/karaf/init/src/main/resources/catalog.bom index 30d0836297..7cfd02249e 100644 --- a/karaf/init/src/main/resources/catalog.bom +++ b/karaf/init/src/main/resources/catalog.bom @@ -158,6 +158,11 @@ brooklyn.catalog: itemType: bean item: type: org.apache.brooklyn.core.workflow.steps.flow.ReturnWorkflowStep + - id: label + format: java-type-name + itemType: bean + item: + type: org.apache.brooklyn.core.workflow.steps.flow.LabelWorkflowStep - id: if format: java-type-name itemType: bean
