You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/10/21 13:57:55 UTC

[brooklyn-server] 03/11: block type instantiation for steps

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 a33153f6c81aa9c092022115b94e2f96a91b1d7c
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Thu Oct 20 09:28:33 2022 +0100

    block type instantiation for steps
    
    they are instantiated specially later, and the field `type` should be ignored
---
 .../brooklyn/camp/brooklyn/WorkflowYamlTest.java   | 88 +++++++++++++++++++++-
 .../resolve/jackson/AsPropertyIfAmbiguous.java     | 31 +++++++-
 .../jackson/JsonPassThroughDeserializer.java       | 59 +++++++++++++++
 .../core/workflow/WorkflowExecutionContext.java    |  9 +++
 .../core/workflow/steps/CustomWorkflowStep.java    | 15 +++-
 .../brooklyn/core/workflow/WorkflowBasicTest.java  | 18 ++++-
 .../software/base/WorkflowSoftwareProcessTest.java |  9 +++
 7 files changed, 220 insertions(+), 9 deletions(-)

diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
index 4559d9bcde..728283f24a 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
@@ -19,33 +19,48 @@
 package org.apache.brooklyn.camp.brooklyn;
 
 import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.Dumper;
 import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
 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.WorkflowBasicTest;
-import org.apache.brooklyn.core.workflow.WorkflowEffector;
-import org.apache.brooklyn.core.workflow.WorkflowPolicy;
-import org.apache.brooklyn.core.workflow.WorkflowSensor;
+import org.apache.brooklyn.core.workflow.*;
 import org.apache.brooklyn.core.workflow.steps.LogWorkflowStep;
+import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors;
+import org.apache.brooklyn.entity.software.base.WorkflowSoftwareProcess;
 import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.location.winrm.WinrmWorkflowStep;
 import org.apache.brooklyn.tasks.kubectl.ContainerWorkflowStep;
 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.internal.ssh.RecordingSshTool;
+import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.testng.Assert;
@@ -53,9 +68,13 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Predicate;
 
+import static org.apache.brooklyn.util.core.internal.ssh.ExecCmdAsserts.assertExecContains;
+import static org.apache.brooklyn.util.core.internal.ssh.ExecCmdAsserts.assertExecsContain;
+
 public class WorkflowYamlTest extends AbstractYamlTest {
 
     static final String VERSION = "0.1.0-SNAPSHOT";
@@ -212,6 +231,11 @@ public class WorkflowYamlTest extends AbstractYamlTest {
         } else {
             EntityAsserts.assertAttributeEqualsContinually(entity, s, null);
         }
+
+        WorkflowExecutionContext lastWorkflowContext = new WorkflowStatePersistenceViaSensors(mgmt()).getWorkflows(entity).values().iterator().next();
+        List<Object> defs = lastWorkflowContext.getStepsDefinition();
+        // step definitions should not be resolved by jackson
+        defs.forEach(def -> Asserts.assertThat(def, d -> !(d instanceof WorkflowStepDefinition)));
     }
 
     public void doTestWorkflowPolicy(String triggers, Predicate<Duration> timeCheckOrNullIfShouldFail) throws Exception {
@@ -460,4 +484,60 @@ public class WorkflowYamlTest extends AbstractYamlTest {
             Asserts.expectedFailureContainsIgnoreCase(e, "resolve step", "unsupported-type");
         }
     }
+
+    @Test
+    public void testWorkflowSoftwareProcessAsYaml() throws Exception {
+        RecordingSshTool.clear();
+
+        FixedListMachineProvisioningLocation loc = mgmt().getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .configure(FixedListMachineProvisioningLocation.MACHINE_SPECS, ImmutableList.<LocationSpec<? extends MachineLocation>>of(
+                        LocationSpec.create(SshMachineLocation.class)
+                                .configure("address", "1.2.3.4")
+                                .configure(SshMachineLocation.SSH_TOOL_CLASS, RecordingSshTool.class.getName()))));
+
+        Application app = createApplicationUnstarted(
+                "services:",
+                "- type: " + WorkflowSoftwareProcess.class.getName(),
+                "  brooklyn.config:",
+                "    "+BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION.getName()+": true",
+                "    install.workflow:",
+                "      steps:",
+                "        - ssh installWorkflow",
+                "        - set-sensor boolean installed = true",
+                "        - type: no-op",
+                "    stop.workflow:",
+                "      steps:",
+                "        - ssh stopWorkflow",
+                "        - set-sensor boolean stopped = true"
+        );
+
+        Entity child = app.getChildren().iterator().next();
+        List<Object> steps = child.config().get(WorkflowSoftwareProcess.INSTALL_WORKFLOW).peekSteps();
+        // should not be resolved yet
+        steps.forEach(def -> Asserts.assertThat(def, d -> !(d instanceof WorkflowStepDefinition)));
+
+        ((Startable)app).start(MutableList.of(loc));
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "installWorkflow"));
+
+        EntityAsserts.assertAttributeEquals(child, Sensors.newSensor(Boolean.class, "installed"), true);
+        EntityAsserts.assertAttributeEquals(child, Sensors.newSensor(Boolean.class, "stopped"), null);
+
+        EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_UP, true);
+        EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        WorkflowExecutionContext lastWorkflowContext = new WorkflowStatePersistenceViaSensors(mgmt()).getWorkflows(child).values().iterator().next();
+        List<Object> defs = lastWorkflowContext.getStepsDefinition();
+        // step definitions should not be resolved by jackson
+        defs.forEach(def -> Asserts.assertThat(def, d -> !(d instanceof WorkflowStepDefinition)));
+
+        ((Startable)app).stop();
+
+        EntityAsserts.assertAttributeEquals(child, Sensors.newSensor(Boolean.class, "stopped"), true);
+        assertExecContains(RecordingSshTool.getLastExecCmd(), "stopWorkflow");
+
+        EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_UP, false);
+        EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+    }
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
index fc9b041a80..819721baba 100644
--- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
@@ -44,6 +44,7 @@ import java.lang.reflect.AccessibleObject;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class AsPropertyIfAmbiguous {
 
@@ -116,7 +117,31 @@ public class AsPropertyIfAmbiguous {
         }
     }
 
-    /** Type deserializer which undersrtands a '@type' property if 'type' conflicts with a field on the class and which uses the base type if no type is specified */
+    static ThreadLocal<AtomicInteger> suppressingTypeFieldDeserialization = new ThreadLocal<>();
+    static boolean isSuppressingTypeFieldDeserialization() {
+        AtomicInteger count = suppressingTypeFieldDeserialization.get();
+        if (count==null) return false;
+        return count.get() > 0;
+    }
+    static void startSuppressingTypeFieldDeserialization() {
+        AtomicInteger count = suppressingTypeFieldDeserialization.get();
+        if (count==null) {
+            count = new AtomicInteger();
+            suppressingTypeFieldDeserialization.set(count);
+        }
+        count.incrementAndGet();
+    }
+    static void stopSuppressingTypeFieldDeserialization() {
+        AtomicInteger count = suppressingTypeFieldDeserialization.get();
+        if (count==null) {
+            throw new IllegalStateException("Count mismatch starting/stopping type field deserialization");
+        }
+        if (count.decrementAndGet()==0) {
+            suppressingTypeFieldDeserialization.remove();
+        }
+    }
+
+    /** Type deserializer which understands a '@type' property if 'type' conflicts with a field on the class and which uses the base type if no type is specified */
     public static class AsPropertyButNotIfFieldConflictTypeDeserializer extends AsPropertyTypeDeserializer {
         public AsPropertyButNotIfFieldConflictTypeDeserializer(JavaType bt, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, JavaType defaultImpl, As inclusion) {
             super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl, inclusion);
@@ -171,6 +196,10 @@ public class AsPropertyIfAmbiguous {
 
         // copied from super class
         private Object deserializeTypedFromObjectSuper(JsonParser p, DeserializationContext ctxt, boolean mustUseConflictingTypePrefix) throws IOException {
+            if (isSuppressingTypeFieldDeserialization()) {
+                return _deserializeTypedUsingDefaultImpl(p, ctxt, null, "typed deserialization is suppressed");
+            }
+
 //            return super.deserializeTypedFromObject(p, ctxt);
 
             // 02-Aug-2013, tatu: May need to use native type ids
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/JsonPassThroughDeserializer.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/JsonPassThroughDeserializer.java
new file mode 100644
index 0000000000..58ae276cd1
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/JsonPassThroughDeserializer.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+package org.apache.brooklyn.core.resolve.jackson;
+
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.google.common.annotations.Beta;
+
+import java.io.IOException;
+
+/** deserializer intended for use via contentUsing (not content), to prevent type expansion */
+@Beta
+public class JsonPassThroughDeserializer extends JsonDeserializer {
+
+    @Override
+    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
+        try {
+            AsPropertyIfAmbiguous.startSuppressingTypeFieldDeserialization();
+            return ctxt.readValue(p, Object.class);
+        } finally {
+            AsPropertyIfAmbiguous.stopSuppressingTypeFieldDeserialization();
+        }
+    }
+
+    @Override
+    public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
+        throw new IllegalStateException("Unsupported to deserialize into an object");
+    }
+
+    @Override
+    public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
+        return deserialize(p, ctxt);
+    }
+
+    @Override
+    public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer, Object intoValue) throws IOException {
+        return deserialize(p, ctxt, intoValue);
+    }
+
+}
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 6679e3c494..388ea5101c 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
@@ -20,6 +20,7 @@ package org.apache.brooklyn.core.workflow;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.google.common.reflect.TypeToken;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -32,6 +33,7 @@ import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityAdjuncts;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.resolve.jackson.JsonPassThroughDeserializer;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
 import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors;
 import org.apache.brooklyn.util.collections.MutableList;
@@ -94,7 +96,10 @@ public class WorkflowExecutionContext {
     transient WorkflowExecutionContext parent;
     String parentId;
 
+    // should be treated as raw json
+    @JsonDeserialize(contentUsing = JsonPassThroughDeserializer.class)
     List<Object> stepsDefinition;
+
     DslPredicates.DslPredicate condition;
 
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
@@ -550,6 +555,10 @@ public class WorkflowExecutionContext {
         return errorHandlerContext;
     }
 
+    public List<Object> getStepsDefinition() {
+        return MutableList.copyOf(stepsDefinition).asUnmodifiable();
+    }
+
     transient Map<String,Pair<Integer,WorkflowStepDefinition>> stepsWithExplicitId;
     transient List<WorkflowStepDefinition> stepsResolved;
     @JsonIgnore
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 ef1cbe7766..4f36809a5e 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
@@ -20,14 +20,17 @@ package org.apache.brooklyn.core.workflow.steps;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.google.common.collect.Iterables;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.reflect.TypeToken;
 import org.apache.brooklyn.api.entity.Entity;
 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.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
+import org.apache.brooklyn.core.resolve.jackson.JsonPassThroughDeserializer;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
 import org.apache.brooklyn.core.workflow.*;
 import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors;
@@ -58,6 +61,9 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl
     }
 
     Map<String,Object> parameters;
+
+    // should be treated as raw json
+    @JsonDeserialize(contentUsing = JsonPassThroughDeserializer.class)
     List<Object> steps;
 
     Object workflowOutput;
@@ -171,4 +177,9 @@ public class CustomWorkflowStep extends WorkflowStepDefinition implements Workfl
                 null,
                 ConfigBag.newInstance(getInput()).putAll(extraConfig), null);
     }
+
+    @VisibleForTesting
+    public List<Object> peekSteps() {
+        return steps;
+    }
 }
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 08c5f7f467..073b477c2f 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
@@ -40,6 +40,7 @@ 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.*;
+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;
@@ -110,7 +111,7 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport {
     }
 
     @Test
-    public void testStepResolution() {
+    public void testStepResolution() throws JsonProcessingException {
         loadTypes();
         Map<String,Object> input = MutableMap.of("type", "no-op");
 
@@ -121,6 +122,12 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport {
         // util
         s = WorkflowStepResolution.resolveStep(mgmt, input);
         Asserts.assertInstanceOf(s, NoOpWorkflowStep.class);
+
+        String output1 = BrooklynObjectsJsonMapper.newDslToStringSerializingMapper(mgmt).writeValueAsString(s);
+        String output2 = BeanWithTypeUtils.newYamlMapper(mgmt, false, null, false).writerFor(Object.class).writeValueAsString(s);
+
+        Asserts.assertStringContains(output1, "\"shorthandTypeName\":\"no-op\"");
+        Asserts.assertStringContains(output2, "shorthandTypeName: \"no-op\"");
     }
 
     @Test
@@ -157,7 +164,7 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport {
     }
 
     @Test
-    public void testCommonStepsInEffector() {
+    public void testCommonStepsInEffector() throws JsonProcessingException {
         loadTypes();
         BasicApplication app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
 
@@ -217,6 +224,13 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport {
         Asserts.assertNull(badSensor);
         Asserts.assertEquals(app.sensors().get(Sensors.newSensor(Object.class, "bad")), null);
         Asserts.assertThat(app.sensors().getAll().keySet().stream().map(Sensor::getName).collect(Collectors.toSet()), s -> !s.contains("bad"));
+
+        WorkflowExecutionContext lastWorkflowContext = new WorkflowStatePersistenceViaSensors(mgmt()).getWorkflows(app).values().iterator().next();
+        String output1 = BrooklynObjectsJsonMapper.newDslToStringSerializingMapper(mgmt).writeValueAsString(lastWorkflowContext);
+        String output2 = BeanWithTypeUtils.newYamlMapper(mgmt, false, null, false).writerFor(Object.class).writeValueAsString(lastWorkflowContext);
+
+        Asserts.assertStringContains(output1, "\"type\":\"no-op\"");
+        Asserts.assertStringContains(output2, "type: \"no-op\"");
     }
 
     public static class WorkflowTestStep extends WorkflowStepDefinition {
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/WorkflowSoftwareProcessTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/WorkflowSoftwareProcessTest.java
index 1c49bd516f..9e3e939756 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/WorkflowSoftwareProcessTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/WorkflowSoftwareProcessTest.java
@@ -35,7 +35,10 @@ import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.sensor.function.FunctionSensor;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.workflow.WorkflowBasicTest;
+import org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
+import org.apache.brooklyn.core.workflow.WorkflowStepDefinition;
 import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep;
+import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors;
 import org.apache.brooklyn.enricher.stock.UpdatingMap;
 import org.apache.brooklyn.entity.software.base.SoftwareProcess.ChildStartableMode;
 import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
@@ -56,6 +59,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -122,6 +126,11 @@ public class WorkflowSoftwareProcessTest extends BrooklynAppUnitTestSupport {
         EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_UP, true);
         EntityAsserts.assertAttributeEqualsEventually(child, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
 
+        WorkflowExecutionContext lastWorkflowContext = new WorkflowStatePersistenceViaSensors(mgmt()).getWorkflows(child).values().iterator().next();
+        List<Object> defs = lastWorkflowContext.getStepsDefinition();
+        // step definitions should not be resolved by jackson
+        defs.forEach(def -> Asserts.assertThat(def, d -> !(d instanceof WorkflowStepDefinition)));
+
         app.stop();
 
         EntityAsserts.assertAttributeEquals(child, Sensors.newSensor(Boolean.class, "stopped"), true);