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