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+")";
+        }
+    }
+
+
 }