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 ab2beb27e5893764f55fd114c234a325c26bfad5 Author: Alex Heneveld <[email protected]> AuthorDate: Wed May 17 11:29:15 2023 +0100 support adding time to an instant --- .../brooklyn/camp/brooklyn/WorkflowYamlTest.java | 75 +++++++++++++++++++++- .../steps/variables/SetVariableWorkflowStep.java | 6 ++ .../variables/TransformVariableWorkflowStep.java | 35 ++++++++-- .../core/flags/BrooklynTypeNameResolution.java | 2 + .../workflow/WorkflowInputOutputExtensionTest.java | 4 +- .../org/apache/brooklyn/util/time/Duration.java | 8 +++ 6 files changed, 119 insertions(+), 11 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 7cfbc8329e..361bd08179 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 @@ -74,6 +74,9 @@ import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -1024,7 +1027,6 @@ public class WorkflowYamlTest extends AbstractYamlTest { Entity entity = Iterables.getOnlyElement(app.getChildren()); - entity.sensors().set(Sensors.newStringSensor("good_interpolation"), "run"); EntityAsserts.assertAttributeEventually(entity, Sensors.newSensor(Object.class, "mySum"), v -> v!=null); EntityAsserts.assertAttributeEquals(entity, Sensors.newSensor(Object.class, "mySum"), 7); } @@ -1048,11 +1050,80 @@ public class WorkflowYamlTest extends AbstractYamlTest { Entity entity = Iterables.getOnlyElement(app.getChildren()); - entity.sensors().set(Sensors.newStringSensor("good_interpolation"), "run"); EntityAsserts.assertAttributeEventually(entity, Sensors.newSensor(Object.class, "mySum"), v -> v!=null); EntityAsserts.assertAttributeEquals(entity, Sensors.newSensor(Object.class, "mySum"), 7); } + @Test + public void testSumListOfDurations() throws Exception { + // same as above except list is of durations + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicEntity.class.getName(), + " name: old-name", + " brooklyn.initializers:", + " - type: workflow-initializer", + " brooklyn.config:", + " name: post-init", + " steps:", + " - let duration d1 = 1d", + " - step: transform y", + " value:", + " - ${d1}", + " - 1h 1m", // if first is a duration the others will be coerced + " transform:", + " - sum", + " - to_string", + " - set-sensor my_total_duration = ${y}", + " - step: transform z", + " value:", + " - ${d1}", + " - 1d 1s", + " - ${d1}", + " transform:", + " - average", + " - to_string", + " - set-sensor my_average_duration = ${z}", + ""); + waitForApplicationTasks(app); + + Entity entity = Iterables.getOnlyElement(app.getChildren()); + + EntityAsserts.assertAttributeEventually(entity, Sensors.newSensor(Object.class, "my_total_duration"), v -> v!=null); + EntityAsserts.assertAttributeEquals(entity, Sensors.newSensor(Object.class, "my_total_duration"), "1d 1h 1m"); + + EntityAsserts.assertAttributeEventually(entity, Sensors.newSensor(Object.class, "my_average_duration"), v -> v!=null); + EntityAsserts.assertAttributeEquals(entity, Sensors.newSensor(Object.class, "my_average_duration"), "1d 333ms"); + } + + @Test + public void testAddDurationToInstant() throws Exception { + // same as above except list is of durations + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicEntity.class.getName(), + " name: old-name", + " brooklyn.initializers:", + " - type: workflow-initializer", + " brooklyn.config:", + " name: post-init", + " steps:", + " - let instant x = ${workflow.util.now_iso}", + " - let duration y = 7 days", + " - let in_a_week = ${x} + ${y}", + " - set-sensor in_a_week = ${in_a_week}", + ""); + waitForApplicationTasks(app); + + Entity entity = Iterables.getOnlyElement(app.getChildren()); + + EntityAsserts.assertAttributeEventually(entity, Sensors.newSensor(Object.class, "in_a_week"), v -> v!=null); + Object inAWeek = entity.sensors().get(Sensors.newSensor(Object.class, "in_a_week")); + Asserts.assertInstanceOf(inAWeek, Instant.class); + Asserts.assertThat((Instant)inAWeek, t -> t.isAfter(Instant.now().plus(6, ChronoUnit.DAYS))); + Asserts.assertThat((Instant)inAWeek, t -> t.isBefore(Instant.now().plus(8, ChronoUnit.DAYS))); + } + @Test public void testSetCurrentEntityName() throws Exception { Entity app = createAndStartApplication( 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 9e1c202521..e2ff30d695 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 @@ -33,11 +33,14 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.QuotedStringTokenizer; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Timestamp; import org.apache.commons.lang3.tuple.MutablePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; +import java.time.Instant; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -371,6 +374,9 @@ public class SetVariableWorkflowStep extends WorkflowStepDefinition { Object lhs = process(lhs0, null); Object rhs = process(rhs0, null); + if (lhs instanceof Instant) return TypeCoercions.coerce(rhs, Duration.class).addTo((Instant)lhs); + if (lhs instanceof Date) return new Timestamp((Instant) TypeCoercions.coerce(rhs, Duration.class).addTo( ((Date)lhs).toInstant() )); + Maybe<Integer> lhsI = asInteger(lhs); Maybe<Integer> rhsI = asInteger(rhs); if (lhsI.isPresent() && rhsI.isPresent()) { diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java index 5b795c3535..55efc1410a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java @@ -35,6 +35,7 @@ import org.apache.brooklyn.util.collections.*; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +58,7 @@ public class TransformVariableWorkflowStep extends WorkflowStepDefinition { public static final ConfigKey<TypedValueToSet> VARIABLE = ConfigKeys.newConfigKey(TypedValueToSet.class, "variable"); public static final ConfigKey<Object> VALUE = ConfigKeys.newConfigKey(Object.class, "value"); - public static final ConfigKey<String> TRANSFORM = ConfigKeys.newConfigKey(String.class, "transform"); + public static final ConfigKey<Object> TRANSFORM = ConfigKeys.newConfigKey(Object.class, "transform"); @Override @@ -83,8 +84,14 @@ public class TransformVariableWorkflowStep extends WorkflowStepDefinition { String name = context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_INPUT, variable.name, String.class); if (Strings.isBlank(name)) throw new IllegalArgumentException("Variable name is required"); - String transform = context.getInput(TRANSFORM); - List<String> transforms = MutableList.copyOf(Arrays.stream(transform.split("\\|")).map(String::trim).collect(Collectors.toList())); + Object transformO = context.getInput(TRANSFORM); + if (!(transformO instanceof Iterable)) transformO = MutableList.of(transformO); + List<String> transforms = MutableList.of(); + for (Object t: (Iterable)transformO) { + if (t instanceof String) transforms.addAll(MutableList.copyOf(Arrays.stream( ((String)t).split("\\|") ).map(String::trim).collect(Collectors.toList()))); + else throw new IllegalArgumentException("Argument to transform should be a string or list of strings, not: "+t); + } + Object v; if (input.containsKey(VALUE.getName())) v = input.get(VALUE.getName()); @@ -176,6 +183,7 @@ public class TransformVariableWorkflowStep extends WorkflowStepDefinition { if (v instanceof Supplier) return ((Supplier)v).get(); return v; }); + TRANSFORMATIONS.put("to_string", () -> v -> Strings.toString(v)); } static final Object minmax(Object v, String word, Predicate<Integer> test) { @@ -190,12 +198,21 @@ public class TransformVariableWorkflowStep extends WorkflowStepDefinition { return result; } - static final Number sum(Object v, String word) { + static final Object sum(Object v, String word) { if (v==null) return null; if (!(v instanceof Iterable)) throw new IllegalArgumentException("Value is not an iterable; cannot take "+word); + Iterable<?> vi = (Iterable<?>) v; + if (vi.iterator().hasNext() && vi.iterator().next() instanceof Duration) { + Duration result = Duration.ZERO; + for (Object vii: vi) { + result = result.add(TypeCoercions.coerce(vii, Duration.class)); + } + return result; + } + double result = 0; - for (Object vi: (Iterable)v) { - result += asDouble(vi).get(); + for (Object vii: vi) { + result += asDouble(vii).get(); } return simplifiedToIntOrLongIfPossible(result); } @@ -232,7 +249,11 @@ public class TransformVariableWorkflowStep extends WorkflowStepDefinition { if (v==null) return null; Integer count = size(v, "average"); if (count==null || count==0) throw new IllegalArgumentException("Value is empty; cannot take "+word); - return simplifiedToIntOrLongIfPossible(asDouble(sum(v, "average")).get() / count); + Object sum = sum(v, "average"); + if (sum instanceof Duration) { + return Duration.millis(Math.round(asDouble( ((Duration)sum).toMilliseconds() ).get() / count) ); + } + return simplifiedToIntOrLongIfPossible(asDouble(sum).get() / count); } @Override protected Boolean isDefaultIdempotent() { return true; } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/BrooklynTypeNameResolution.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/BrooklynTypeNameResolution.java index ee5568c444..b09db8e1be 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/flags/BrooklynTypeNameResolution.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/BrooklynTypeNameResolution.java @@ -45,6 +45,7 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.time.Instant; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -78,6 +79,7 @@ public class BrooklynTypeNameResolution { .put("duration", Duration.class) .put("timestamp", Timestamp.class) + .put("instant", Instant.class) .put("port", PortRange.class) .build(); diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowInputOutputExtensionTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowInputOutputExtensionTest.java index 2a7b48bd3e..81e3c5c0c5 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowInputOutputExtensionTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowInputOutputExtensionTest.java @@ -233,9 +233,9 @@ public class WorkflowInputOutputExtensionTest extends BrooklynMgmtUnitTestSuppor }; Asserts.assertEquals(transform.apply("min"), 1); Asserts.assertEquals(transform.apply("max"), 2); - Asserts.assertEquals(transform.apply("sum"), 3d); + Asserts.assertEquals(transform.apply("sum"), 3); Asserts.assertEquals(transform.apply("size"), 2); - Asserts.assertEquals(transform.apply("average"), 1.5d); + Asserts.assertEquals(transform.apply("average"), 1.5); Asserts.assertEquals(transform.apply("first"), 1); Asserts.assertEquals(transform.apply("last"), 2); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java index 92bcc682fc..20a2e78d02 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java @@ -22,12 +22,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.base.Function; import com.google.common.base.Preconditions; import static com.google.common.base.Preconditions.checkNotNull; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; + import com.google.common.base.Stopwatch; import java.io.Serializable; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Instant; +import java.time.temporal.Temporal; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.apache.brooklyn.util.text.Strings; @@ -346,4 +350,8 @@ public class Duration implements Comparable<Duration>, Serializable { return this; } + public Object addTo(Temporal temporal) { + return temporal.plus(nanos, NANOS); + } + }
