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 2015/04/21 22:41:30 UTC
[11/16] incubator-brooklyn git commit: simplify yaml for averaging +
summing enricher, plus test
simplify yaml for averaging + summing enricher, plus test
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5af29257
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5af29257
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5af29257
Branch: refs/heads/master
Commit: 5af2925722042561838e12b0d5902d2852eef431
Parents: ff31a41
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Apr 18 16:45:22 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sat Apr 18 17:21:23 2015 +0100
----------------------------------------------------------------------
.../main/java/brooklyn/enricher/Enrichers.java | 89 ++++++++++++++++++--
.../brooklyn/enricher/basic/Aggregator.java | 34 ++++++++
.../EnrichersSlightlySimplerYamlTest.java | 38 +++++++++
.../test-webapp-with-averaging-enricher.yaml | 47 +++++++++++
.../java/brooklyn/util/math/MathPredicates.java | 32 +++++++
5 files changed, 231 insertions(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/core/src/main/java/brooklyn/enricher/Enrichers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java
index 9b34b13..c8ee899 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -46,11 +46,13 @@ import brooklyn.util.flags.TypeCoercions;
import brooklyn.util.text.StringPredicates;
import brooklyn.util.text.Strings;
+import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -181,7 +183,8 @@ public class Enrichers {
protected final AttributeSensor<S> aggregating;
protected AttributeSensor<T> publishing;
protected Entity fromEntity;
- protected Function<? super Collection<S>, ? extends T> computing;
+ // use supplier so latest values of other fields can be used
+ protected Supplier<Function<? super Collection<S>, ? extends T>> computingSupplier;
protected Boolean fromMembers;
protected Boolean fromChildren;
protected Boolean excludingBlank;
@@ -216,13 +219,25 @@ public class Enrichers {
this.fromHardcodedProducers = ImmutableSet.copyOf(val);
return self();
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
public B computing(Function<? super Collection<S>, ? extends T> val) {
- this.computing = checkNotNull(val);
+ this.computingSupplier = (Supplier)Suppliers.ofInstance(checkNotNull(val));
return self();
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
public B computingSum() {
- // relies of TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+ this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Function<? super Collection<S>, ? extends T> get() {
+ // relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+ return (Function)new ComputingSum((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
+ }
+ };
+ return self();
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes", "unused" })
+ private B computingSumLegacy() {
+ // since 0.7.0, kept in case we need to rebind to this
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
@@ -230,9 +245,21 @@ public class Enrichers {
this.computing((Function)function);
return self();
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
+
public B computingAverage() {
- // relies of TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+ this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Function<? super Collection<S>, ? extends T> get() {
+ // relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+ return (Function)new ComputingAverage((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
+ }
+ };
+ return self();
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes", "unused" })
+ private B computingAverageLegacy() {
+ // since 0.7.0, kept in case we need to rebind to this
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
@@ -240,6 +267,7 @@ public class Enrichers {
this.computing((Function)function);
return self();
}
+
public B defaultValueForUnreportedSensors(S val) {
this.defaultValueForUnreportedSensors = val;
return self();
@@ -282,7 +310,7 @@ public class Enrichers {
.put(Aggregator.SOURCE_SENSOR, aggregating)
.putIfNotNull(Aggregator.FROM_CHILDREN, fromChildren)
.putIfNotNull(Aggregator.FROM_MEMBERS, fromMembers)
- .putIfNotNull(Aggregator.TRANSFORMATION, computing)
+ .putIfNotNull(Aggregator.TRANSFORMATION, computingSupplier.get())
.putIfNotNull(Aggregator.FROM_HARDCODED_PRODUCERS, fromHardcodedProducers)
.putIfNotNull(Aggregator.ENTITY_FILTER, entityFilter)
.putIfNotNull(Aggregator.VALUE_FILTER, valueFilter)
@@ -297,7 +325,7 @@ public class Enrichers {
.add("aggregating", aggregating)
.add("publishing", publishing)
.add("fromEntity", fromEntity)
- .add("computing", computing)
+ .add("computing", computingSupplier)
.add("fromMembers", fromMembers)
.add("fromChildren", fromChildren)
.add("excludingBlank", excludingBlank)
@@ -703,6 +731,49 @@ public class Enrichers {
}
}
+ @Beta
+ private abstract static class ComputingNumber<T extends Number> implements Function<Collection<T>, T> {
+ protected final Number defaultValueForUnreportedSensors;
+ protected final Number valueToReportIfNoSensors;
+ protected final TypeToken<T> typeToken;
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public ComputingNumber(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+ this.defaultValueForUnreportedSensors = defaultValueForUnreportedSensors;
+ this.valueToReportIfNoSensors = valueToReportIfNoSensors;
+ if (typeToken!=null && TypeToken.of(Number.class).isAssignableFrom(typeToken.getType())) {
+ this.typeToken = typeToken;
+ } else if (typeToken==null || typeToken.isAssignableFrom(Number.class)) {
+ // use double if e.g. Object is supplied
+ this.typeToken = (TypeToken)TypeToken.of(Double.class);
+ } else {
+ throw new IllegalArgumentException("Type "+typeToken+" is not valid for "+this);
+ }
+ }
+ @Override public abstract T apply(Collection<T> input);
+ }
+
+ @Beta
+ public static class ComputingSum<T extends Number> extends ComputingNumber<T> {
+ public ComputingSum(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+ super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
+ }
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override public T apply(Collection<T> input) {
+ return (T) sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
+ }
+ }
+
+ @Beta
+ public static class ComputingAverage<T extends Number> extends ComputingNumber<T> {
+ public ComputingAverage(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+ super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
+ }
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override public T apply(Collection<T> input) {
+ return (T) average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
+ }
+ }
+
protected static <T extends Number> T average(Collection<T> vals, Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> type) {
Double doubleValueToReportIfNoSensors = (valueToReportIfNoSensors == null) ? null : valueToReportIfNoSensors.doubleValue();
int count = count(vals, defaultValueForUnreportedSensors!=null);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
index cb80431..d7f4fca 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import brooklyn.config.BrooklynLogging;
import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.event.AttributeSensor;
@@ -38,6 +39,7 @@ import brooklyn.event.SensorEventListener;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
import brooklyn.util.text.StringPredicates;
import com.google.common.base.Function;
@@ -54,7 +56,13 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class);
public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
+
+ @SetFromFlag("transformation")
+ public static final ConfigKey<Object> TRANSFORMATION_UNTYPED = ConfigKeys.newConfigKey(Object.class, "enricher.transformation.untyped",
+ "Specifies a transformation, as a function from a collection to the value, or as a string matching a pre-defined named transformation, "
+ + "such as 'average' (for numbers), 'add' (for numbers), or 'list' (the default, putting any collection of items into a list)");
public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {}, "enricher.transformation");
+
public static final ConfigKey<Boolean> EXCLUDE_BLANK = ConfigKeys.newBooleanConfigKey("enricher.aggregator.excludeBlank", "Whether explicit nulls or blank strings should be excluded (default false); this only applies if no value filter set", false);
protected Sensor<T> sourceSensor;
@@ -73,9 +81,35 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
protected void setEntityLoadingConfig() {
super.setEntityLoadingConfig();
this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);
+
+ Object t1 = config().get(TRANSFORMATION_UNTYPED);
+ if (t1 instanceof String) t1 = lookupTransformation((String)t1);
+
this.transformation = (Function<? super Collection<T>, ? extends U>) config().get(TRANSFORMATION);
+ if (this.transformation==null) {
+ this.transformation = (Function<? super Collection<T>, ? extends U>) t1;
+ } else if (t1!=null && !t1.equals(this.transformation)) {
+ throw new IllegalStateException("Cannot supply both "+TRANSFORMATION_UNTYPED+" and "+TRANSFORMATION+" unless they are equal.");
+ }
}
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ protected Function<? super Collection<?>, ?> lookupTransformation(String t1) {
+ if ("average".equalsIgnoreCase(t1)) return new Enrichers.ComputingAverage(null, null, targetSensor.getTypeToken());
+ if ("sum".equalsIgnoreCase(t1)) return new Enrichers.ComputingAverage(null, null, targetSensor.getTypeToken());
+ if ("list".equalsIgnoreCase(t1)) return new ComputingList();
+ return null;
+ }
+
+ private class ComputingList<TT> implements Function<Collection<TT>, List<TT>> {
+ @Override
+ public List<TT> apply(Collection<TT> input) {
+ if (input==null) return null;
+ return MutableList.copyOf(input).asUnmodifiable();
+ }
+
+ }
+
@Override
protected void setEntityBeforeSubscribingProducerChildrenEvents() {
BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer),
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
index 99165c5..9001824 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
@@ -21,6 +21,7 @@ package io.brooklyn.camp.brooklyn;
import java.net.URI;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,9 +33,12 @@ import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.webapp.JavaWebAppSoftwareProcess;
import brooklyn.event.basic.Sensors;
import brooklyn.test.EntityTestUtils;
import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.math.MathPredicates;
import brooklyn.util.text.StringPredicates;
import com.google.common.base.Predicate;
@@ -88,6 +92,40 @@ public class EnrichersSlightlySimplerYamlTest extends AbstractYamlTest {
// TODO would we want to allow "all-but-usual" as the default if nothing specified
}
+ @Test(groups="Integration")
+ public void testWebappWithAveragingEnricher() throws Exception {
+ Entity app = createAndStartApplication(loadYaml("test-webapp-with-averaging-enricher.yaml"));
+ waitForApplicationTasks(app);
+ log.info("Started "+app+":");
+ Entities.dumpInfo(app);
+
+ List<JavaWebAppSoftwareProcess> appservers = MutableList.copyOf(Entities.descendants(app, JavaWebAppSoftwareProcess.class));
+ Assert.assertEquals(appservers.size(), 3);
+
+ EntityInternal srv0 = (EntityInternal) appservers.get(0);
+ EntityInternal dwac = (EntityInternal) srv0.getParent();
+ EntityInternal cdwac = (EntityInternal) dwac.getParent();
+
+ srv0.setAttribute(Sensors.newDoubleSensor("my.load"), 20.0);
+
+ EntityTestUtils.assertAttributeEventually(dwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+ MathPredicates.equalsApproximately(20));
+ EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+ MathPredicates.equalsApproximately(20));
+
+ srv0.setAttribute(Sensors.newDoubleSensor("my.load"), null);
+ EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+ Predicates.isNull());
+
+ ((EntityInternal) appservers.get(1)).setAttribute(Sensors.newDoubleSensor("my.load"), 10.0);
+ ((EntityInternal) appservers.get(2)).setAttribute(Sensors.newDoubleSensor("my.load"), 20.0);
+ EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+ MathPredicates.equalsApproximately(15));
+ srv0.setAttribute(Sensors.newDoubleSensor("my.load"), 0.0);
+ EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+ MathPredicates.equalsApproximately(10));
+ }
+
@Override
protected Logger getLogger() {
return log;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml b/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
new file mode 100644
index 0000000..d4fc6ee
--- /dev/null
+++ b/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+# example showing how enrichers can be set
+#
+name: test-webapp-with-averaging-enricher
+description: Testing many enrichers
+services:
+- type: brooklyn.entity.webapp.ControlledDynamicWebAppCluster
+ initialSize: 3
+ location: localhost
+
+ # define the web cluster, adding an averaging enricher to the cluster.
+ # this assumes the test fixture will set the "my.load" sensor on the member-specs in here.
+ webClusterSpec:
+ $brooklyn:entitySpec:
+ type: brooklyn.entity.webapp.DynamicWebAppCluster
+ id: cluster
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Aggregator
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("my.load")
+ enricher.targetSensor: $brooklyn:sensor("my.load.averaged")
+ enricher.aggregating.fromMembers: true
+ transformation: average
+
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Propagator
+ brooklyn.config:
+ producer: $brooklyn:entity("cluster")
+ propagating:
+ - $brooklyn:sensor("my.load.averaged")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java b/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
index 7a5b325..41f4024 100644
--- a/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
+++ b/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
@@ -20,6 +20,7 @@ package brooklyn.util.math;
import javax.annotation.Nullable;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
public class MathPredicates {
@@ -71,4 +72,35 @@ public class MathPredicates {
}
};
}
+
+ /**
+ * Creates a predicate comparing a given number with {@code val}.
+ * A number of {@code null} passed to the predicate will always return false.
+ */
+ public static <T extends Number> Predicate<T> equalsApproximately(final Number val, final double delta) {
+ return new EqualsApproximately<T>(val, delta);
+ }
+ /** Convenience for {@link #equalsApproximately(double,double)} with a delta of 10^{-6}. */
+ public static <T extends Number> Predicate<T> equalsApproximately(final Number val) {
+ return equalsApproximately(val, 0.0000001);
+ }
+
+ private static final class EqualsApproximately<T extends Number> implements Predicate<T> {
+ private final double val;
+ private final double delta;
+ private EqualsApproximately(Number val, double delta) {
+ this.val = val.doubleValue();
+ Preconditions.checkArgument(delta>=0, "delta must be non-negative");
+ this.delta = delta;
+ }
+ public boolean apply(@Nullable T input) {
+ return (input == null) ? false : Math.abs(input.doubleValue() - val) <= delta;
+ }
+ @Override
+ public String toString() {
+ return "equals-approximately("+val+" +- "+delta+")";
+ }
+ }
+
+
}