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


Reply via email to