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 2014/08/30 01:01:01 UTC

[01/26] git commit: prototype of service_up being derived from service_not_up_indicators

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 74b15c0ef -> 761f471ab


prototype of service_up being derived from service_not_up_indicators


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/238fab7f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/238fab7f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/238fab7f

Branch: refs/heads/master
Commit: 238fab7f0f520a462175ca88b2a807c081e32145
Parents: 0be9f25
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 11:16:23 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Aug 25 09:32:25 2014 +0100

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/Attributes.java  |  6 ++++
 .../brooklyn/entity/basic/SoftwareProcess.java  |  3 ++
 .../entity/basic/SoftwareProcessImpl.java       | 34 +++++++++++++++++---
 3 files changed, 39 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/238fab7f/core/src/main/java/brooklyn/entity/basic/Attributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Attributes.java b/core/src/main/java/brooklyn/entity/basic/Attributes.java
index 3752752..4453cad 100644
--- a/core/src/main/java/brooklyn/entity/basic/Attributes.java
+++ b/core/src/main/java/brooklyn/entity/basic/Attributes.java
@@ -31,6 +31,7 @@ import brooklyn.event.basic.Sensors;
 import brooklyn.util.net.UserAndHostAndPort;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
 
 /**
  * This interface should be used to access {@link Sensor} definitions.
@@ -91,6 +92,11 @@ public interface Attributes {
      */
     AttributeSensor<Boolean> SERVICE_UP = Sensors.newBooleanSensor("service.isUp", 
             "Whether the service is active and availability (confirmed and monitored)");
+    @SuppressWarnings("serial")
+    AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Sensors.newSensor(
+        new TypeToken<Map<String,Object>>() {},
+        "service.notUp.indicators", 
+        "A map of namespaced indicators that the service is not up");
     
     AttributeSensor<Lifecycle> SERVICE_STATE = Sensors.newSensor(Lifecycle.class,
             "service.state", "Expected lifecycle state of the service");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/238fab7f/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
index 368619d..eb90660 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
@@ -153,6 +153,9 @@ public interface SoftwareProcess extends Entity, Startable {
     public static final AttributeSensor<MachineProvisioningLocation> PROVISIONING_LOCATION = new BasicAttributeSensor<MachineProvisioningLocation>(
             MachineProvisioningLocation.class, "softwareservice.provisioningLocation", "Location used to provision a machine where this is running");
 
+    public static final AttributeSensor<Boolean> SERVICE_PROCESS_IS_RUNNING = Sensors.newBooleanSensor("service.process.isRunning", 
+        "Whether the process for the service is confirmed as running");
+    
     public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
  
     public static final AttributeSensor<String> PID_FILE = Sensors.newStringSensor("softwareprocess.pid.file", "PID file");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/238fab7f/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index 741cb4d..fb872b0 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -32,6 +32,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
 import brooklyn.entity.drivers.DriverDependentEntity;
 import brooklyn.entity.drivers.EntityDriverManager;
@@ -45,6 +46,7 @@ import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.location.basic.Machines;
 import brooklyn.location.cloud.CloudLocationConfig;
 import brooklyn.management.Task;
+import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.config.ConfigBag;
@@ -55,6 +57,7 @@ import brooklyn.util.time.CountdownTimer;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 
+import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -72,7 +75,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
 	private transient SoftwareProcessDriver driver;
 
     /** @see #connectServiceUpIsRunning() */
-    private volatile FunctionFeed serviceUp;
+    private volatile FunctionFeed serviceProcessIsRunning;
 
     private static final SoftwareProcessDriverLifecycleEffectorTasks LIFECYCLE_TASKS =
             new SoftwareProcessDriverLifecycleEffectorTasks();
@@ -148,10 +151,10 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
      * @see #disconnectServiceUpIsRunning()
      */
     protected void connectServiceUpIsRunning() {
-        serviceUp = FunctionFeed.builder()
+        serviceProcessIsRunning = FunctionFeed.builder()
                 .entity(this)
                 .period(5000)
-                .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_UP)
+                .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING)
                         .onException(Functions.constant(Boolean.FALSE))
                         .callable(new Callable<Boolean>() {
                             public Boolean call() {
@@ -159,6 +162,29 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
                             }
                         }))
                 .build();
+
+        // FIXME quick-and-dirty change
+        Function<Boolean, Map<String,Object>> f = new Function<Boolean, Map<String,Object>>() {
+            @Override
+            public Map<String, Object> apply(Boolean input) {
+                Map<String, Object> result = getAttribute(Attributes.SERVICE_NOT_UP_INDICATORS);
+                if (result==null) result = MutableMap.of();
+                // TODO only change/publish if it needs changing...
+                if (Boolean.TRUE.equals(input)) {
+                    result.remove(SERVICE_PROCESS_IS_RUNNING.getName());
+                    return result;
+                } else {
+                    result.put(SERVICE_PROCESS_IS_RUNNING.getName(), "Process not running (according to driver checkRunning)");                    
+                    return result;
+                }
+            }
+        };
+        addEnricher(Enrichers.builder().transforming(SERVICE_PROCESS_IS_RUNNING).publishing(Attributes.SERVICE_NOT_UP_INDICATORS)
+            .computing(f).build());
+        
+        // FIXME lives elsewhere
+        addEnricher(Enrichers.builder().transforming(Attributes.SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
+            .computing( Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)) ).build());
     }
 
     /**
@@ -169,7 +195,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
      * @see #connectServiceUpIsRunning()
      */
     protected void disconnectServiceUpIsRunning() {
-        if (serviceUp != null) serviceUp.stop();
+        if (serviceProcessIsRunning != null) serviceProcessIsRunning.stop();
     }
 
     /**


[16/26] git commit: default children/members enrichers for service_up and service_problems ignores entities that report null as well as those that don't report; this can be overridden with IGNORE_{NULL,TRANSITIONING} config keys. and the load balancing p

Posted by he...@apache.org.
default children/members enrichers for service_up and service_problems ignores entities that report null as well as those that don't report; this can be overridden with IGNORE_{NULL,TRANSITIONING} config keys.
and the load balancing policy defaults not to blocking service up simply because children/members are not present. misc other tidies around policies.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/d3886a05
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/d3886a05
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/d3886a05

Branch: refs/heads/master
Commit: d3886a055353f2efa435c84d45668c1a3ab0aa23
Parents: cbc103a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 25 10:48:19 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:18 2014 -0400

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/Entities.java    |  5 +--
 .../brooklyn/entity/basic/EntityConfigMap.java  |  8 ++---
 .../java/brooklyn/entity/basic/QuorumCheck.java |  3 ++
 .../entity/basic/ServiceStateLogic.java         | 36 +++++++++++++++-----
 .../loadbalancing/BalanceableContainer.java     | 13 +++++--
 .../loadbalancing/BalanceableWorkerPool.java    |  3 ++
 .../BalanceableWorkerPoolImpl.java              |  6 ++--
 .../loadbalancing/LoadBalancingPolicy.java      | 17 +++++----
 .../AbstractLoadBalancingPolicyTest.java        |  2 +-
 .../loadbalancing/LoadBalancingPolicyTest.java  |  4 ++-
 .../loadbalancing/MockContainerEntity.java      |  2 +-
 .../loadbalancing/MockContainerEntityImpl.java  |  3 +-
 12 files changed, 71 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/core/src/main/java/brooklyn/entity/basic/Entities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index ea35432..4191110 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -281,7 +281,7 @@ public class Entities {
         dumpInfo(e, new PrintWriter(System.out), currentIndentation, tab);
     }
     public static void dumpInfo(Entity e, Writer out, String currentIndentation, String tab) throws IOException {
-        out.append(currentIndentation+e.toString()+"\n");
+        out.append(currentIndentation+e.toString()+" "+e.getId()+"\n");
 
         out.append(currentIndentation+tab+tab+"locations = "+e.getLocations()+"\n");
 
@@ -332,7 +332,8 @@ public class Entities {
         if (e instanceof Group) {
             StringBuilder members = new StringBuilder();
             for (Entity it : ((Group)e).getMembers()) {
-                members.append(it.getId()+", ");
+                if (members.length()>0) members.append(", ");
+                members.append(it.getId());
             }
             out.append(currentIndentation+tab+tab+"Members: "+members.toString()+"\n");
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
index c946fe0..3f5884b 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
@@ -102,8 +102,6 @@ public class EntityConfigMap implements ConfigMap {
         //           but that example doesn't have a default...
         ConfigKey<T> ownKey = entity!=null ? (ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : key;
         
-        ExecutionContext exec = entity.getExecutionContext();
-
         // TODO We're notifying of config-changed because currently persistence needs to know when the
         // attributeWhenReady is complete (so it can persist the result).
         // Long term, we'll just persist tasks properly so the call to onConfigChanged will go!
@@ -114,11 +112,13 @@ public class EntityConfigMap implements ConfigMap {
             T result = null;
             boolean complete = false;
             if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) {
+                ExecutionContext exec = entity.getExecutionContext();
                 result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec);
                 complete = true;
             } else if (((ConfigKeySelfExtracting<T>)ownKey).isSet(inheritedConfig)) {
-               result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec);
-               complete = true;
+                ExecutionContext exec = entity.getExecutionContext();
+                result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec);
+                complete = true;
             } else if (localConfigBag.containsKey(ownKey)) {
                 // TODO configBag.get doesn't handle tasks/attributeWhenReady - it only uses TypeCoercions
                 result = localConfigBag.get(ownKey);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
index bac4515..17195e8 100644
--- a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
+++ b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
@@ -39,6 +39,9 @@ public interface QuorumCheck {
         public static QuorumCheck atLeastOneUnlessEmpty() {
             return new NumericQuorumCheck(1, 0.0, true);
         }
+        public static QuorumCheck alwaysTrue() {
+            return new NumericQuorumCheck(0, 0.0, true);
+        }
         public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
             return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index de4f1ad..9e66381 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -25,6 +25,9 @@ import java.util.Map;
 
 import javax.annotation.Nullable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.enricher.Enrichers;
 import brooklyn.enricher.basic.AbstractEnricher;
@@ -55,6 +58,8 @@ import com.google.common.collect.ImmutableList;
 /** Logic, sensors and enrichers, and conveniences, for computing service status */ 
 public class ServiceStateLogic {
 
+    private static final Logger log = LoggerFactory.getLogger(ServiceStateLogic.class);
+    
     public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP;
     public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS;
     
@@ -206,6 +211,7 @@ public class ServiceStateLogic {
         }
 
         protected void setActualState(@Nullable Lifecycle state) {
+            if (log.isTraceEnabled()) log.trace("{} setting actual state {}", this, state);
             emit(SERVICE_STATE_ACTUAL, (state==null ? Entities.REMOVE : state));
         }
     }
@@ -253,6 +259,8 @@ public class ServiceStateLogic {
             "Logic for checking whether this service is healthy, based on children and/or members running, defaulting to requiring none to be ON-FIRE", QuorumCheck.QuorumChecks.all());
         public static final ConfigKey<Boolean> DERIVE_SERVICE_NOT_UP = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_up.publish", "Whether to derive a service-not-up indicator from children", true);
         public static final ConfigKey<Boolean> DERIVE_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_problems.publish", "Whether to derive a service-problem indicator from children", true);
+        public static final ConfigKey<Boolean> IGNORE_NULL_VALUES = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_nulls", "Whether to ignore children reporting null values for the sensor", true);
+        public static final ConfigKey<Boolean> IGNORE_TRANSITIONING = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_transitioning", "Whether to ignore children reporting transitional states (starting, stopping, etc) for service state", true);
 
         protected String getKeyForMapSensor() {
             return Preconditions.checkNotNull(super.getUniqueTag());
@@ -304,7 +312,12 @@ public class ServiceStateLogic {
         protected Object computeServiceNotUp() {
             Map<Entity, Boolean> values = getValues(SERVICE_UP);
             List<Entity> violators = MutableList.of();
+            boolean ignoreNull = getConfig(IGNORE_NULL_VALUES);
+            int entries=0;
             for (Map.Entry<Entity, Boolean> state: values.entrySet()) {
+                if (ignoreNull && state.getValue()==null)
+                    continue;
+                entries++;
                 if (!Boolean.TRUE.equals(state.getValue())) {
                     violators.add(state.getKey());
                 }
@@ -312,11 +325,12 @@ public class ServiceStateLogic {
 
             QuorumCheck qc = getConfig(UP_QUORUM_CHECK);
             if (qc!=null) {
-                if (qc.isQuorate(values.size()-violators.size(), values.size()))
+                if (qc.isQuorate(entries-violators.size(), entries))
                     // quorate
                     return null;
 
                 if (values.isEmpty()) return "No entities present";
+                if (entries==0) return "No entities publishing service up";
                 if (violators.isEmpty()) return "Not enough entities";
             } else {
                 if (violators.isEmpty())
@@ -324,35 +338,41 @@ public class ServiceStateLogic {
             }
 
             if (violators.size()==1) return violators.get(0)+" is not up";
-            if (violators.size()==values.size()) return "None of the entities are up";
+            if (violators.size()==entries) return "None of the entities are up";
             return violators.size()+" entities are not up, including "+violators.get(0);
         }
 
         protected Object computeServiceProblems() {
             Map<Entity, Lifecycle> values = getValues(SERVICE_STATE_ACTUAL);
-            int numRunning=0, numOnFire=0;
+            int numRunning=0, numNotHealthy=0;
+            boolean ignoreNull = getConfig(IGNORE_NULL_VALUES);
+            boolean ignoreTransition = getConfig(IGNORE_TRANSITIONING);
             for (Lifecycle state: values.values()) {
                 if (state==Lifecycle.RUNNING) numRunning++;
-                else if (state==Lifecycle.ON_FIRE) numOnFire++;
+                else if (state==Lifecycle.ON_FIRE) numNotHealthy++;
+                else if (state==null) { if (!ignoreNull) numNotHealthy++; }
+                else { if (!ignoreTransition) numNotHealthy++; }
             }
 
             QuorumCheck qc = getConfig(RUNNING_QUORUM_CHECK);
             if (qc!=null) {
-                if (qc.isQuorate(numRunning, numOnFire+numRunning))
+                if (qc.isQuorate(numRunning, numNotHealthy+numRunning))
                     // quorate
                     return null;
 
-                if (numOnFire==0)
+                if (numNotHealthy==0)
                     return "Not enough entities running to be quorate";
             } else {
-                if (numOnFire==0)
+                if (numNotHealthy==0)
                     return null;
             }
 
-            return numOnFire+" entit"+Strings.ies(numOnFire)+" are on fire";
+            return numNotHealthy+" entit"+Strings.ies(numNotHealthy)+" not healthy";
         }
 
         protected void updateMapSensor(AttributeSensor<Map<String, Object>> sensor, Object value) {
+            if (log.isTraceEnabled()) log.trace("{} updating map sensor {} with {}", new Object[] { this, sensor, value });
+
             if (value!=null)
                 updateMapSensorEntry(entity, sensor, getKeyForMapSensor(), value);
             else

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
index 56969d0..9a7ecb1 100644
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
+++ b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
@@ -20,7 +20,12 @@ package brooklyn.policy.loadbalancing;
 
 import java.util.Set;
 
+import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.QuorumCheck;
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
 import brooklyn.event.basic.BasicNotificationSensor;
 
 /**
@@ -28,14 +33,18 @@ import brooklyn.event.basic.BasicNotificationSensor;
  * Membership of a balanceable container does not imply a parent-child relationship in the Brooklyn
  * management sense.
  */
-public interface BalanceableContainer<ItemType extends Movable> extends Entity {
+public interface BalanceableContainer<ItemType extends Movable> extends Entity, AbstractGroup {
     
     public static BasicNotificationSensor<Entity> ITEM_ADDED = new BasicNotificationSensor<Entity>(
             Entity.class, "balanceablecontainer.item.added", "Movable item added to balanceable container");
     public static BasicNotificationSensor<Entity> ITEM_REMOVED = new BasicNotificationSensor<Entity>(
             Entity.class, "balanceablecontainer.item.removed", "Movable item removed from balanceable container");
     
-    
+    public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(AbstractGroup.UP_QUORUM_CHECK, 
+        "Up check from members; default one for container overrides usual check to always return true, "
+        + "i.e. not block service up simply because the container is empty or something in the container has failed",
+        QuorumChecks.alwaysTrue());
+
     public Set<ItemType> getBalanceableItems();
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
index 023edbe..ab18ce6 100644
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
+++ b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
@@ -32,6 +32,9 @@ import brooklyn.event.basic.BasicNotificationSensor;
  * Represents an elastic group of "container" entities, each of which is capable of hosting "item" entities that perform
  * work and consume the container's available resources (e.g. CPU or bandwidth). Auto-scaling and load-balancing policies can
  * be attached to this pool to provide dynamic elasticity based on workrates reported by the individual item entities.
+ * <p>
+ * The containers must be "up" in order to receive work, thus they must NOT follow the default enricher pattern
+ * for groups which says that the group must be up to receive work.
  */
 @ImplementedBy(BalanceableWorkerPoolImpl.class)
 public interface BalanceableWorkerPool extends Entity, Resizable {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
index 905411e..bb7b64b 100644
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
+++ b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
@@ -36,9 +36,7 @@ import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 
 /**
- * Represents an elastic group of "container" entities, each of which is capable of hosting "item" entities that perform
- * work and consume the container's available resources (e.g. CPU or bandwidth). Auto-scaling and load-balancing policies can
- * be attached to this pool to provide dynamic elasticity based on workrates reported by the individual item entities.
+ * @see BalanceableWorkerPool
  */
 public class BalanceableWorkerPoolImpl extends AbstractEntity implements BalanceableWorkerPool {
 
@@ -60,7 +58,7 @@ public class BalanceableWorkerPoolImpl extends AbstractEntity implements Balance
             if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", BalanceableWorkerPoolImpl.this, event);
             Entity source = event.getSource();
             Object value = event.getValue();
-            Sensor sensor = event.getSensor();
+            Sensor<?> sensor = event.getSensor();
             
             if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
                 if (source.equals(containerGroup)) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java b/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
index 6738d6c..7cef21d 100644
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
+++ b/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
@@ -87,9 +87,11 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
     private volatile long executorTime = 0;
 
     private int lastEmittedDesiredPoolSize = 0;
-    private String lastEmittedPoolTemperature = null; // "cold" or "hot"
+    private static enum TemperatureStates { COLD, HOT }
+    private TemperatureStates lastEmittedPoolTemperature = null; // "cold" or "hot"
     
     private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+        @SuppressWarnings({ "rawtypes", "unchecked" })
         public void onEvent(SensorEvent<Object> event) {
             if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", LoadBalancingPolicy.this, event);
             Entity source = event.getSource();
@@ -119,6 +121,7 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
             BalanceablePoolModel<NodeType, ItemType> model) {
         this(MutableMap.of(), metric, model);
     }
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public LoadBalancingPolicy(Map props, AttributeSensor<? extends Number> metric,
             BalanceablePoolModel<NodeType, ItemType> model) {
         
@@ -133,6 +136,7 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
         executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
     }
     
+    @SuppressWarnings("unchecked")
     @Override
     public void setEntity(EntityLocal entity) {
         Preconditions.checkArgument(entity instanceof BalanceableWorkerPool, "Provided entity must be a BalanceableWorkerPool");
@@ -185,12 +189,13 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
             long delay = Math.max(0, (executorTime + minPeriodBetweenExecs) - now);
             
             executor.schedule(new Runnable() {
+                @SuppressWarnings("rawtypes")
                 public void run() {
                     try {
                         executorTime = System.currentTimeMillis();
                         executorQueued.set(false);
                         strategy.rebalance();
-                        
+
                         if (LOG.isDebugEnabled()) LOG.debug("{} post-rebalance: poolSize={}; workrate={}; lowThreshold={}; " + 
                                 "highThreshold={}", new Object[] {this, model.getPoolSize(), model.getCurrentPoolWorkrate(), 
                                 model.getPoolLowThreshold(), model.getPoolHighThreshold()});
@@ -206,10 +211,10 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
                             
                             if (LOG.isInfoEnabled()) {
                                 int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolLowThreshold()/model.getPoolSize()));
-                                if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != "cold") {
+                                if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.COLD) {
                                     LOG.info("{} emitted COLD (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
                                     lastEmittedDesiredPoolSize = desiredPoolSize;
-                                    lastEmittedPoolTemperature = "cold";
+                                    lastEmittedPoolTemperature = TemperatureStates.COLD;
                                 }
                             }
                         
@@ -224,10 +229,10 @@ public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movab
                             
                             if (LOG.isInfoEnabled()) {
                                 int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolHighThreshold()/model.getPoolSize()));
-                                if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != "hot") {
+                                if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.HOT) {
                                     LOG.info("{} emitted HOT (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
                                     lastEmittedDesiredPoolSize = desiredPoolSize;
-                                    lastEmittedPoolTemperature = "hot";
+                                    lastEmittedPoolTemperature = TemperatureStates.HOT;
                                 }
                             }
                         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
index 0cd05a0..244d8fa 100644
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
+++ b/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
@@ -91,7 +91,7 @@ public class AbstractLoadBalancingPolicyTest {
         
         model = new DefaultBalanceablePoolModel<Entity, Entity>("pool-model");
         
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        app = TestApplication.Factory.newManagedInstanceForTests();
         containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
                 .displayName("containerGroup")
                 .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
index df7d300..c0ff7b7 100644
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
+++ b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
@@ -27,6 +27,8 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.test.Asserts;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -179,7 +181,7 @@ public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
         
         MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 30, CONTAINER_STARTUP_DELAY_MS);
         // New container allows hot ones to offload work.
-        
+
         assertWorkratesEventually(
                 ImmutableList.of(containerA, containerB, containerC), 
                 ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8), 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
index 1de0d47..2fba8f0 100644
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
+++ b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
@@ -41,7 +41,7 @@ public interface MockContainerEntity extends AbstractGroup, BalanceableContainer
     public static final ConfigKey<Long> DELAY = new BasicConfigKey<Long>(
             Long.class, "mock.container.delay", "", 0L);
 
-    public static final Effector OFFLOAD_AND_STOP = new MethodEffector(MockContainerEntity.class, "offloadAndStop");
+    public static final Effector<Void> OFFLOAD_AND_STOP = new MethodEffector<Void>(MockContainerEntity.class, "offloadAndStop");
 
     public void lock();
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3886a05/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
index 13baec5..09906d0 100644
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
+++ b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
@@ -116,6 +116,7 @@ public class MockContainerEntityImpl extends AbstractGroupImpl implements MockCo
         emit(BalanceableContainer.ITEM_REMOVED, item);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public Set<Movable> getBalanceableItems() {
         return (Set) Sets.newLinkedHashSet(getMembers());
@@ -137,8 +138,6 @@ public class MockContainerEntityImpl extends AbstractGroupImpl implements MockCo
             if (getDelay() > 0) Time.sleep(getDelay());
             running = true;
             addLocations(locs);
-            Location loc = Iterables.get(locs, 0);
-            String locName = (loc.getDisplayName() != null) ? loc.getDisplayName() : loc.toString();
             emit(Attributes.LOCATION_CHANGED, null);
             setAttribute(SERVICE_UP, true);
         } finally {


[10/26] git commit: simple cleaned up illustration of service_up from not_up computation

Posted by he...@apache.org.
simple cleaned up illustration of service_up from not_up computation


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/843f1fca
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/843f1fca
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/843f1fca

Branch: refs/heads/master
Commit: 843f1fcaa28331e5b3e9b3325899366d0588601f
Parents: 41deca4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 23:47:59 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:49 2014 -0400

----------------------------------------------------------------------
 .../entity/basic/ServiceStatusLogic.java        | 73 ++++++++++++++++++++
 .../entity/basic/SoftwareProcessImpl.java       | 13 ++--
 .../entity/webapp/jboss/JBoss7ServerImpl.java   | 16 +++--
 3 files changed, 89 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/843f1fca/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
new file mode 100644
index 0000000..056334c
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
@@ -0,0 +1,73 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import java.util.Map;
+
+import brooklyn.enricher.Enrichers;
+import brooklyn.event.AttributeSensor;
+import brooklyn.policy.EnricherSpec;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Functions;
+
+/** Logic, sensors and enrichers, and conveniences, for computing service status */ 
+public class ServiceStatusLogic {
+
+    public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP;
+    public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS;
+    
+    public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
+        return Enrichers.builder()
+            .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
+            .computing( Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)) )
+            .uniqueTag("service.isUp if no service.notUp.indicators")
+            .build();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <TKey,TVal> void updateMapSensor(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor,
+            TKey key, Object v) {
+        Map<TKey, TVal> map = entity.getAttribute(sensor);
+
+        // TODO synchronize
+        
+        boolean created = (map==null);
+        if (created) map = MutableMap.of();
+                
+        boolean changed;
+        if (v == Entities.REMOVE) {
+            changed = map.containsKey(key);
+            if (changed)
+                map.remove(key);
+        } else {
+            TVal oldV = map.get(key);
+            if (oldV==null)
+                changed = (v!=null || !map.containsKey(key));
+            else
+                changed = !oldV.equals(v);
+            if (changed)
+                map.put(key, (TVal)v);
+        }
+        if (changed || created)
+            entity.setAttribute(sensor, map);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/843f1fca/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index 56bb319..e376bee 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -46,7 +46,6 @@ import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.location.basic.Machines;
 import brooklyn.location.cloud.CloudLocationConfig;
 import brooklyn.management.Task;
-import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.config.ConfigBag;
@@ -153,7 +152,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     protected void connectServiceUpIsRunning() {
         serviceProcessIsRunning = FunctionFeed.builder()
                 .entity(this)
-                .period(5000)
+                .period(Duration.FIVE_SECONDS)
                 .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING)
                         .onException(Functions.constant(Boolean.FALSE))
                         .callable(new Callable<Boolean>() {
@@ -165,12 +164,11 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
 
         addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
             .from(SERVICE_PROCESS_IS_RUNNING)
-            .computing(Functionals.when(false).value("Process not running (according to driver checkRunning)"))
+            .computing(Functionals.when(false).value("Process not running (according to driver checkRunning)")
+                .when((Boolean)null).value("Process not running (no data for "+SERVICE_PROCESS_IS_RUNNING.getName()+")") )
             .build());
-
-        // FIXME lives elsewhere
-        addEnricher(Enrichers.builder().transforming(Attributes.SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
-            .computing( Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)) ).build());
+        
+        addEnricher(ServiceStatusLogic.newEnricherForServiceUpIfNoNotUpIndicators());
     }
 
     /**
@@ -182,6 +180,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
      */
     protected void disconnectServiceUpIsRunning() {
         if (serviceProcessIsRunning != null) serviceProcessIsRunning.stop();
+        ServiceStatusLogic.updateMapSensor(this, Attributes.SERVICE_NOT_UP_INDICATORS, SERVICE_PROCESS_IS_RUNNING.getName(), "Disabled checking whether service process is running");
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/843f1fca/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
index 2cb1303..05421d0 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.webapp.HttpsSslConfig;
 import brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
 import brooklyn.entity.webapp.WebAppServiceMethods;
@@ -33,8 +34,10 @@ import brooklyn.event.feed.http.HttpPollConfig;
 import brooklyn.event.feed.http.HttpValueFunctions;
 import brooklyn.location.access.BrooklynAccessUtils;
 import brooklyn.policy.Enricher;
+import brooklyn.util.guava.Functionals;
 
 import com.google.common.base.Functions;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.net.HostAndPort;
 
@@ -43,7 +46,6 @@ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements J
 	public static final Logger log = LoggerFactory.getLogger(JBoss7ServerImpl.class);
 
     private volatile HttpFeed httpFeed;
-    private Enricher serviceUpEnricher;
     
     public JBoss7ServerImpl(){
         super();
@@ -114,14 +116,16 @@ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements J
     }
     
     protected void connectServiceUp() {
-        serviceUpEnricher = addEnricher(Enrichers.builder()
-                .propagating(ImmutableMap.of(MANAGEMENT_URL_UP, SERVICE_UP))
-                .from(this)
-                .build());
+        connectServiceUpIsRunning();
+        
+        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+            .from(MANAGEMENT_URL_UP)
+            .computing(Functionals.when(Predicates.not(Predicates.equalTo(true))).value("Management URL not reachable") )
+            .build());
     }
     
     protected void disconnectServiceUp() {
-        if (serviceUpEnricher != null) removeEnricher(serviceUpEnricher);
+        disconnectServiceUpIsRunning();
     }
     
     @Override


[22/26] git commit: write comprehensive tests for ServiceStateLogic, and tidy some of the semantics and usages elsewhere

Posted by he...@apache.org.
write comprehensive tests for ServiceStateLogic, and tidy some of the semantics and usages elsewhere


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/31c5a0c2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/31c5a0c2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/31c5a0c2

Branch: refs/heads/master
Commit: 31c5a0c2fd15ec23904bf438ba7b39579064dc8c
Parents: ab1381b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 26 22:10:47 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:21:22 2014 -0400

----------------------------------------------------------------------
 .../brooklyn/enricher/basic/Transformer.java    |   5 +-
 .../entity/basic/AbstractApplication.java       |  10 +-
 .../brooklyn/entity/basic/AbstractEntity.java   |  10 +
 .../brooklyn/entity/basic/EntityAdjuncts.java   |  69 ++++++
 .../java/brooklyn/entity/basic/QuorumCheck.java |   2 +-
 .../entity/basic/ServiceStateLogic.java         |  75 ++++--
 .../entity/group/DynamicClusterImpl.java        |   5 +-
 .../java/brooklyn/enricher/EnrichersTest.java   |  31 +--
 .../entity/basic/ServiceStateLogicTest.java     | 246 +++++++++++++++++++
 .../brooklyn/test/entity/TestEntityImpl.java    |   1 +
 .../camp/brooklyn/EnrichersYamlTest.java        |  18 +-
 11 files changed, 416 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/enricher/basic/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
index c517e88..81ee346 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
@@ -106,6 +106,9 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
     }
 
     protected Object compute(SensorEvent<T> event) {
-        return transformation.apply(event);
+        U result = transformation.apply(event);
+        if (LOG.isTraceEnabled())
+            LOG.trace("Enricher "+this+" computed "+result+" from "+event);
+        return result;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
index 961906f..4cab77c 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
@@ -35,6 +35,7 @@ import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.RuntimeInterruptedException;
 import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.time.Time;
 
 /**
  * Users can extend this to define the entities in their application, and the relationships between
@@ -125,6 +126,7 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         
         // default app logic; easily overridable by adding a different enricher with the same tag
         ServiceStateLogic.newEnricherFromChildren().checkChildrenAndMembers().addTo(this);
+        ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application created but not yet started, at "+Time.makeDateString());
     }
     
     /**
@@ -137,13 +139,17 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         Collection<? extends Location> locationsToUse = getLocations();
         ServiceProblemsLogic.clearProblemsIndicator(this, START);
         ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
+        ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application starting");
         recordApplicationEvent(Lifecycle.STARTING);
         try {
             preStart(locationsToUse);
+            // if there are other items which should block service_up, they should be done in preStart
+            ServiceStateLogic.ServiceNotUpLogic.clearNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL);
+            
             doStart(locationsToUse);
             postStart(locationsToUse);
         } catch (Exception e) {
-            // TODO should probably remember these problems then clear?  if so, do it here or on all effectors?
+            // TODO should probably remember these problems then clear?  if so, do it here ... or on all effectors?
 //            ServiceProblemsLogic.updateProblemsIndicator(this, START, e);
             
             recordApplicationEvent(Lifecycle.ON_FIRE);
@@ -188,6 +194,7 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
     public void stop() {
         logApplicationLifecycle("Stopping");
 
+        ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application stopping");
         setAttribute(SERVICE_UP, false);
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         recordApplicationEvent(Lifecycle.STOPPING);
@@ -199,6 +206,7 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
             log.warn("Error stopping application " + this + " (rethrowing): "+e);
             throw Exceptions.propagate(e);
         }
+        ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application stopping");
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         recordApplicationEvent(Lifecycle.STOPPED);
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index eb5f099..a612b0c 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -761,12 +761,16 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
         return attributesInternal.getValue(attribute);
     }
 
+    @SuppressWarnings("unchecked")
     public <T> T getAttributeByNameParts(List<String> nameParts) {
         return (T) attributesInternal.getValue(nameParts);
     }
     
     @Override
     public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
+        if (LOG.isTraceEnabled())
+            LOG.trace(""+this+" setAttribute "+attribute+" "+val);
+        
         T result = attributesInternal.update(attribute, val);
         if (result == null) {
             // could be this is a new sensor
@@ -779,6 +783,9 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
 
     @Override
     public <T> T setAttributeWithoutPublishing(AttributeSensor<T> attribute, T val) {
+        if (LOG.isTraceEnabled())
+            LOG.trace(""+this+" setAttributeWithoutPublishing "+attribute+" "+val);
+        
         T result = attributesInternal.updateWithoutPublishing(attribute, val);
         if (result == null) {
             // could be this is a new sensor
@@ -791,6 +798,9 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
 
     @Override
     public void removeAttribute(AttributeSensor<?> attribute) {
+        if (LOG.isTraceEnabled())
+            LOG.trace(""+this+" removeAttribute "+attribute);
+        
         attributesInternal.remove(attribute);
         entityType.removeSensor(attribute);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/basic/EntityAdjuncts.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityAdjuncts.java b/core/src/main/java/brooklyn/entity/basic/EntityAdjuncts.java
new file mode 100644
index 0000000..ab1d003
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/EntityAdjuncts.java
@@ -0,0 +1,69 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import java.util.Iterator;
+import java.util.List;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.EntityAdjunct;
+import brooklyn.util.collections.MutableList;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Convenience methods for working with entity adjunts.
+ */
+public class EntityAdjuncts {
+
+    public static <T extends EntityAdjunct> T findWithUniqueTag(Iterable<T> adjuncts, Object tag) {
+        Preconditions.checkNotNull(tag, "tag");
+        for (T adjunct: adjuncts)
+            if (tag.equals(adjunct.getUniqueTag())) 
+                return adjunct;
+        return null;
+    }
+    
+    public static final List<String> SYSTEM_ENRICHER_UNIQUE_TAGS = ImmutableList.of(
+        ServiceNotUpLogic.DEFAULT_ENRICHER_UNIQUE_TAG,
+        ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG,
+        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG,
+        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG_UP);
+    
+    public static List<Enricher> getNonSystemEnrichers(Entity entity) {
+        List<Enricher> result = MutableList.copyOf(entity.getEnrichers());
+        Iterator<Enricher> ri = result.iterator();
+        while (ri.hasNext()) {
+            if (isSystemEnricher(ri.next())) ri.remove();
+        }
+        return result;
+    }
+
+    public static boolean isSystemEnricher(Enricher enr) {
+        if (enr.getUniqueTag()==null) return false;
+        if (SYSTEM_ENRICHER_UNIQUE_TAGS.contains(enr.getUniqueTag())) return true;
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
index 17195e8..5235413 100644
--- a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
+++ b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
@@ -70,7 +70,7 @@ public interface QuorumCheck {
         
         @Override
         public String toString() {
-            return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%]";
+            return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index 749c83d..388f80d 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -47,6 +48,7 @@ import brooklyn.policy.EnricherSpec.ExtensibleEnricherSpec;
 import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
 import brooklyn.util.guava.Functionals;
 import brooklyn.util.text.Strings;
 
@@ -54,6 +56,8 @@ import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
 
 /** Logic, sensors and enrichers, and conveniences, for computing service status */ 
 public class ServiceStateLogic {
@@ -139,11 +143,19 @@ public class ServiceStateLogic {
         }
         
         /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the 
-         * {@link UpdatingMap} enricher for the given sensor reported */
+         * {@link UpdatingMap} enricher for the given key */
+        public static void updateNotUpIndicator(EntityLocal entity, String key, Object value) {
+            updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, key, value);
+        }
+        /** clears any entry for the given key in the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map */
+        public static void clearNotUpIndicator(EntityLocal entity, String key) {
+            clearMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, key);
+        }
+        /** as {@link #updateNotUpIndicator(EntityLocal, String, Object)} using the given sensor as the key */
         public static void updateNotUpIndicator(EntityLocal entity, Sensor<?> sensor, Object value) {
             updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName(), value);
         }
-        /** clears any entry for the given sensor in the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map */
+        /** as {@link #clearNotUpIndicator(EntityLocal, String)} using the given sensor as the key */
         public static void clearNotUpIndicator(EntityLocal entity, Sensor<?> sensor) {
             clearMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName());
         }
@@ -222,12 +234,19 @@ public class ServiceStateLogic {
         
         protected Lifecycle computeActualStateWhenNotExpectedRunning(Map<String, Object> problems, Boolean up, Lifecycle.Transition stateTransition) {
             if (stateTransition!=null) {
+                // if expected state is present but not running, just echo the expected state (ignore problems and up-ness)
                 return stateTransition.getState();
+                
             } else if (problems!=null && !problems.isEmpty()) {
-                return Lifecycle.ON_FIRE;
+                // if there is no expected state, then if service is not up, say stopped, else say on fire (whether service up is true or not present)
+                if (Boolean.FALSE.equals(up))
+                    return Lifecycle.STOPPED;
+                else
+                    return Lifecycle.ON_FIRE;
             } else {
-                // no expected transition
-                // if the problems map is non-null, then infer, else leave unchanged
+                // no expected transition and no problems
+                // if the problems map is non-null, then infer from service up;
+                // if there is no problems map, then leave unchanged (user may have set it explicitly)
                 if (problems!=null)
                     return (up==null ? null /* remove if up is not set */ : 
                         up ? Lifecycle.RUNNING : Lifecycle.STOPPED);
@@ -240,6 +259,7 @@ public class ServiceStateLogic {
             if (log.isTraceEnabled()) log.trace("{} setting actual state {}", this, state);
             emit(SERVICE_STATE_ACTUAL, (state==null ? Entities.REMOVE : state));
         }
+
     }
     
     public static final EnricherSpec<?> newEnricherForServiceStateFromProblemsAndUp() {
@@ -293,8 +313,12 @@ public class ServiceStateLogic {
             "Logic for checking whether this service is healthy, based on children and/or members running, defaulting to requiring none to be ON-FIRE", QuorumCheck.QuorumChecks.all());
         public static final ConfigKey<Boolean> DERIVE_SERVICE_NOT_UP = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_up.publish", "Whether to derive a service-not-up indicator from children", true);
         public static final ConfigKey<Boolean> DERIVE_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_problems.publish", "Whether to derive a service-problem indicator from children", true);
-        public static final ConfigKey<Boolean> IGNORE_NULL_VALUES = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_nulls", "Whether to ignore children reporting null values for the sensor", true);
-        public static final ConfigKey<Boolean> IGNORE_TRANSITIONING = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_transitioning", "Whether to ignore children reporting transitional states (starting, stopping, etc) for service state", true);
+        public static final ConfigKey<Boolean> IGNORE_ENTITIES_WITH_SERVICE_UP_NULL = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_entities.service_up_null", "Whether to ignore children reporting null values for service up", true);
+        @SuppressWarnings("serial")
+        public static final ConfigKey<Set<Lifecycle>> IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES = ConfigKeys.newConfigKey(new TypeToken<Set<Lifecycle>>() {},
+            "enricher.service_state.children_and_members.ignore_entities.service_state_values", 
+            "Service states (including null) which indicate an entity should be ignored when looking at children service states; anything apart from RUNNING not in this list will be treated as not healthy (by default just ON_FIRE will mean not healthy)", 
+            MutableSet.<Lifecycle>builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).build().asUnmodifiable());
 
         protected String getKeyForMapSensor() {
             return Preconditions.checkNotNull(super.getUniqueTag());
@@ -326,6 +350,27 @@ public class ServiceStateLogic {
             }
         }
 
+        final static Set<ConfigKey<?>> RECONFIGURABLE_KEYS = ImmutableSet.<ConfigKey<?>>of(
+            UP_QUORUM_CHECK, RUNNING_QUORUM_CHECK,
+            DERIVE_SERVICE_NOT_UP, DERIVE_SERVICE_NOT_UP, 
+            IGNORE_ENTITIES_WITH_SERVICE_UP_NULL, IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
+        
+        @Override
+        protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
+            if (RECONFIGURABLE_KEYS.contains(key)) {
+                return;
+            } else {
+                super.doReconfigureConfig(key, val);
+            }
+        }
+        
+        @Override
+        protected void onChanged() {
+            super.onChanged();
+            if (entity != null && isRunning())
+                onUpdated();
+        }
+        
         private final List<Sensor<?>> SOURCE_SENSORS = ImmutableList.<Sensor<?>>of(SERVICE_UP, SERVICE_STATE_ACTUAL);
         @Override
         protected Collection<Sensor<?>> getSourceSensors() {
@@ -334,8 +379,13 @@ public class ServiceStateLogic {
 
         @Override
         protected void onUpdated() {
-            // override superclass to publish potentially several items
+            if (entity==null || !Entities.isManaged(entity)) {
+                // either invoked during setup or entity has become unmanaged; just ignore
+                log.debug("Ignoring {} onUpdated when entity is not in valid state ({})", this, entity);
+                return;
+            }
 
+            // override superclass to publish multiple sensors
             if (getConfig(DERIVE_SERVICE_PROBLEMS))
                 updateMapSensor(SERVICE_PROBLEMS, computeServiceProblems());
 
@@ -346,7 +396,7 @@ public class ServiceStateLogic {
         protected Object computeServiceNotUp() {
             Map<Entity, Boolean> values = getValues(SERVICE_UP);
             List<Entity> violators = MutableList.of();
-            boolean ignoreNull = getConfig(IGNORE_NULL_VALUES);
+            boolean ignoreNull = getConfig(IGNORE_ENTITIES_WITH_SERVICE_UP_NULL);
             int entries=0;
             for (Map.Entry<Entity, Boolean> state: values.entrySet()) {
                 if (ignoreNull && state.getValue()==null)
@@ -379,13 +429,10 @@ public class ServiceStateLogic {
         protected Object computeServiceProblems() {
             Map<Entity, Lifecycle> values = getValues(SERVICE_STATE_ACTUAL);
             int numRunning=0, numNotHealthy=0;
-            boolean ignoreNull = getConfig(IGNORE_NULL_VALUES);
-            boolean ignoreTransition = getConfig(IGNORE_TRANSITIONING);
+            Set<Lifecycle> ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
             for (Lifecycle state: values.values()) {
                 if (state==Lifecycle.RUNNING) numRunning++;
-                else if (state==Lifecycle.ON_FIRE) numNotHealthy++;
-                else if (state==null) { if (!ignoreNull) numNotHealthy++; }
-                else { if (!ignoreTransition) numNotHealthy++; }
+                else if (!ignoreStates.contains(state)) numNotHealthy++;
             }
 
             QuorumCheck qc = getConfig(RUNNING_QUORUM_CHECK);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
index 1023db6..553d453 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.AbstractGroupImpl;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityFactory;
 import brooklyn.entity.basic.EntityFactoryForLocation;
@@ -141,7 +142,6 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
     @Override
     public void init() {
         super.init();
-        setAttribute(SERVICE_UP, false);
     }
 
     @Override
@@ -149,6 +149,9 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
         if (getConfigRaw(UP_QUORUM_CHECK, true).isAbsent() && getConfig(INITIAL_SIZE)==0) {
             // if initial size is 0 then override up check to allow zero if empty
             setConfig(UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty());
+            setAttribute(SERVICE_UP, true);
+        } else {
+            setAttribute(SERVICE_UP, false);
         }
         super.initEnrichers();
         // override previous enricher so that only members are checked

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index aeffe6a..12f1cad 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -19,8 +19,6 @@
 package brooklyn.enricher;
 
 import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -29,13 +27,10 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BasicGroup;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityAdjuncts;
 import brooklyn.entity.basic.EntitySubscriptionTest.RecordingSensorEventListener;
-import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
-import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.SensorEvent;
@@ -45,7 +40,6 @@ import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.CollectionFunctionals;
-import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.guava.Functionals;
@@ -63,27 +57,6 @@ import com.google.common.reflect.TypeToken;
 @SuppressWarnings("serial")
 public class EnrichersTest extends BrooklynAppUnitTestSupport {
 
-    public static List<Enricher> getNonSystemEnrichers(Entity entity) {
-        List<Enricher> result = MutableList.copyOf(entity.getEnrichers());
-        Iterator<Enricher> ri = result.iterator();
-        while (ri.hasNext()) {
-            if (isSystemEnricher(ri.next())) ri.remove();
-        }
-        return result;
-    }
-
-    public static final List<String> SYSTEM_ENRICHER_UNIQUE_TAGS = ImmutableList.of(
-        ServiceNotUpLogic.DEFAULT_ENRICHER_UNIQUE_TAG,
-        ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG,
-        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG,
-        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG_UP);
-    
-    public static boolean isSystemEnricher(Enricher enr) {
-        if (enr.getUniqueTag()==null) return false;
-        if (SYSTEM_ENRICHER_UNIQUE_TAGS.contains(enr.getUniqueTag())) return true;
-        return false;
-    }
-    
     public static final AttributeSensor<Integer> NUM1 = Sensors.newIntegerSensor("test.num1");
     public static final AttributeSensor<Integer> NUM2 = Sensors.newIntegerSensor("test.num2");
     public static final AttributeSensor<Integer> NUM3 = Sensors.newIntegerSensor("test.num3");
@@ -117,7 +90,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
                 .computingSum()
                 .build());
         
-        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(entity), ImmutableList.of(enr));
+        Assert.assertEquals(EntityAdjuncts.getNonSystemEnrichers(entity), ImmutableList.of(enr));
         
         entity.setAttribute(NUM1, 2);
         entity.setAttribute(NUM2, 3);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/test/java/brooklyn/entity/basic/ServiceStateLogicTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/ServiceStateLogicTest.java b/core/src/test/java/brooklyn/entity/basic/ServiceStateLogicTest.java
new file mode 100644
index 0000000..ca5d8c1
--- /dev/null
+++ b/core/src/test/java/brooklyn/entity/basic/ServiceStateLogicTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.AttributeSensor;
+import brooklyn.location.Location;
+import brooklyn.policy.Enricher;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+@Test
+public class ServiceStateLogicTest extends BrooklynAppUnitTestSupport {
+    
+    private static final Logger log = LoggerFactory.getLogger(ServiceStateLogicTest.class);
+    
+    final static String INDICATOR_KEY_1 = "test-indicator-1";
+    final static String INDICATOR_KEY_2 = "test-indicator-2";
+
+    protected TestEntity entity;
+
+    protected void setUpApp() {
+        super.setUpApp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+
+
+    public void testManuallySettingIndicatorsOnEntities() {
+        // if we set a not up indicator, entity service up should become false
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        
+        // but state will not change unless we also set either a problem or expected state
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+        ServiceProblemsLogic.updateProblemsIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service state also");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // and if we clear the not up indicator, service up becomes true, but there is a problem, so it shows on-fire
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // if we then clear the problem also, state goes to RUNNING
+        ServiceProblemsLogic.clearProblemsIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        // now add not-up indicator again, and it reverts to up=false, state=stopped
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're again pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // but if we expect it to be running it will show on fire (because service is not up)
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // and if we again clear the not up indicator it will deduce up=true and state=running
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+    public void testAppStoppedAndEntityNullBeforeStarting() {
+        // AbstractApplication has default logic to ensure service is not up if it hasn't been started,
+        // (this can be removed by updating the problem indicator associated with the SERVICE_STATE_ACTUAL sensor)
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        // and from that it imputes stopped state
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // TestEntity has no such indicators however
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, null);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+    }
+    
+    public void testAllUpAndRunningAfterStart() {
+        app.start(ImmutableList.<Location>of());
+        
+        assertAttributeEquals(app, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, true);
+        // above should be immediate, entity should then derive RUNNING from expected state, and then so should app from children
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+    
+    public void testStopsNicelyToo() {
+        app.start(ImmutableList.<Location>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        app.stop();
+        
+        assertAttributeEquals(app, Attributes.SERVICE_UP, false);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, false);
+        // above should be immediate, app and entity should then derive STOPPED from the expected state
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+    }
+
+    public void testTwoIndicatorsAreBetterThanOne() {        
+        // if we set a not up indicator, entity service up should become false
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_2, "We're also pretending to block service up");
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        // clearing one indicator is not sufficient
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, false);
+        
+        // but it does not become true when both are cleared
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_2);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+    }
+
+    public void testManuallySettingIndicatorsOnApplicationsIsMoreComplicated() {
+        // indicators on application are more complicated because it is configured with additional indicators from its children
+        // test a lot of situations, including reconfiguring some of the quorum config
+        
+        // to begin with, an entity has not reported anything, so the ComputeServiceIndicatorsFromChildren ignores it
+        // but the AbstractApplication has emitted a not-up indicator because it has not been started
+        // both as asserted by this other test:
+        testAppStoppedAndEntityNullBeforeStarting();
+        
+        // if we clear the not up indicator, the app will show as up, and as running, because it has no reporting children 
+        ServiceNotUpLogic.clearNotUpIndicator(app, Attributes.SERVICE_STATE_ACTUAL);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        // if we then put a not-up indicator on the TestEntity, it publishes false, so app also is false, and thus stopped
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're also pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        // but the entity still has no opinion about its state
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+        
+        // if the entity expects to be stopped, it will report stopped
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.STOPPED);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        // and now so does the app 
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // if we clear the not-up indicator, both the entity and the app report service up (with the entity first)
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, true);
+        // but entity is still stopped because that is what is expected there, and that's okay even if service is apparently up
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // the app however is running, because the default state quorum check is "all are healthy"
+        assertAttributeEquals(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        // if we change the state quorum check for the app to be "all are healthy and at least one running" *then* it shows stopped
+        // (normally this would be done in `initEnrichers` of course)
+        Enricher appChildrenBasedEnricher = EntityAdjuncts.findWithUniqueTag(app.getEnrichers(), ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG);
+        Assert.assertNotNull(appChildrenBasedEnricher, "Expected enricher not found");
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumChecks.allAndAtLeastOne());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // if entity is expected running, then it shows running, because service is up, and it's reflected at app and at entity
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        // now, when the entity is unmanaged, the app is still running because children are empty
+        Entities.unmanage(entity);
+        assertAttributeEquals(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        // but if we change its up quorum to atLeastOne then service up becomes false
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOne());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        // and state becomes stopped (because there is no expected state)
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // if we now start it will successfully start (because unlike entities it does not wait for service up) 
+        // but will remain down and will go on fire
+        app.start(ImmutableList.<Location>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        // restoring this to atLeastOneUnlessEmpty causes it to become RUNNING
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumChecks.all());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        // now add a child, it's still up and running because null values are ignored by default
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        assertAttributeEquals(app, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        // tell it not to ignore null values for children states, and it will go onfire (but still be service up)
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES, 
+            ImmutableSet.<Lifecycle>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        assertAttributeEquals(app, Attributes.SERVICE_UP, true);
+        // tell it not to ignore null values for service up and it will go service down
+        appChildrenBasedEnricher.setConfig(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_SERVICE_UP_NULL, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+    }
+        
+    private static <T> void assertAttributeEqualsEventually(Entity x, AttributeSensor<T> sensor, T value) {
+        try {
+            EntityTestUtils.assertAttributeEqualsEventually(ImmutableMap.of("timeout", Duration.seconds(10)), x, sensor, value);
+        } catch (Throwable e) {
+            log.warn("Expected "+x+" eventually to have "+sensor+" = "+value+"; instead:");
+            Entities.dumpInfo(x);
+            throw Exceptions.propagate(e);
+        }
+    }
+    private static <T> void assertAttributeEquals(Entity x, AttributeSensor<T> sensor, T value) {
+        try {
+            EntityTestUtils.assertAttributeEquals(x, sensor, value);
+        } catch (Throwable e) {
+            log.warn("Expected "+x+" to have "+sensor+" = "+value+"; instead:");
+            Entities.dumpInfo(x);
+            throw Exceptions.propagate(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
index 3ccf614..d58aa86 100644
--- a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
@@ -137,6 +137,7 @@ public class TestEntityImpl extends AbstractEntity implements TestEntity {
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         counter.decrementAndGet();
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
+        setAttribute(SERVICE_UP, false);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/31c5a0c2/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
index 7d3feaf..4391ba9 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
@@ -28,10 +28,10 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import brooklyn.config.ConfigKey;
-import brooklyn.enricher.EnrichersTest;
 import brooklyn.enricher.basic.Propagator;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityAdjuncts;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.policy.Enricher;
 import brooklyn.test.Asserts;
@@ -57,8 +57,8 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         log.info("App started:");
         Entities.dumpInfo(app);
         
-        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(app).size(), 1);
-        final Enricher enricher = EnrichersTest.getNonSystemEnrichers(app).iterator().next();
+        Assert.assertEquals(EntityAdjuncts.getNonSystemEnrichers(app).size(), 1);
+        final Enricher enricher = EntityAdjuncts.getNonSystemEnrichers(app).iterator().next();
         Assert.assertTrue(enricher instanceof TestEnricher, "enricher="+enricher);
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_NAME), "Name from YAML");
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_FROM_FUNCTION), "$brooklyn: is a fun place");
@@ -90,16 +90,16 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         log.info("App started:");
         Entities.dumpInfo(app);
 
-        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(app).size(), 0);
+        Assert.assertEquals(EntityAdjuncts.getNonSystemEnrichers(app).size(), 0);
         Assert.assertEquals(app.getChildren().size(), 1);
         final Entity child = app.getChildren().iterator().next();
         Asserts.eventually(new Supplier<Integer>() {
             @Override
             public Integer get() {
-                return EnrichersTest.getNonSystemEnrichers(child).size();
+                return EntityAdjuncts.getNonSystemEnrichers(child).size();
             }
         }, Predicates.<Integer> equalTo(1));        
-        final Enricher enricher = EnrichersTest.getNonSystemEnrichers(child).iterator().next();
+        final Enricher enricher = EntityAdjuncts.getNonSystemEnrichers(child).iterator().next();
         Assert.assertNotNull(enricher);
         Assert.assertTrue(enricher instanceof TestEnricher, "enricher=" + enricher + "; type=" + enricher.getClass());
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_NAME), "Name from YAML");
@@ -151,10 +151,10 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         Asserts.eventually(new Supplier<Integer>() {
             @Override
             public Integer get() {
-                return EnrichersTest.getNonSystemEnrichers(parentEntity).size();
+                return EntityAdjuncts.getNonSystemEnrichers(parentEntity).size();
             }
         }, Predicates.<Integer>equalTo(1));
-        Enricher enricher = EnrichersTest.getNonSystemEnrichers(parentEntity).iterator().next();
+        Enricher enricher = EntityAdjuncts.getNonSystemEnrichers(parentEntity).iterator().next();
         Asserts.assertTrue(enricher instanceof Propagator, "Expected enricher to be Propagator, found:" + enricher);
         final Propagator propagator = (Propagator)enricher;
         Entity producer = ((EntityInternal)parentEntity).getExecutionContext().submit(MutableMap.of(), new Callable<Entity>() {
@@ -242,7 +242,7 @@ public class EnrichersYamlTest extends AbstractYamlTest {
     }
     
     private Enricher getEnricher(Entity entity) {
-        List<Enricher> enrichers = EnrichersTest.getNonSystemEnrichers(entity);
+        List<Enricher> enrichers = EntityAdjuncts.getNonSystemEnrichers(entity);
         Assert.assertEquals(enrichers.size(), 1, "Wrong number of enrichers: "+enrichers);
         Enricher enricher = enrichers.iterator().next();
         Assert.assertTrue(enricher instanceof TestReferencingEnricher, "Wrong enricher: "+enricher);


[26/26] git commit: This closes #131

Posted by he...@apache.org.
This closes #131


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/761f471a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/761f471a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/761f471a

Branch: refs/heads/master
Commit: 761f471ab02362ff4a17301f99d951282b66d7d7
Parents: 74b15c0 225f182
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Aug 29 19:00:32 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Aug 29 19:00:32 2014 -0400

----------------------------------------------------------------------
 .../basic/AbstractBrooklynObjectSpec.java       |  14 +-
 api/src/main/java/brooklyn/entity/Entity.java   |   6 +-
 api/src/main/java/brooklyn/entity/Group.java    |   1 +
 .../java/brooklyn/entity/basic/EntityLocal.java |   1 -
 .../brooklyn/entity/proxying/EntitySpec.java    |   7 +-
 .../java/brooklyn/location/LocationSpec.java    |   2 +-
 .../main/java/brooklyn/policy/EnricherSpec.java |  70 ++-
 .../main/java/brooklyn/policy/PolicySpec.java   |   2 +-
 .../main/java/brooklyn/enricher/Enrichers.java  | 288 +++++++---
 .../enricher/basic/AbstractAggregator.java      | 215 ++++++++
 .../enricher/basic/AbstractEnricher.java        |  45 ++
 .../basic/AbstractMultipleSensorAggregator.java | 149 +++++
 .../basic/AbstractTypeTransformingEnricher.java |   1 +
 .../brooklyn/enricher/basic/Aggregator.java     | 174 ++----
 .../java/brooklyn/enricher/basic/Combiner.java  |  10 +-
 .../brooklyn/enricher/basic/Propagator.java     |  16 +-
 .../basic/SensorPropagatingEnricher.java        |   1 +
 .../brooklyn/enricher/basic/Transformer.java    |  17 +-
 .../brooklyn/enricher/basic/UpdatingMap.java    | 160 ++++++
 .../entity/basic/AbstractApplication.java       |  50 +-
 .../brooklyn/entity/basic/AbstractEntity.java   |  94 ++--
 .../brooklyn/entity/basic/AbstractGroup.java    |  10 +
 .../entity/basic/AbstractGroupImpl.java         |  11 +
 .../java/brooklyn/entity/basic/Attributes.java  |  25 +-
 .../entity/basic/BasicApplicationImpl.java      |   7 -
 .../java/brooklyn/entity/basic/ConfigKeys.java  |   4 +
 .../brooklyn/entity/basic/DynamicGroup.java     |   1 +
 .../brooklyn/entity/basic/DynamicGroupImpl.java |   2 +-
 .../java/brooklyn/entity/basic/Entities.java    |   5 +-
 .../brooklyn/entity/basic/EntityAdjuncts.java   |  69 +++
 .../brooklyn/entity/basic/EntityConfigMap.java  |   8 +-
 .../brooklyn/entity/basic/EntityFunctions.java  |  55 +-
 .../java/brooklyn/entity/basic/Lifecycle.java   |  66 ++-
 .../java/brooklyn/entity/basic/QuorumCheck.java |  77 +++
 .../entity/basic/ServiceStateLogic.java         | 546 +++++++++++++++++++
 .../drivers/downloads/BasicDownloadTargets.java |   2 +-
 .../brooklyn/entity/group/DynamicCluster.java   |   2 +-
 .../entity/group/DynamicClusterImpl.java        |  87 +--
 .../brooklyn/entity/group/DynamicFabric.java    |   2 +-
 .../entity/group/DynamicFabricImpl.java         |  13 +-
 .../brooklyn/event/basic/BasicConfigKey.java    |   6 +-
 .../event/basic/DependentConfiguration.java     |  12 +-
 .../brooklyn/event/basic/ListConfigKey.java     |   2 +-
 .../java/brooklyn/event/basic/SetConfigKey.java |   2 +-
 .../internal/LocalSubscriptionManager.java      |   6 +-
 .../policy/basic/AbstractEntityAdjunct.java     |  19 +-
 .../util/task/BasicExecutionManager.java        |   7 +-
 .../catalog/internal/MyCatalogItems.java        |   4 -
 .../enricher/CustomAggregatingEnricherTest.java |   2 +-
 .../java/brooklyn/enricher/EnrichersTest.java   | 107 +++-
 .../enricher/basic/BasicEnricherTest.java       |  27 +-
 .../entity/BrooklynAppUnitTestSupport.java      |   4 +
 .../basic/DependentConfigurationTest.java       |  39 +-
 .../brooklyn/entity/basic/DynamicGroupTest.java |   2 -
 .../brooklyn/entity/basic/EntitySpecTest.java   |   5 +-
 .../brooklyn/entity/basic/EntityTypeTest.java   |   4 +-
 .../entity/basic/PolicyRegistrationTest.java    |   6 +-
 .../entity/basic/ServiceStateLogicTest.java     | 246 +++++++++
 .../entity/group/DynamicClusterTest.java        |   5 +-
 .../entity/rebind/RebindEnricherTest.java       |   4 +-
 .../entity/rebind/RebindTestFixtureWithApp.java |   3 +-
 .../BrooklynMementoPersisterTestFixture.java    |   2 +-
 .../brooklyn/event/feed/http/HttpFeedTest.java  |   5 +-
 .../basic/MultiLocationResolverTest.java        |   3 +-
 .../brooklyn/policy/basic/EnricherTypeTest.java |   2 +-
 .../EntityCleanupLongevityTestFixture.java      |   2 +-
 .../test/entity/TestApplicationImpl.java        |   5 -
 .../entity/TestApplicationNoEnrichersImpl.java  |  44 ++
 .../brooklyn/test/entity/TestClusterImpl.java   |   9 +
 .../java/brooklyn/test/entity/TestEntity.java   |   4 +-
 .../brooklyn/test/entity/TestEntityImpl.java    |  13 +-
 .../test/entity/TestEntityNoEnrichersImpl.java  |  32 ++
 .../defining-applications/service-state.md      |  73 +++
 docs/use/guide/defining-applications/toc.json   |   2 +
 .../brooklyn/demo/GlobalWebFabricExample.java   |   2 +-
 .../demo/StandaloneQpidBrokerExample.java       |   2 +-
 .../brooklyn/demo/CumulusRDFApplication.java    |  16 +-
 .../demo/HighAvailabilityCassandraCluster.java  |   7 +-
 .../java/brooklyn/demo/ResilientMongoDbApp.java |   4 +-
 .../java/brooklyn/demo/RiakClusterExample.java  |  11 +-
 .../brooklyn/demo/SimpleCassandraCluster.java   |   2 +-
 .../main/java/brooklyn/demo/StormSampleApp.java |   2 +-
 .../brooklyn/demo/WideAreaCassandraCluster.java |   7 +-
 .../brooklyn/demo/NodeJsTodoApplication.java    |   2 +-
 .../brooklyn/demo/SingleWebServerExample.java   |   2 +-
 .../demo/WebClusterDatabaseExample.java         |   2 +-
 .../demo/WebClusterDatabaseExampleApp.java      |   2 +-
 .../demo/WebClusterDatabaseExampleGroovy.groovy |   2 +-
 .../java/brooklyn/demo/WebClusterExample.java   |   2 +-
 .../brooklyn/location/jclouds/JcloudsUtil.java  |   2 +-
 .../policy/ha/MemberFailureDetectionPolicy.java |   8 +-
 .../policy/ha/ServiceFailureDetector.java       | 409 ++++----------
 .../brooklyn/policy/ha/ServiceReplacer.java     |   5 +-
 .../brooklyn/policy/ha/ServiceRestarter.java    |   5 +-
 .../loadbalancing/BalanceableContainer.java     |  13 +-
 .../loadbalancing/BalanceableWorkerPool.java    |   3 +
 .../BalanceableWorkerPoolImpl.java              |   6 +-
 .../loadbalancing/LoadBalancingPolicy.java      |  17 +-
 .../entity/brooklyn/BrooklynMetricsTest.java    |  23 +-
 .../autoscaling/AutoScalerPolicyMetricTest.java |   5 +-
 .../autoscaling/AutoScalerPolicyTest.java       |   2 +-
 .../brooklyn/policy/ha/HaPolicyRebindTest.java  |   9 +-
 ...ServiceFailureDetectorStabilizationTest.java |  31 +-
 .../policy/ha/ServiceFailureDetectorTest.java   | 207 ++++---
 .../brooklyn/policy/ha/ServiceReplacerTest.java |   6 +-
 .../policy/ha/ServiceRestarterTest.java         |   4 +-
 .../AbstractLoadBalancingPolicyTest.java        |   2 +-
 .../loadbalancing/LoadBalancingPolicyTest.java  |   7 +-
 .../loadbalancing/MockContainerEntity.java      |   2 +-
 .../loadbalancing/MockContainerEntityImpl.java  |   3 +-
 .../basic/AbstractSoftwareProcessDriver.java    |   6 +-
 .../brooklyn/entity/basic/SameServerEntity.java |   6 +-
 .../entity/basic/SameServerEntityImpl.java      |   6 +
 .../brooklyn/entity/basic/SoftwareProcess.java  |   5 +-
 ...wareProcessDriverLifecycleEffectorTasks.java |   3 +-
 .../entity/basic/SoftwareProcessImpl.java       |  51 +-
 .../brooklynnode/BrooklynEntityMirrorImpl.java  |  11 +-
 .../entity/chef/ChefLifecycleEffectorTasks.java |   8 +-
 .../brooklyn/entity/chef/KnifeTaskFactory.java  |   4 +-
 .../brooklyn/entity/pool/ServerPoolImpl.java    |   4 +-
 .../software/MachineLifecycleEffectorTasks.java |  32 +-
 .../entity/basic/lifecycle/MyEntityImpl.java    |   3 +-
 .../basic/lifecycle/NaiveScriptRunnerTest.java  |   3 +
 .../basic/lifecycle/ScriptHelperTest.java       |  61 ++-
 .../entity/java/VanillaJavaAppTest.java         |   4 +-
 .../brooklyn/entity/pool/ServerPoolTest.java    |  13 +-
 .../mysql/AbstractToyMySqlEntityTest.java       |   4 +-
 .../entity/messaging/jms/JMSBrokerImpl.java     |  13 +-
 .../entity/zookeeper/ZooKeeperEnsembleImpl.java |  22 +-
 .../cassandra/CassandraDatacenterImpl.java      |  38 +-
 .../nosql/cassandra/CassandraFabricImpl.java    |   8 +-
 .../nosql/couchbase/CouchbaseClusterImpl.java   |  40 +-
 .../nosql/couchdb/CouchDBClusterImpl.java       |  21 +-
 .../elasticsearch/ElasticSearchClusterImpl.java |  10 -
 .../nosql/mongodb/MongoDBReplicaSetImpl.java    |   3 +-
 .../MongoDBConfigServerClusterImpl.java         |  16 +-
 .../sharding/MongoDBShardedDeploymentImpl.java  |  22 +-
 .../entity/nosql/riak/RiakClusterImpl.java      |  10 +-
 .../nosql/cassandra/CassandraFabricTest.java    |   5 +-
 .../entity/dns/AbstractGeoDnsService.java       |   2 +-
 .../entity/dns/AbstractGeoDnsServiceImpl.java   |   9 +-
 .../geoscaling/GeoscalingDnsServiceImpl.java    |   5 +-
 .../entity/proxy/AbstractControllerImpl.java    |   2 +-
 .../entity/proxy/LoadBalancerClusterImpl.java   |  31 --
 .../entity/proxy/nginx/NginxControllerImpl.java |   6 +-
 .../entity/proxy/nginx/NginxSshDriver.java      |   2 +-
 .../webapp/ControlledDynamicWebAppCluster.java  |   2 +-
 .../ControlledDynamicWebAppClusterImpl.java     |  71 +--
 .../entity/webapp/DynamicWebAppClusterImpl.java |  41 --
 .../entity/webapp/jboss/JBoss7ServerImpl.java   |  22 +-
 .../entity/webapp/jetty/Jetty6ServerImpl.java   |   2 +-
 .../proxy/nginx/NginxRebindIntegrationTest.java |   3 +-
 .../ControlledDynamicWebAppClusterTest.java     |  19 +-
 .../entity/webapp/DynamicWebAppClusterTest.java |  12 +-
 .../brooklyn/entity/webapp/JBossExample.groovy  |  48 --
 .../test/entity/TestJavaWebAppEntity.groovy     |  75 ---
 .../test/entity/TestJavaWebAppEntity.java       |  79 +++
 .../camp/brooklyn/EnrichersYamlTest.java        |  23 +-
 .../camp/brooklyn/EntitiesYamlTest.java         |   2 +-
 .../brooklyn/JavaWebAppsIntegrationTest.java    |   5 +-
 .../brooklyn/VanillaBashNetcatYamlTest.java     |  10 +-
 .../BrooklynEntityMirrorIntegrationTest.java    |   4 +-
 .../qa/longevity/webcluster/WebClusterApp.java  |   2 +-
 .../java/brooklyn/rest/api/EffectorApi.java     |   3 +-
 .../main/java/brooklyn/rest/api/PolicyApi.java  |   1 +
 .../java/brooklyn/rest/api/PolicyConfigApi.java |   3 +
 .../rest/resources/ApplicationResource.java     |   2 +-
 .../rest/transform/ApplicationTransformer.java  |   2 +-
 .../rest/transform/LocationTransformer.java     |   2 +-
 .../rest/util/BrooklynRestResourceUtils.java    |   2 +-
 .../rest/resources/ApplicationResourceTest.java |   9 +-
 .../rest/testing/BrooklynRestResourceTest.java  |  26 +-
 .../rest/testing/mocks/RestMockApp.java         |   8 -
 .../rest/testing/mocks/RestMockAppBuilder.java  |   5 +-
 .../testing/mocks/RestMockSimpleEntity.java     |  11 +-
 .../util/BrooklynRestResourceUtilsTest.java     |   4 -
 .../java/brooklyn/test/EntityTestUtils.java     |   1 +
 .../main/java/brooklyn/test/TestUtils.groovy    |   8 +-
 .../src/main/java/brooklyn/test/Asserts.java    |   7 +-
 .../util/collections/CollectionFunctionals.java |  94 +++-
 .../brooklyn/util/collections/MutableList.java  |  31 +-
 .../brooklyn/util/collections/MutableMap.java   |  26 +
 .../brooklyn/util/collections/MutableSet.java   |  34 +-
 .../java/brooklyn/util/guava/Functionals.java   |  73 +++
 .../java/brooklyn/util/guava/IfFunctions.java   | 158 ++++++
 .../collections/CollectionFunctionalsTest.java  |  51 ++
 .../util/collections/MutableListTest.java       |  23 +
 .../brooklyn/util/guava/FunctionalsTest.java    |  58 ++
 .../brooklyn/util/guava/IfFunctionsTest.java    | 101 ++++
 .../guava/KeyTransformingLoadingCacheTest.java  |   1 -
 190 files changed, 3971 insertions(+), 1539 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/761f471a/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java
----------------------------------------------------------------------


[05/26] git commit: refactor Enrichers.builder() types to give correct generics for published sensor

Posted by he...@apache.org.
refactor Enrichers.builder() types to give correct generics for published sensor


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3c714ed1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3c714ed1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3c714ed1

Branch: refs/heads/master
Commit: 3c714ed12259c3909f0244f83f504e6a62be7bdb
Parents: 141751b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 22:09:15 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:48 2014 -0400

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  | 132 ++++++++++---------
 .../java/brooklyn/enricher/EnrichersTest.java   |  55 +++++++-
 2 files changed, 115 insertions(+), 72 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3c714ed1/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 6ddc780..0d3a0d5 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -56,8 +56,8 @@ public class Enrichers {
 
     private Enrichers() {}
     
-    public static InitialBuilder<?> builder() {
-        return new ConcreteInitialBuilder();
+    public static InitialBuilder builder() {
+        return new InitialBuilder();
     }
 
     public abstract static class Builder<B extends Builder<B>> {
@@ -67,36 +67,46 @@ public class Enrichers {
         }
     }
     
-    public abstract static class InitialBuilder<B extends InitialBuilder<B>> extends Builder<B> {
-        public PropagatorBuilder<?> propagating(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
-            return new ConcretePropagatorBuilder(vals);
+    protected abstract static class AbstractInitialBuilder<B extends AbstractInitialBuilder<B>> extends Builder<B> {
+        public PropagatorBuilder propagating(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
+            return new PropagatorBuilder(vals);
         }
-        public PropagatorBuilder<?> propagating(Iterable<? extends Sensor<?>> vals) {
-            return new ConcretePropagatorBuilder(vals);
+        public PropagatorBuilder propagating(Iterable<? extends Sensor<?>> vals) {
+            return new PropagatorBuilder(vals);
         }
-        public PropagatorBuilder<?> propagating(Sensor<?>... vals) {
-            return new ConcretePropagatorBuilder(vals);
+        public PropagatorBuilder propagating(Sensor<?>... vals) {
+            return new PropagatorBuilder(vals);
         }
-        public PropagatorBuilder<?> propagatingAll() {
-            return new ConcretePropagatorBuilder(true, null);
+        public PropagatorBuilder propagatingAll() {
+            return new PropagatorBuilder(true, null);
         }
-        public PropagatorBuilder<?> propagatingAllBut(Sensor<?>... vals) {
-            return new ConcretePropagatorBuilder(true, ImmutableSet.copyOf(vals));
+        public PropagatorBuilder propagatingAllBut(Sensor<?>... vals) {
+            return new PropagatorBuilder(true, ImmutableSet.copyOf(vals));
         }
-        public PropagatorBuilder<?> propagatingAllBut(Iterable<? extends Sensor<?>> vals) {
-            return new ConcretePropagatorBuilder(true, vals);
+        public PropagatorBuilder propagatingAllBut(Iterable<? extends Sensor<?>> vals) {
+            return new PropagatorBuilder(true, vals);
         }
-        public <S> TransformerBuilder<S, Object, ?> transforming(AttributeSensor<S> val) {
-            return new ConcreteTransformerBuilder<S, Object>(val);
-        }
-        public <S> CombinerBuilder<S, Object, ?> combining(AttributeSensor<? extends S>... vals) {
-            return new ConcreteCombinerBuilder<S, Object>(vals);
-        }
-        public <S> CombinerBuilder<S, Object, ?> combining(Collection<AttributeSensor<? extends S>> vals) {
-            return new ConcreteCombinerBuilder<S, Object>(vals);
-        }
-        public <S> AggregatorBuilder<S, Object, ?> aggregating(AttributeSensor<S> val) {
-            return new ConcreteAggregatorBuilder<S,Object>(val);
+        
+        /** builds an enricher which transforms a given sensor:
+         * <li> applying a (required) function ({@link TransformerBuilder#computing(Function)}, or {@link TransformerBuilder#computingAverage()}/{@link TransformerBuilder#computingSum()}, mandatory);
+         * <li> and publishing it on the entity where the enricher is attached;
+         * <li> optionally taking the sensor from a different source entity ({@link TransformerBuilder#from(Entity)});
+         * <li> and optionally publishing it as a different sensor ({@link TransformerBuilder#publishing(AttributeSensor)});
+         * <p> (You must supply at least one of the optional values, of course, otherwise the enricher may loop endlessly!) */
+        public <S> TransformerBuilder<S, Object> transforming(AttributeSensor<S> val) {
+            return new TransformerBuilder<S, Object>(val);
+        }
+        /** as {@link #transforming(AttributeSensor)} but accepting multiple sensors, with the function acting on the set of values */
+        public <S> CombinerBuilder<S, Object> combining(Collection<AttributeSensor<? extends S>> vals) {
+            return new CombinerBuilder<S, Object>(vals);
+        }
+        /** as {@link #combining(Collection)} */
+        public <S> CombinerBuilder<S, Object> combining(AttributeSensor<? extends S>... vals) {
+            return new CombinerBuilder<S, Object>(vals);
+        }
+        /** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */
+        public <S> AggregatorBuilder<S, Object> aggregating(AttributeSensor<S> val) {
+            return new AggregatorBuilder<S,Object>(val);
         }
         /** creates an {@link UpdatingMap} enricher: 
          * {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required
@@ -107,7 +117,7 @@ public class Enrichers {
     }
 
 
-    public abstract static class AggregatorBuilder<S, T, B extends AggregatorBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends Builder<B> {
         protected final AttributeSensor<S> aggregating;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
@@ -121,15 +131,13 @@ public class Enrichers {
         protected Object defaultValueForUnreportedSensors;
         protected Object valueToReportIfNoSensors;
         
-        public AggregatorBuilder(AttributeSensor<S> aggregating) {
+        public AbstractAggregatorBuilder(AttributeSensor<S> aggregating) {
             this.aggregating = aggregating;
         }
-        // TODO change the signature of this to have correct type info as done for UpdatingMapBuilder.from(Sensor)
-        // (including change *Builder to Abstract*Builder and Concrete*Builder to *Builder, for all other enricher types)
         @SuppressWarnings({ "unchecked", "rawtypes" })
-        public B publishing(AttributeSensor<? extends T> val) {
+        public <T2 extends T> AggregatorBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
             this.publishing = (AttributeSensor) checkNotNull(val);
-            return self();
+            return (AggregatorBuilder) self();
         }
         public B from(Entity val) {
             this.fromEntity = checkNotNull(val);
@@ -236,7 +244,7 @@ public class Enrichers {
         }
     }
     
-    public abstract static class CombinerBuilder<S, T, B extends CombinerBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends Builder<B> {
         protected final List<AttributeSensor<? extends S>> combining;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
@@ -248,17 +256,17 @@ public class Enrichers {
         // For summing/averaging
         protected Object defaultValueForUnreportedSensors;
         
-        public CombinerBuilder(AttributeSensor<? extends S>... vals) {
+        public AbstractCombinerBuilder(AttributeSensor<? extends S>... vals) {
             this(ImmutableList.copyOf(vals));
         }
-        public CombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
+        public AbstractCombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
             checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty");
             this.combining = ImmutableList.<AttributeSensor<? extends S>>copyOf(vals);
         }
         @SuppressWarnings({ "unchecked", "rawtypes" })
-        public B publishing(AttributeSensor<? extends T> val) {
+        public <T2 extends T> CombinerBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
             this.publishing = (AttributeSensor) checkNotNull(val);
-            return self();
+            return (CombinerBuilder) this;
         }
         public B from(Entity val) {
             this.fromEntity = checkNotNull(val);
@@ -325,26 +333,20 @@ public class Enrichers {
         }
     }
 
-    /** builds an enricher which transforms a given sensor:
-     * <li> applying a function ({@link #computing(Function)}, or {@link #computingAverage()}/{@link #computingSum()}, mandatory);
-     * <li> and publishing it on the entity where the enricher is attached;
-     * <li> optionally taking the sensor from a different source entity ({@link #from(Entity)});
-     * <li> and optionally publishing it as a different sensor ({@link #publishing(AttributeSensor)});
-     * <p> (You should supply at least one of the optional values, of course, otherwise the enricher may loop endlessly!) */
-    public abstract static class TransformerBuilder<S, T, B extends TransformerBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends Builder<B> {
         protected final AttributeSensor<S> transforming;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
         protected Function<? super S, ?> computing;
         protected Function<? super SensorEvent<S>, ?> computingFromEvent;
 
-        public TransformerBuilder(AttributeSensor<S> val) {
+        public AbstractTransformerBuilder(AttributeSensor<S> val) {
             this.transforming = checkNotNull(val);
         }
         @SuppressWarnings({ "unchecked", "rawtypes" })
-        public B publishing(AttributeSensor<? extends T> val) {
+        public <T2 extends T> TransformerBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
             this.publishing = (AttributeSensor) checkNotNull(val);
-            return self();
+            return (TransformerBuilder) this;
         }
         public B from(Entity val) {
             this.fromEntity = checkNotNull(val);
@@ -382,25 +384,25 @@ public class Enrichers {
         }
     }
 
-    public abstract static class PropagatorBuilder<B extends PropagatorBuilder<B>> extends Builder<B> {
+    protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends Builder<B> {
         protected final Map<? extends Sensor<?>, ? extends Sensor<?>> propagating;
         protected final Boolean propagatingAll;
         protected final Iterable<? extends Sensor<?>> propagatingAllBut;
         protected Entity fromEntity;
         
-        public PropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
+        public AbstractPropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
             checkArgument(checkNotNull(vals).size() > 0, "propagating-sensors must be non-empty");
             this.propagating = vals;
             this.propagatingAll = null;
             this.propagatingAllBut = null;
         }
-        public PropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
+        public AbstractPropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
             this(newIdentityMap(ImmutableSet.copyOf(vals)));
         }
-        public PropagatorBuilder(Sensor<?>... vals) {
+        public AbstractPropagatorBuilder(Sensor<?>... vals) {
             this(newIdentityMap(ImmutableSet.copyOf(vals)));
         }
-        public PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
+        public AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
             // Ugly constructor! Taking boolean to differentiate it from others; could use a static builder
             // but feels like overkill having a builder for a builder, being called by a builder!
             checkArgument(propagatingAll, "Not propagating all; use PropagatingAll(vals)");
@@ -507,41 +509,41 @@ public class Enrichers {
         }
     }
 
-    private static class ConcreteInitialBuilder extends InitialBuilder<ConcreteInitialBuilder> {
+    public static class InitialBuilder extends AbstractInitialBuilder<InitialBuilder> {
     }
 
-    private static class ConcreteAggregatorBuilder<S, T> extends AggregatorBuilder<S, T, ConcreteAggregatorBuilder<S, T>> {
-        public ConcreteAggregatorBuilder(AttributeSensor<S> aggregating) {
+    public static class AggregatorBuilder<S, T> extends AbstractAggregatorBuilder<S, T, AggregatorBuilder<S, T>> {
+        public AggregatorBuilder(AttributeSensor<S> aggregating) {
             super(aggregating);
         }
     }
 
-    private static class ConcretePropagatorBuilder extends PropagatorBuilder<ConcretePropagatorBuilder> {
-        public ConcretePropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
+    public static class PropagatorBuilder extends AbstractPropagatorBuilder<PropagatorBuilder> {
+        public PropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
             super(vals);
         }
-        public ConcretePropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
+        public PropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
             super(vals);
         }
-        public ConcretePropagatorBuilder(Sensor<?>... vals) {
+        public PropagatorBuilder(Sensor<?>... vals) {
             super(vals);
         }
-        public ConcretePropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
+        public PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
             super(propagatingAll, butVals);
         }
     }
 
-    private static class ConcreteCombinerBuilder<S, T> extends CombinerBuilder<S, T, ConcreteCombinerBuilder<S, T>> {
-        public ConcreteCombinerBuilder(AttributeSensor<? extends S>... vals) {
+    public static class CombinerBuilder<S, T> extends AbstractCombinerBuilder<S, T, CombinerBuilder<S, T>> {
+        public CombinerBuilder(AttributeSensor<? extends S>... vals) {
             super(vals);
         }
-        public ConcreteCombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
+        public CombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
             super(vals);
         }
     }
 
-    private static class ConcreteTransformerBuilder<S, T> extends TransformerBuilder<S, T, ConcreteTransformerBuilder<S, T>> {
-        public ConcreteTransformerBuilder(AttributeSensor<S> val) {
+    public static class TransformerBuilder<S, T> extends AbstractTransformerBuilder<S, T, TransformerBuilder<S, T>> {
+        public TransformerBuilder(AttributeSensor<S> val) {
             super(val);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3c714ed1/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index 8bfd2bb..ab3f199 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -28,12 +28,15 @@ import org.testng.annotations.Test;
 import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.basic.BasicGroup;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntitySubscriptionTest.RecordingSensorEventListener;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.basic.Sensors;
+import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.guava.Functionals;
@@ -41,6 +44,7 @@ import brooklyn.util.text.StringFunctions;
 
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -107,7 +111,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
     public void testCombiningRespectsUnchanged() {
         entity.addEnricher(Enrichers.builder()
                 .combining(NUM1, NUM2)
-                .publishing(NUM3)
+                .<Object>publishing(NUM3)
                 .computing(new Function<Iterable<Integer>, Object>() {
                         @Override public Object apply(Iterable<Integer> input) {
                             if (input != null && Iterables.contains(input, 123)) {
@@ -156,7 +160,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.addEnricher(Enrichers.builder()
                 .transforming(NUM1)
                 .publishing(LONG1)
-                .computing(Functions.constant(Integer.valueOf(1)))
+                .computing(Functions.constant(Long.valueOf(1)))
                 .build());
         
         entity.setAttribute(NUM1, 123);
@@ -179,23 +183,60 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
     }
 
     @Test(groups="Integration") // because takes a second
-    public void testTransformingRespectsUnchanged() {
+    public void testTransformingRespectsUnchangedButWillRepublish() {
+        RecordingSensorEventListener record = new RecordingSensorEventListener();
+        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
+        
         entity.addEnricher(Enrichers.builder()
                 .transforming(STR1)
-                .publishing(STR2)
+                .<Object>publishing(STR2)
                 .computing(new Function<String, Object>() {
                         @Override public Object apply(String input) {
                             return ("ignoredval".equals(input)) ? Entities.UNCHANGED : input;
                         }})
                 .build());
+        Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(0));
         
         entity.setAttribute(STR1, "myval");
-        EntityTestUtils.assertAttributeEqualsEventually(entity, STR2, "myval");
+        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
         
         entity.setAttribute(STR1, "ignoredval");
         EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval");
+        
+        entity.setAttribute(STR1, "myval2");
+        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(2));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval2");
+        
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval3");
+        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(5));
     }
 
+    // TODO if we had something like suppressDuplicates(true) :
+//    public void testTransformingSuppressDuplicates() {
+//        RecordingSensorEventListener record = new RecordingSensorEventListener();
+//        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
+//        
+//        entity.addEnricher(Enrichers.builder()
+//                .transforming(STR1)
+//                .publishing(STR2)
+//                .computing(Functions.<String>identity())
+//                .suppressDuplicates(true)
+//                .build());
+//
+//        entity.setAttribute(STR1, "myval");
+//        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1));
+//        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
+//        
+//        entity.setAttribute(STR1, "myval2");
+//        entity.setAttribute(STR1, "myval2");
+//        entity.setAttribute(STR1, "myval3");
+//        EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3");
+//        Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(3));
+//    }
+
     @Test
     public void testPropagating() {
         entity.addEnricher(Enrichers.builder()
@@ -321,7 +362,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
                 .aggregating(NUM1)
                 .publishing(LONG1)
                 .fromMembers()
-                .computing(Functions.constant(Integer.valueOf(1)))
+                .computing(Functions.constant(Long.valueOf(1)))
                 .build());
         
         entity.setAttribute(NUM1, 123);
@@ -333,7 +374,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         group.addMember(entity);
         group.addEnricher(Enrichers.builder()
                 .aggregating(NUM1)
-                .publishing(LONG1)
+                .<Object>publishing(LONG1)
                 .fromMembers()
                 .computing(new Function<Iterable<Integer>, Object>() {
                         @Override public Object apply(Iterable<Integer> input) {


[07/26] git commit: add support for suppressing duplicates in enrichers, defaulting false for most, but true for UpdatingMap; and in Enrichers.builder() use better superclasses to support tags, uniqueTags

Posted by he...@apache.org.
add support for suppressing duplicates in enrichers, defaulting false for most, but true for UpdatingMap; and in Enrichers.builder() use better superclasses to support tags, uniqueTags


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/41deca4d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/41deca4d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/41deca4d

Branch: refs/heads/master
Commit: 41deca4d9428867d9c1d54ec3e5334fe183cfea2
Parents: 3c714ed
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 22:38:11 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:49 2014 -0400

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  | 111 +++++++++++++++----
 .../enricher/basic/AbstractEnricher.java        |  35 ++++++
 .../brooklyn/enricher/basic/Transformer.java    |   7 +-
 .../brooklyn/enricher/basic/UpdatingMap.java    |  13 ++-
 .../policy/basic/AbstractEntityAdjunct.java     |   2 +
 .../java/brooklyn/enricher/EnrichersTest.java   |  43 ++++---
 6 files changed, 163 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/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 0d3a0d5..23b1b83 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import brooklyn.enricher.basic.AbstractEnricher;
 import brooklyn.enricher.basic.Aggregator;
 import brooklyn.enricher.basic.Combiner;
 import brooklyn.enricher.basic.Propagator;
@@ -39,12 +40,14 @@ import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherSpec;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.text.Strings;
 
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -67,6 +70,48 @@ public class Enrichers {
         }
     }
     
+    public abstract static class AbstractEnricherBuilder<B extends AbstractEnricherBuilder<B>> extends Builder<B> {
+        final Class<? extends Enricher> enricherType;
+        Boolean suppressDuplicates;
+        String uniqueTag;
+        Set<Object> tags = MutableSet.of();
+        
+        public AbstractEnricherBuilder(Class<? extends Enricher> enricherType) {
+            this.enricherType = enricherType;
+        }
+        
+        public B uniqueTag(String tag) {
+            uniqueTag = Preconditions.checkNotNull(tag);
+            return self();
+        }
+        public B addTag(Object tag) {
+            tags.add(Preconditions.checkNotNull(tag));
+            return self();
+        }
+        public B suppressDuplicates(Boolean suppressDuplicates) {
+            this.suppressDuplicates = suppressDuplicates;
+            return self();
+        }
+
+        protected abstract String getDefaultUniqueTag();
+        
+        protected EnricherSpec<?> build() {
+            EnricherSpec<? extends Enricher> spec = EnricherSpec.create(enricherType);
+            
+            String uniqueTag2 = uniqueTag;
+            if (uniqueTag!=null)
+                uniqueTag2 = getDefaultUniqueTag();
+            if (uniqueTag2!=null)
+                spec.uniqueTag(uniqueTag2);
+            
+            if (!tags.isEmpty()) spec.tags(tags);
+            if (suppressDuplicates!=null)
+                spec.configure(AbstractEnricher.SUPPRESS_DUPLICATES, suppressDuplicates);
+            
+            return spec;
+        }
+    }
+    
     protected abstract static class AbstractInitialBuilder<B extends AbstractInitialBuilder<B>> extends Builder<B> {
         public PropagatorBuilder propagating(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
             return new PropagatorBuilder(vals);
@@ -117,7 +162,7 @@ public class Enrichers {
     }
 
 
-    protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
         protected final AttributeSensor<S> aggregating;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
@@ -132,6 +177,7 @@ public class Enrichers {
         protected Object valueToReportIfNoSensors;
         
         public AbstractAggregatorBuilder(AttributeSensor<S> aggregating) {
+            super(Aggregator.class);
             this.aggregating = aggregating;
         }
         @SuppressWarnings({ "unchecked", "rawtypes" })
@@ -195,6 +241,11 @@ public class Enrichers {
             this.excludingBlank = true;
             return self();
         }
+        @Override
+        protected String getDefaultUniqueTag() {
+            if (publishing==null) return null;
+            return "aggregator:"+publishing.getName();
+        }
         public EnricherSpec<?> build() {
             Predicate<Object> valueFilter;
             if (Boolean.TRUE.equals(excludingBlank)) {
@@ -208,9 +259,7 @@ public class Enrichers {
                 valueFilter = null;
             }
             // FIXME excludingBlank; use valueFilter? exclude means ignored entirely or substituted for defaultMemberValue?
-            return EnricherSpec.create(Aggregator.class)
-                    .uniqueTag("aggregator:"+publishing)
-                    .configure(MutableMap.builder()
+            return super.build().configure(MutableMap.builder()
                             .putIfNotNull(Aggregator.PRODUCER, fromEntity)
                             .put(Aggregator.TARGET_SENSOR, publishing)
                             .put(Aggregator.SOURCE_SENSOR, aggregating)
@@ -244,7 +293,7 @@ public class Enrichers {
         }
     }
     
-    protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
         protected final List<AttributeSensor<? extends S>> combining;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
@@ -260,6 +309,7 @@ public class Enrichers {
             this(ImmutableList.copyOf(vals));
         }
         public AbstractCombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
+            super(Combiner.class);
             checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty");
             this.combining = ImmutableList.<AttributeSensor<? extends S>>copyOf(vals);
         }
@@ -306,10 +356,13 @@ public class Enrichers {
             this.excludingBlank = true;
             return self();
         }
+        @Override
+        protected String getDefaultUniqueTag() {
+            if (publishing==null) return null;
+            return "combiner:"+publishing.getName();
+        }
         public EnricherSpec<?> build() {
-            return EnricherSpec.create(Combiner.class)
-                    .uniqueTag("combiner:"+publishing)
-                    .configure(MutableMap.builder()
+            return super.build().configure(MutableMap.builder()
                             .putIfNotNull(Combiner.PRODUCER, fromEntity)
                             .put(Combiner.TARGET_SENSOR, publishing)
                             .put(Combiner.SOURCE_SENSORS, combining)
@@ -333,7 +386,7 @@ public class Enrichers {
         }
     }
 
-    protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends Builder<B> {
+    protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
         protected final AttributeSensor<S> transforming;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
@@ -341,6 +394,7 @@ public class Enrichers {
         protected Function<? super SensorEvent<S>, ?> computingFromEvent;
 
         public AbstractTransformerBuilder(AttributeSensor<S> val) {
+            super(Transformer.class);
             this.transforming = checkNotNull(val);
         }
         @SuppressWarnings({ "unchecked", "rawtypes" })
@@ -360,10 +414,13 @@ public class Enrichers {
             this.computingFromEvent = checkNotNull(val);
             return self();
         }
+        @Override
+        protected String getDefaultUniqueTag() {
+            if (publishing==null) return null;
+            return "transformer:"+publishing.getName();
+        }
         public EnricherSpec<?> build() {
-            return EnricherSpec.create(Transformer.class)
-                    .uniqueTag("transformer:"+publishing)
-                    .configure(MutableMap.builder()
+            return super.build().configure(MutableMap.builder()
                             .putIfNotNull(Transformer.PRODUCER, fromEntity)
                             .put(Transformer.TARGET_SENSOR, publishing)
                             .put(Transformer.SOURCE_SENSOR, transforming)
@@ -384,13 +441,14 @@ public class Enrichers {
         }
     }
 
-    protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends Builder<B> {
+    protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends AbstractEnricherBuilder<B> {
         protected final Map<? extends Sensor<?>, ? extends Sensor<?>> propagating;
         protected final Boolean propagatingAll;
         protected final Iterable<? extends Sensor<?>> propagatingAllBut;
         protected Entity fromEntity;
         
         public AbstractPropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
+            super(Propagator.class);
             checkArgument(checkNotNull(vals).size() > 0, "propagating-sensors must be non-empty");
             this.propagating = vals;
             this.propagatingAll = null;
@@ -402,7 +460,8 @@ public class Enrichers {
         public AbstractPropagatorBuilder(Sensor<?>... vals) {
             this(newIdentityMap(ImmutableSet.copyOf(vals)));
         }
-        public AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
+        AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
+            super(Propagator.class);
             // Ugly constructor! Taking boolean to differentiate it from others; could use a static builder
             // but feels like overkill having a builder for a builder, being called by a builder!
             checkArgument(propagatingAll, "Not propagating all; use PropagatingAll(vals)");
@@ -414,7 +473,8 @@ public class Enrichers {
             this.fromEntity = checkNotNull(val);
             return self();
         }
-        public EnricherSpec<? extends Enricher> build() {
+        @Override
+        protected String getDefaultUniqueTag() {
             List<String> summary = MutableList.of();
             if (propagating!=null) {
                 for (Map.Entry<? extends Sensor<?>, ? extends Sensor<?>> entry: propagating.entrySet()) {
@@ -432,9 +492,10 @@ public class Enrichers {
                 summary.add("ALL_BUT:"+Joiner.on(",").join(allBut));
             }
             
-            return EnricherSpec.create(Propagator.class)
-                    .uniqueTag("propagating["+fromEntity.getId()+":"+Joiner.on(",").join(summary)+"]")
-                    .configure(MutableMap.builder()
+            return "propagating["+fromEntity.getId()+":"+Joiner.on(",").join(summary)+"]";
+        }
+        public EnricherSpec<? extends Enricher> build() {
+            return super.build().configure(MutableMap.builder()
                             .putIfNotNull(Propagator.PRODUCER, fromEntity)
                             .putIfNotNull(Propagator.SENSOR_MAPPING, propagating)
                             .putIfNotNull(Propagator.PROPAGATING_ALL, propagatingAll)
@@ -454,7 +515,7 @@ public class Enrichers {
         }
     }
 
-    public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends Builder<B> {
+    public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends AbstractEnricherBuilder<B> {
         protected AttributeSensor<Map<TKey,TVal>> targetSensor;
         protected AttributeSensor<? extends S> fromSensor;
         protected TKey key;
@@ -462,6 +523,7 @@ public class Enrichers {
         protected Boolean removingIfResultIsNull;
         
         public AbstractUpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> target) {
+            super(UpdatingMap.class);
             this.targetSensor = target;
         }
         @SuppressWarnings({ "unchecked", "rawtypes" })
@@ -484,10 +546,13 @@ public class Enrichers {
             this.removingIfResultIsNull = val;
             return self();
         }
+        @Override
+        protected String getDefaultUniqueTag() {
+            if (targetSensor==null || fromSensor==null) return null;
+            return "updating:"+targetSensor.getName()+"<-"+fromSensor.getName();
+        }
         public EnricherSpec<?> build() {
-            return EnricherSpec.create(UpdatingMap.class)
-                    .uniqueTag("updating:"+targetSensor+"<-"+fromSensor)
-                    .configure(MutableMap.builder()
+            return super.build().configure(MutableMap.builder()
                             .put(UpdatingMap.TARGET_SENSOR, targetSensor)
                             .put(UpdatingMap.SOURCE_SENSOR, fromSensor)
                             .putIfNotNull(UpdatingMap.KEY_IN_TARGET_SENSOR, key)
@@ -528,7 +593,7 @@ public class Enrichers {
         public PropagatorBuilder(Sensor<?>... vals) {
             super(vals);
         }
-        public PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
+        PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
             super(propagatingAll, butVals);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
index 9cba5b9..52a924a 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
@@ -18,15 +18,23 @@
  */
 package brooklyn.enricher.basic;
 
+import static com.google.common.base.Preconditions.checkState;
+
 import java.util.Map;
 
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.rebind.BasicEnricherRebindSupport;
 import brooklyn.entity.rebind.RebindSupport;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
 import brooklyn.mementos.EnricherMemento;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherType;
 import brooklyn.policy.basic.AbstractEntityAdjunct;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.Maps;
 
 /**
@@ -34,7 +42,11 @@ import com.google.common.collect.Maps;
 */
 public abstract class AbstractEnricher extends AbstractEntityAdjunct implements Enricher {
 
+    public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey("enricher.suppressDuplicates",
+        "Whether duplicate values published by this enricher should be suppressed");
+
     private final EnricherDynamicType enricherType;
+    protected Boolean suppressDuplicates;
 
     public AbstractEnricher() {
         this(Maps.newLinkedHashMap());
@@ -61,8 +73,31 @@ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements
     }
 
     @Override
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        this.suppressDuplicates = getConfig(SUPPRESS_DUPLICATES);
+    }
+    
+    @Override
     protected void onChanged() {
         requestPersist();
     }
+
+    @Override
+    protected <T> void emit(Sensor<T> sensor, T val) {
+        checkState(entity != null, "entity must first be set");
+        
+        if (sensor instanceof AttributeSensor) {
+            if (Boolean.TRUE.equals(suppressDuplicates)) {
+                T oldValue = entity.getAttribute((AttributeSensor<T>)sensor);
+                if (Objects.equal(oldValue, val))
+                    return;
+            }
+            entity.setAttribute((AttributeSensor<T>)sensor, val);
+        } else { 
+            entity.emit(sensor, val);
+        }
+
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/enricher/basic/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
index 768e7c5..6877ec9 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
@@ -52,7 +52,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
     public static ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
 
     public static ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
-
+    
     protected Function<? super SensorEvent<T>, ? extends U> transformation;
     protected Entity producer;
     protected Sensor<T> sourceSensor;
@@ -65,6 +65,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
     @Override
     public void setEntity(EntityLocal entity) {
         super.setEntity(entity);
+        
         final Function<? super T, ? extends U> transformationFromValue = (Function<? super T, ? extends U>) getConfig(TRANSFORMATION_FROM_VALUE);
         final Function<? super SensorEvent<T>, ? extends U> transformationFromEvent = (Function<? super SensorEvent<T>, ? extends U>) getConfig(TRANSFORMATION_FROM_EVENT);
         checkArgument(transformationFromEvent != null ^ transformationFromValue != null, "must set exactly one of %s or %s", TRANSFORMATION_FROM_VALUE.getName(), TRANSFORMATION_FROM_EVENT.getName());
@@ -107,7 +108,9 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
         if (v == Entities.UNCHANGED) {
             // nothing
         } else {
-            emit(targetSensor, TypeCoercions.coerce(v, targetSensor.getTypeToken()));
+            U newValue = TypeCoercions.coerce(v, targetSensor.getTypeToken());
+//            oldValue = entity.
+            emit(targetSensor, newValue);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
index f85852c..60bbe4b 100644
--- a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
+++ b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
@@ -36,6 +36,7 @@ import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.flags.SetFromFlag;
 
 import com.google.common.base.Function;
+import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
 /**
@@ -47,7 +48,8 @@ import com.google.common.reflect.TypeToken;
  * with default behaviour being to remove an entry if <code>null</code> is returned
  * but this can be overriden by setting {@link #REMOVING_IF_RESULT_IS_NULL} false.
  * {@link Entities#REMOVE} and {@link Entities#UNCHANGED} are also respeced as return values for the computation
- * (ignoring generics).  
+ * (ignoring generics).
+ * Unlike most other enrichers, this defaults to {@link AbstractEnricher#SUPPRESS_DUPLICATES} being true
  *  
  * @author alex
  *
@@ -80,6 +82,15 @@ public class UpdatingMap<S,TKey,TVal> extends AbstractEnricher implements Sensor
     protected Boolean removingIfResultIsNull;
 
     public UpdatingMap() {
+        this(Maps.newLinkedHashMap());
+    }
+
+    public UpdatingMap(Map<Object, Object> flags) {
+        super(flags);
+        if (suppressDuplicates==null) {
+            // this defaults to suppressing duplicates
+            suppressDuplicates = true;
+        }
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 4797b59..126968e 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -36,6 +36,7 @@ import brooklyn.basic.AbstractBrooklynObject;
 import brooklyn.basic.BrooklynObjectInternal;
 import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigMap;
+import brooklyn.enricher.basic.AbstractEnricher;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
 import brooklyn.entity.basic.EntityInternal;
@@ -238,6 +239,7 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
         this.entity = entity;
     }
     
+    /** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */
     protected <T> void emit(Sensor<T> sensor, T val) {
         checkState(entity != null, "entity must first be set");
         if (sensor instanceof AttributeSensor) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index ab3f199..4257c36 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -214,28 +214,27 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(5));
     }
 
-    // TODO if we had something like suppressDuplicates(true) :
-//    public void testTransformingSuppressDuplicates() {
-//        RecordingSensorEventListener record = new RecordingSensorEventListener();
-//        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
-//        
-//        entity.addEnricher(Enrichers.builder()
-//                .transforming(STR1)
-//                .publishing(STR2)
-//                .computing(Functions.<String>identity())
-//                .suppressDuplicates(true)
-//                .build());
-//
-//        entity.setAttribute(STR1, "myval");
-//        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1));
-//        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
-//        
-//        entity.setAttribute(STR1, "myval2");
-//        entity.setAttribute(STR1, "myval2");
-//        entity.setAttribute(STR1, "myval3");
-//        EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3");
-//        Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(3));
-//    }
+    public void testTransformingSuppressDuplicates() {
+        RecordingSensorEventListener record = new RecordingSensorEventListener();
+        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
+        
+        entity.addEnricher(Enrichers.builder()
+                .transforming(STR1)
+                .publishing(STR2)
+                .computing(Functions.<String>identity())
+                .suppressDuplicates(true)
+                .build());
+
+        entity.setAttribute(STR1, "myval");
+        Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
+        
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval3");
+        EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3");
+        Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(3));
+    }
 
     @Test
     public void testPropagating() {


[02/26] git commit: minor test utils fixes, including `assertThat`

Posted by he...@apache.org.
minor test utils fixes, including `assertThat`


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/141751b9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/141751b9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/141751b9

Branch: refs/heads/master
Commit: 141751b9213a28943b90e767349d11973a278083
Parents: a8bff36
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 22:04:45 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Aug 25 09:32:26 2014 +0100

----------------------------------------------------------------------
 .../src/main/java/brooklyn/test/TestUtils.groovy             | 8 ++++++--
 utils/common/src/main/java/brooklyn/test/Asserts.java        | 7 ++++++-
 .../brooklyn/util/collections/CollectionFunctionals.java     | 3 +++
 3 files changed, 15 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/141751b9/usage/test-support/src/main/java/brooklyn/test/TestUtils.groovy
----------------------------------------------------------------------
diff --git a/usage/test-support/src/main/java/brooklyn/test/TestUtils.groovy b/usage/test-support/src/main/java/brooklyn/test/TestUtils.groovy
index e163fad..78fefeb 100644
--- a/usage/test-support/src/main/java/brooklyn/test/TestUtils.groovy
+++ b/usage/test-support/src/main/java/brooklyn/test/TestUtils.groovy
@@ -24,7 +24,6 @@ import groovy.time.TimeDuration
 import java.util.concurrent.Callable
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
 
 import org.codehaus.groovy.runtime.InvokerInvocationException
 import org.slf4j.Logger
@@ -32,7 +31,8 @@ import org.slf4j.LoggerFactory
 
 import brooklyn.entity.Entity
 import brooklyn.event.AttributeSensor
-import brooklyn.util.time.Duration;
+import brooklyn.util.text.StringFunctions;
+import brooklyn.util.time.Duration
 
 import com.google.common.base.Predicate
 import com.google.common.base.Supplier
@@ -526,6 +526,10 @@ public class TestUtils {
         fail("Expected collection of size "+expectedSize+" but got size "+actualSize+": "+c);
     }
 
+    /**
+     * @deprecated since 0.7.0; use {@link Asserts#assertThat(Object, Predicate)} with {@link StringFunctions})}
+     */
+    @Deprecated
     public static void assertStringContainsLiteral(String string, String substring) {
         if (string==null) fail("String is null");
         if (substring==null) fail("Substring is null");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/141751b9/utils/common/src/main/java/brooklyn/test/Asserts.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/test/Asserts.java b/utils/common/src/main/java/brooklyn/test/Asserts.java
index 68a414f..d52c347 100644
--- a/utils/common/src/main/java/brooklyn/test/Asserts.java
+++ b/utils/common/src/main/java/brooklyn/test/Asserts.java
@@ -420,7 +420,12 @@ public class Asserts {
             throw new ExecutionException(throwable.get());
         }
     }
-    
+
+    public static <T> void assertThat(T object, Predicate<T> condition) {
+        if (condition.apply(object)) return;
+        fail("Failed "+condition+": "+object);
+    }
+
     @SuppressWarnings("rawtypes")
     private static boolean groovyTruth(Object o) {
         // TODO Doesn't handle matchers (see http://docs.codehaus.org/display/GROOVY/Groovy+Truth)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/141751b9/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
index 1e7ffb5..07088b5 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
@@ -42,6 +42,7 @@ public class CollectionFunctionals {
             public Integer get() {
                 return Iterables.size(collection);
             }
+            @Override public String toString() { return "sizeSupplier("+collection+")"; }
         };
     }
     
@@ -51,6 +52,7 @@ public class CollectionFunctionals {
             public Integer apply(Iterable<?> input) {
                 return Iterables.size(input);
             }
+            @Override public String toString() { return "sizeFunction"; }
         };
     }
 
@@ -60,6 +62,7 @@ public class CollectionFunctionals {
             public Set<K> apply(Map<K, ?> input) {
                 return input.keySet();
             }
+            @Override public String toString() { return "keys"; }
         };
     }
 


[24/26] git commit: docs for service state

Posted by he...@apache.org.
docs for service state


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/6a8af41e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6a8af41e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6a8af41e

Branch: refs/heads/master
Commit: 6a8af41e70ab96e361fb58953c2c3f072ff12678
Parents: 099a549
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 26 08:46:48 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:38:15 2014 -0400

----------------------------------------------------------------------
 .../entity/basic/AbstractGroupImpl.java         |  1 +
 .../defining-applications/service-state.md      | 73 ++++++++++++++++++++
 docs/use/guide/defining-applications/toc.json   |  2 +
 3 files changed, 76 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6a8af41e/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
index e3520f6..a8ace7f 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
@@ -93,6 +93,7 @@ public abstract class AbstractGroupImpl extends AbstractEntity implements Abstra
     protected void initEnrichers() {
         super.initEnrichers();
         
+        // check states and upness separately so they can be individually replaced if desired
         // problem if any children or members are on fire
         ServiceStateLogic.newEnricherFromChildrenState().checkChildrenAndMembers().requireRunningChildren(getConfig(RUNNING_QUORUM_CHECK)).addTo(this);
         // defaults to requiring at least one member or child who is up

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6a8af41e/docs/use/guide/defining-applications/service-state.md
----------------------------------------------------------------------
diff --git a/docs/use/guide/defining-applications/service-state.md b/docs/use/guide/defining-applications/service-state.md
new file mode 100644
index 0000000..b142fb1
--- /dev/null
+++ b/docs/use/guide/defining-applications/service-state.md
@@ -0,0 +1,73 @@
+---
+title: Service State
+layout: page
+toc: ../guide_toc.json
+categories: [use, guide, defining-applications]
+---
+
+Any entity can use the standard "service-up" and "service-state" 
+sensors to inform other entities and the GUI about its status.
+
+In normal operation, entities should publish at least one "service not-up indicator",
+using the `ServiceNotUpLogic.updateNotUpIndicator` method.  Each such indicator should have
+a unique name or input sensor.  `Attributes.SERVICE_UP` will then be updated automatically
+when there are no not-up indicators.
+
+When there are transient problems that can be detected, to trigger `ON_FIRE` status
+entity code can similarly set `ServiceProblemsLogic.updateProblemsIndicator` with a unique namespace,
+and subsequently clear it when the problem goes away.
+These problems are reflected at runtime in the `SERVICE_PROBLEMS` sensor,
+allowing multiple problems to be tracked independently.
+
+When an entity is changing the expected state, e.g. starting or stopping,
+the expected state can be set using `ServiceStateLogic.setExpectedState`;
+this expected lifecycle state is considered together with `SERVICE_UP` and `SERVICE_PROBLEMS`
+to compute the actual state.  By default the logic in `ComputeServiceState` is applied.
+
+For common entities, good out-of-the-box logic is applied, as follows:
+
+* For `SoftwareProcess` entities, lifecycle service state is updated by the framework
+  and a service not-up indicator is linked to the driver `isRunning()` check.
+  
+* For common parents, including `AbstractApplication` and `AbstractGroup` subclasses (including clusters, fabrics, etc),
+  the default enrichers analyse children and members to set a not-up indicator
+  (requiring at least one child or member who is up) and a problem indicator
+  (if any children or members are on-fire).
+  In some cases other quorum checks are preferable; this can be set e.g. by overriding 
+  the `UP_QUORUM_CHECK` or the `RUNNING_QUORUM_CHECK`, as follows:
+  
+      public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(AbstractGroup.UP_QUORUM_CHECK, 
+          "Require all children and members to be up for this node to be up",
+          QuorumChecks.all());
+
+  Alternatively the `initEnrichers()` method can be overridden to specify a custom-configured
+  enricher or set custom config key values (as done e.g. in `DynamicClusterImpl` so that
+  zero children is permitted provided when the initial size is configured to be 0).
+
+
+For sample code to set and more information on these methods' behaviours,
+see javadoc in `ServiceStateLogic`,
+overrides of `AbstractEntity.initEnrichers()`
+and tests in `ServiceStateLogicTests`.
+
+<!-- TODO include more documentation, sample code (ideally extracted on the fly from test cases so we know it works!) -->
+
+
+## Notes on Advanced Use
+
+The enricher to derive `SERVICE_UP` and `SERVICE_STATE_ACTUAL` from the maps and expected state values discussed above
+is added by the `AbstractEntity.initEnrichers()` method.
+This method can be overridden -- or excluded altogether by by overriding `init()` --
+and can add enrichers created using the `ServiceStateLogic.newEnricherFromChildren()` method
+suitably customized using methods on the returned spec object, for instance to look only at members
+or specify a quorum function (from `QuorumChecks`). 
+If different logic is required for computing `SERVICE_UP` and `SERVICE_STATE_ACTUAL`,
+use `ServiceStateLogic.newEnricherFromChildrenState()` and `ServiceStateLogic.newEnricherFromChildrenUp()`,
+noting that the first of these will replace the enricher added by the default `initEnrichers()`,
+whereas the second one runs with a different namespace (unique tag).
+For more information consult the javadoc on those classes.
+
+Entities can set `SERVICE_UP` and `SERVICE_STATE_ACTUAL` directly.
+Provided these entities never use the `SERVICE_NOT_UP_INDICATORS` and `SERVICE_PROBLEMS` map,
+the default enrichers will not override these values.
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6a8af41e/docs/use/guide/defining-applications/toc.json
----------------------------------------------------------------------
diff --git a/docs/use/guide/defining-applications/toc.json b/docs/use/guide/defining-applications/toc.json
index c8b32aa..08940a8 100644
--- a/docs/use/guide/defining-applications/toc.json
+++ b/docs/use/guide/defining-applications/toc.json
@@ -12,6 +12,8 @@
   "file":  "{{ site.url }}/use/guide/defining-applications/deploying-yaml.html" },
 { "title": "YAML Reference",
   "file":  "{{ site.url }}/use/guide/defining-applications/yaml-reference.html" },
+{ "title": "Service State",
+  "file":  "{{ site.url }}/use/guide/defining-applications/service-state.html" },
 { "title": "Maven Archetype",
   "file":  "{{ site.url }}/use/guide/defining-applications/archetype.html" }
 ]


[12/26] add SERVICE_PROBLEMS and enrichers in ServiceStateLogic to compute state from that. add enrichers to populate PROBLEMS and NOT_UP_INDICATORS from children and members: this removes a lot of ad hoc service-up computations; some quorum up logic e.g

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
new file mode 100644
index 0000000..de4f1ad
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -0,0 +1,440 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
+import brooklyn.enricher.basic.AbstractEnricher;
+import brooklyn.enricher.basic.AbstractMultipleSensorAggregator;
+import brooklyn.enricher.basic.UpdatingMap;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.Lifecycle.Transition;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.EnricherSpec;
+import brooklyn.policy.EnricherSpec.ExtensibleEnricherSpec;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+/** Logic, sensors and enrichers, and conveniences, for computing service status */ 
+public class ServiceStateLogic {
+
+    public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP;
+    public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS;
+    
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
+    public static final AttributeSensor<Lifecycle.Transition> SERVICE_STATE_EXPECTED = Attributes.SERVICE_STATE_EXPECTED;
+    public static final AttributeSensor<Map<String,Object>> SERVICE_PROBLEMS = Attributes.SERVICE_PROBLEMS;
+
+    /** static only; not for instantiation */
+    private ServiceStateLogic() {}
+
+    @SuppressWarnings("unchecked")
+    public static <TKey,TVal> void clearMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key) {
+        updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE);
+    }
+
+    /** update the given key in the given map sensor */
+    public static <TKey,TVal> void updateMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key, TVal v) {
+        Map<TKey, TVal> map = entity.getAttribute(sensor);
+
+        // TODO synchronize
+        
+        boolean created = (map==null);
+        if (created) map = MutableMap.of();
+                
+        boolean changed;
+        if (v == Entities.REMOVE) {
+            changed = map.containsKey(key);
+            if (changed)
+                map.remove(key);
+        } else {
+            TVal oldV = map.get(key);
+            if (oldV==null)
+                changed = (v!=null || !map.containsKey(key));
+            else
+                changed = !oldV.equals(v);
+            if (changed)
+                map.put(key, (TVal)v);
+        }
+        if (changed || created)
+            entity.setAttribute(sensor, map);
+    }
+    
+    public static void setExpectedState(Entity entity, Lifecycle state) {
+        ((EntityInternal)entity).setAttribute(Attributes.SERVICE_STATE_EXPECTED, new Lifecycle.Transition(state, new Date()));
+    }
+    public static Lifecycle getExpectedState(Entity entity) {
+        Transition expected = entity.getAttribute(Attributes.SERVICE_STATE_EXPECTED);
+        if (expected==null) return null;
+        return expected.getState();
+    }
+    public static boolean isExpectedState(Entity entity, Lifecycle state) {
+        return getExpectedState(entity)==state;
+    }
+    
+    public static class ServiceNotUpLogic {
+        /** static only; not for instantiation */
+        private ServiceNotUpLogic() {}
+        
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
+            return Enrichers.builder()
+                .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
+                .computing( /* cast hacks to support removing */ (Function)
+                    Functionals.<Map<String,?>>
+                        ifNotEquals(null).<Object>apply(Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)))
+                        .defaultValue(Entities.REMOVE) )
+                .uniqueTag("service.isUp if no service.notUp.indicators")
+                .build();
+        }
+        
+        /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the 
+         * {@link UpdatingMap} enricher for the given sensor reported */
+        public static void updateNotUpIndicator(EntityLocal entity, Sensor<?> sensor, Object value) {
+            updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName(), value);
+        }
+        /** clears any entry for the given sensor in the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map */
+        public static void clearNotUpIndicator(EntityLocal entity, Sensor<?> sensor) {
+            clearMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName());
+        }
+
+        public static void updateNotUpIndicatorRequiringNonEmptyList(EntityLocal entity, AttributeSensor<? extends Collection<?>> collectionSensor) {
+            Collection<?> nodes = entity.getAttribute(collectionSensor);
+            if (nodes==null || nodes.isEmpty()) ServiceNotUpLogic.updateNotUpIndicator(entity, collectionSensor, "Should have at least one entry");
+            else ServiceNotUpLogic.clearNotUpIndicator(entity, collectionSensor);
+        }
+        public static void updateNotUpIndicatorRequiringNonEmptyMap(EntityLocal entity, AttributeSensor<? extends Map<?,?>> mapSensor) {
+            Map<?, ?> nodes = entity.getAttribute(mapSensor);
+            if (nodes==null || nodes.isEmpty()) ServiceNotUpLogic.updateNotUpIndicator(entity, mapSensor, "Should have at least one entry");
+            else ServiceNotUpLogic.clearNotUpIndicator(entity, mapSensor);
+        }
+        
+    }
+    
+    /** Enricher which sets {@link Attributes#SERVICE_STATE_ACTUAL} on changes to 
+     * {@link Attributes#SERVICE_STATE_EXPECTED}, {@link Attributes#SERVICE_PROBLEMS}, and {@link Attributes#SERVICE_UP}
+     * <p>
+     * The default implementation uses {@link #computeActualStateWhenExpectedRunning(Map, Boolean)} if the last expected transition
+     * was to {@link Lifecycle#RUNNING} and 
+     * {@link #computeActualStateWhenNotExpectedRunning(Map, Boolean, brooklyn.entity.basic.Lifecycle.Transition)} otherwise.
+     * If these methods return null, the {@link Attributes#SERVICE_STATE_ACTUAL} sensor will be cleared (removed).
+     * Either of these methods can be overridden for custom logic, and that custom enricher can be created using 
+     * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity.
+     */
+    public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener<Object> {
+        public void setEntity(EntityLocal entity) {
+            super.setEntity(entity);
+            if (suppressDuplicates==null) {
+                // only publish on changes, unless it is configured otherwise
+                suppressDuplicates = Boolean.TRUE;
+            }
+            
+            subscribe(entity, SERVICE_PROBLEMS, this);
+            subscribe(entity, SERVICE_UP, this);
+            subscribe(entity, SERVICE_STATE_EXPECTED, this);
+            onEvent(null);
+        }
+
+        @Override
+        public void onEvent(@Nullable SensorEvent<Object> event) {
+            Preconditions.checkNotNull(entity, "Cannot handle subscriptions or compute state until associated with an entity");
+            
+            Map<String, Object> serviceProblems = entity.getAttribute(SERVICE_PROBLEMS);
+            Boolean serviceUp = entity.getAttribute(SERVICE_UP);
+            Lifecycle.Transition serviceExpected = entity.getAttribute(SERVICE_STATE_EXPECTED);
+            
+            if (serviceExpected!=null && serviceExpected.getState()==Lifecycle.RUNNING) {
+                setActualState( computeActualStateWhenExpectedRunning(serviceProblems, serviceUp) );
+            } else {
+                setActualState( computeActualStateWhenNotExpectedRunning(serviceProblems, serviceUp, serviceExpected) );
+            }
+        }
+
+        protected Lifecycle computeActualStateWhenExpectedRunning(Map<String, Object> problems, Boolean serviceUp) {
+            if (Boolean.TRUE.equals(serviceUp) && (problems==null || problems.isEmpty())) {
+                return Lifecycle.RUNNING;
+            } else {
+                return Lifecycle.ON_FIRE;
+            }
+        }
+        
+        protected Lifecycle computeActualStateWhenNotExpectedRunning(Map<String, Object> problems, Boolean up, Lifecycle.Transition stateTransition) {
+            if (stateTransition!=null) {
+                return stateTransition.getState();
+            } else if (problems!=null && !problems.isEmpty()) {
+                return Lifecycle.ON_FIRE;
+            } else {
+                return (up==null ? null : up ? Lifecycle.RUNNING : Lifecycle.STOPPED);
+            }
+        }
+
+        protected void setActualState(@Nullable Lifecycle state) {
+            emit(SERVICE_STATE_ACTUAL, (state==null ? Entities.REMOVE : state));
+        }
+    }
+    
+    public static final EnricherSpec<?> newEnricherForServiceStateFromProblemsAndUp() {
+        return newEnricherForServiceState(ComputeServiceState.class);
+    }
+    public static final EnricherSpec<?> newEnricherForServiceState(Class<? extends Enricher> type) {
+        return EnricherSpec.create(type).uniqueTag("service.state.actual from service.state.expected and service.problems");
+    }
+    
+    public static class ServiceProblemsLogic {
+        /** static only; not for instantiation */
+        private ServiceProblemsLogic() {}
+        
+        /** puts the given value into the {@link Attributes#SERVICE_PROBLEMS} map as if the 
+         * {@link UpdatingMap} enricher for the given sensor reported this value */
+        public static void updateProblemsIndicator(EntityLocal entity, Sensor<?> sensor, Object value) {
+            updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, sensor.getName(), value);
+        }
+        /** clears any entry for the given sensor in the {@link Attributes#SERVICE_PROBLEMS} map */
+        public static void clearProblemsIndicator(EntityLocal entity, Sensor<?> sensor) {
+            clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, sensor.getName());
+        }
+        /** as {@link #updateProblemsIndicator(EntityLocal, Sensor, Object)} */
+        public static void updateProblemsIndicator(EntityLocal entity, Effector<?> eff, Object value) {
+            updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, eff.getName(), value);
+        }
+        /** as {@link #clearProblemsIndicator(EntityLocal, Sensor)} */
+        public static void clearProblemsIndicator(EntityLocal entity, Effector<?> eff) {
+            clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, eff.getName());
+        }
+    }
+    
+    public static class ComputeServiceIndicatorsFromChildrenAndMembers extends AbstractMultipleSensorAggregator<Void> implements SensorEventListener<Object> {
+        /** standard unique tag identifying instances of this enricher at runtime, also used for the map sensor if no unique tag specified */
+        public final static String IDENTIFIER_DEFAULT = "service-lifecycle-indicators-from-children-and-members";
+        
+        /** as {@link #IDENTIFIER_LIFECYCLE}, but when a second distinct instance is responsible for computing service up */
+        public final static String IDENTIFIER_UP = "service-not-up-indicators-from-children-and-members";
+
+        public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKey(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.up", 
+            "Logic for checking whether this service is up, based on children and/or members, defaulting to allowing none but if there are any requiring at least one to be up", QuorumCheck.QuorumChecks.atLeastOneUnlessEmpty());
+        public static final ConfigKey<QuorumCheck> RUNNING_QUORUM_CHECK = ConfigKeys.newConfigKey(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.running", 
+            "Logic for checking whether this service is healthy, based on children and/or members running, defaulting to requiring none to be ON-FIRE", QuorumCheck.QuorumChecks.all());
+        public static final ConfigKey<Boolean> DERIVE_SERVICE_NOT_UP = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_up.publish", "Whether to derive a service-not-up indicator from children", true);
+        public static final ConfigKey<Boolean> DERIVE_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_problems.publish", "Whether to derive a service-problem indicator from children", true);
+
+        protected String getKeyForMapSensor() {
+            return Preconditions.checkNotNull(super.getUniqueTag());
+        }
+
+        @Override
+        protected void setEntityLoadingConfig() {
+            fromChildren = true;
+            fromMembers = true;
+            // above sets default
+            super.setEntityLoadingConfig();
+            if (fromMembers && (!(entity instanceof Group))) {
+                if (fromChildren) fromMembers=false;
+                else throw new IllegalStateException("Cannot monitor only members for non-group entity "+entity+": "+this);
+            }
+            Preconditions.checkNotNull(getKeyForMapSensor());
+        }
+
+        protected void setEntityLoadingTargetConfig() {
+            if (getConfig(TARGET_SENSOR)!=null)
+                throw new IllegalArgumentException("Must not set "+TARGET_SENSOR+" when using "+this);
+        }
+
+        public void setEntity(EntityLocal entity) {
+            super.setEntity(entity);
+            if (suppressDuplicates==null) {
+                // only publish on changes, unless it is configured otherwise
+                suppressDuplicates = Boolean.TRUE;
+            }
+        }
+
+        private final List<Sensor<?>> SOURCE_SENSORS = ImmutableList.<Sensor<?>>of(SERVICE_UP, SERVICE_STATE_ACTUAL);
+        @Override
+        protected Collection<Sensor<?>> getSourceSensors() {
+            return SOURCE_SENSORS;
+        }
+
+        @Override
+        protected void onUpdated() {
+            // override superclass to publish potentially several items
+
+            if (getConfig(DERIVE_SERVICE_PROBLEMS))
+                updateMapSensor(SERVICE_PROBLEMS, computeServiceProblems());
+
+            if (getConfig(DERIVE_SERVICE_NOT_UP))
+                updateMapSensor(SERVICE_NOT_UP_INDICATORS, computeServiceNotUp());
+        }
+
+        protected Object computeServiceNotUp() {
+            Map<Entity, Boolean> values = getValues(SERVICE_UP);
+            List<Entity> violators = MutableList.of();
+            for (Map.Entry<Entity, Boolean> state: values.entrySet()) {
+                if (!Boolean.TRUE.equals(state.getValue())) {
+                    violators.add(state.getKey());
+                }
+            }
+
+            QuorumCheck qc = getConfig(UP_QUORUM_CHECK);
+            if (qc!=null) {
+                if (qc.isQuorate(values.size()-violators.size(), values.size()))
+                    // quorate
+                    return null;
+
+                if (values.isEmpty()) return "No entities present";
+                if (violators.isEmpty()) return "Not enough entities";
+            } else {
+                if (violators.isEmpty())
+                    return null;
+            }
+
+            if (violators.size()==1) return violators.get(0)+" is not up";
+            if (violators.size()==values.size()) return "None of the entities are up";
+            return violators.size()+" entities are not up, including "+violators.get(0);
+        }
+
+        protected Object computeServiceProblems() {
+            Map<Entity, Lifecycle> values = getValues(SERVICE_STATE_ACTUAL);
+            int numRunning=0, numOnFire=0;
+            for (Lifecycle state: values.values()) {
+                if (state==Lifecycle.RUNNING) numRunning++;
+                else if (state==Lifecycle.ON_FIRE) numOnFire++;
+            }
+
+            QuorumCheck qc = getConfig(RUNNING_QUORUM_CHECK);
+            if (qc!=null) {
+                if (qc.isQuorate(numRunning, numOnFire+numRunning))
+                    // quorate
+                    return null;
+
+                if (numOnFire==0)
+                    return "Not enough entities running to be quorate";
+            } else {
+                if (numOnFire==0)
+                    return null;
+            }
+
+            return numOnFire+" entit"+Strings.ies(numOnFire)+" are on fire";
+        }
+
+        protected void updateMapSensor(AttributeSensor<Map<String, Object>> sensor, Object value) {
+            if (value!=null)
+                updateMapSensorEntry(entity, sensor, getKeyForMapSensor(), value);
+            else
+                clearMapSensorEntry(entity, sensor, getKeyForMapSensor());
+        }
+
+        /** not used; see specific `computeXxx` methods, invoked by overridden onUpdated */
+        @Override
+        protected Object compute() {
+            return null;
+        }
+    }
+    
+    public static class ComputeServiceIndicatorsFromChildrenAndMembersSpec extends ExtensibleEnricherSpec<ComputeServiceIndicatorsFromChildrenAndMembers,ComputeServiceIndicatorsFromChildrenAndMembersSpec> {
+        private static final long serialVersionUID = -607444925297963712L;
+        
+        protected ComputeServiceIndicatorsFromChildrenAndMembersSpec() {
+            this(ComputeServiceIndicatorsFromChildrenAndMembers.class);
+        }
+        
+        protected ComputeServiceIndicatorsFromChildrenAndMembersSpec(Class<? extends ComputeServiceIndicatorsFromChildrenAndMembers> clazz) {
+            super(clazz);
+        }
+
+        public void addTo(Entity entity) {
+            entity.addEnricher(this);
+        }
+
+        public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkChildrenAndMembers() {
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, true);
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, true);
+            return self();
+        }
+        public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkMembersOnly() {
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, true);
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, false);
+            return self();
+        }
+        public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkChildrenOnly() {
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, false);
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, true);
+            return self();
+        }
+
+        public ComputeServiceIndicatorsFromChildrenAndMembersSpec requireUpChildren(QuorumCheck check) {
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, check);
+            return self();
+        }
+        public ComputeServiceIndicatorsFromChildrenAndMembersSpec requireRunningChildren(QuorumCheck check) {
+            configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, check);
+            return self();
+        }
+    }
+
+    /** provides the default {@link ComputeServiceIndicatorsFromChildrenAndMembers} enricher, 
+     * using the default unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#IDENTIFIER_DEFAULT}),
+     * configured here to require none on fire, and either no children or at least one up child,
+     * the spec can be further configured as appropriate */
+    public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildren() {
+        return new ComputeServiceIndicatorsFromChildrenAndMembersSpec()
+            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.IDENTIFIER_DEFAULT);
+    }
+
+    /** as {@link #newEnricherFromChildren()} but only publishing service not-up indicators, 
+     * using a different unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#IDENTIFIER_UP}),
+     * listening to children only, ignoring lifecycle/service-state,
+     * and using the same logic 
+     * (viz looking only at children (not members) and requiring either no children or at least one child up) by default */
+    public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildrenUp() {
+        return newEnricherFromChildren()
+            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.IDENTIFIER_UP)
+            .checkChildrenOnly()
+            .configure(ComputeServiceIndicatorsFromChildrenAndMembers.DERIVE_SERVICE_PROBLEMS, false);
+    }
+    
+    /** as {@link #newEnricherFromChildren()} but only publishing service problems,
+     * listening to children and members, ignoring service up,
+     * and using the same logic 
+     * (viz looking at children and members and requiring none are on fire) by default */
+    public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildrenState() {
+        return newEnricherFromChildren()
+            .configure(ComputeServiceIndicatorsFromChildrenAndMembers.DERIVE_SERVICE_NOT_UP, false);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
deleted file mode 100644
index 8f4c6b4..0000000
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 brooklyn.entity.basic;
-
-import java.util.Map;
-
-import brooklyn.enricher.Enrichers;
-import brooklyn.enricher.basic.UpdatingMap;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.Sensor;
-import brooklyn.policy.EnricherSpec;
-import brooklyn.util.collections.CollectionFunctionals;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.guava.Functionals;
-
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-
-/** Logic, sensors and enrichers, and conveniences, for computing service status */ 
-public class ServiceStatusLogic {
-
-    public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP;
-    public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS;
-    
-    private ServiceStatusLogic() {}
-    
-    public static class ServiceNotUpLogic {
-        @SuppressWarnings({ "unchecked", "rawtypes" })
-        public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
-            return Enrichers.builder()
-                .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
-                .computing( /* cast hacks to support removing */ (Function)
-                    Functionals.<Map<String,?>>
-                        ifNotEquals(null).<Object>apply(Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)))
-                        .defaultValue(Entities.REMOVE) )
-                .uniqueTag("service.isUp if no service.notUp.indicators")
-                .build();
-        }
-        
-        /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the 
-         * {@link UpdatingMap} enricher for the given sensor reported this value (including {@link Entities#REMOVE}) */
-        public static void updateMapFromSensor(EntityLocal entity, Sensor<?> sensor, Object value) {
-            updateMapSensor(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName(), value);
-        }
-    }
-    
-
-    @SuppressWarnings("unchecked")
-    public static <TKey,TVal> void updateMapSensor(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor,
-            TKey key, Object v) {
-        Map<TKey, TVal> map = entity.getAttribute(sensor);
-
-        // TODO synchronize
-        
-        boolean created = (map==null);
-        if (created) map = MutableMap.of();
-                
-        boolean changed;
-        if (v == Entities.REMOVE) {
-            changed = map.containsKey(key);
-            if (changed)
-                map.remove(key);
-        } else {
-            TVal oldV = map.get(key);
-            if (oldV==null)
-                changed = (v!=null || !map.containsKey(key));
-            else
-                changed = !oldV.equals(v);
-            if (changed)
-                map.put(key, (TVal)v);
-        }
-        if (changed || created)
-            entity.setAttribute(sensor, map);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/group/DynamicCluster.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicCluster.java b/core/src/main/java/brooklyn/entity/group/DynamicCluster.java
index c44f302..f51a9ef 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicCluster.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicCluster.java
@@ -103,7 +103,7 @@ public interface DynamicCluster extends AbstractGroup, Cluster, MemberReplaceabl
     ConfigKey<Boolean> QUARANTINE_FAILED_ENTITIES = ConfigKeys.newBooleanConfigKey(
             "dynamiccluster.quarantineFailedEntities", "If true, will quarantine entities that fail to start; if false, will get rid of them (i.e. delete them)", true);
 
-    AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     BasicNotificationSensor<Entity> ENTITY_QUARANTINED = new BasicNotificationSensor<Entity>(Entity.class, "dynamiccluster.entityQuarantined", "Entity failed to start, and has been quarantined");
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
index 27ffaf8..1023db6 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -40,14 +39,13 @@ import brooklyn.entity.basic.AbstractGroupImpl;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityFactory;
 import brooklyn.entity.basic.EntityFactoryForLocation;
-import brooklyn.entity.basic.EntityFunctions;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Startable;
 import brooklyn.entity.trait.StartableMethods;
-import brooklyn.event.SensorEvent;
-import brooklyn.event.SensorEventListener;
 import brooklyn.location.Location;
 import brooklyn.location.basic.Locations;
 import brooklyn.location.cloud.AvailabilityZoneExtension;
@@ -147,6 +145,17 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
     }
 
     @Override
+    protected void initEnrichers() {
+        if (getConfigRaw(UP_QUORUM_CHECK, true).isAbsent() && getConfig(INITIAL_SIZE)==0) {
+            // if initial size is 0 then override up check to allow zero if empty
+            setConfig(UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty());
+        }
+        super.initEnrichers();
+        // override previous enricher so that only members are checked
+        ServiceStateLogic.newEnricherFromChildrenUp().checkMembersOnly().requireUpChildren(getConfig(UP_QUORUM_CHECK)).addTo(this);
+    }
+    
+    @Override
     public void setRemovalStrategy(Function<Collection<Entity>, Entity> val) {
         setConfig(REMOVAL_STRATEGY, checkNotNull(val, "removalStrategy"));
     }
@@ -247,13 +256,15 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
             setAttribute(SUB_LOCATIONS, findSubLocations(loc));
         }
 
-        setAttribute(SERVICE_STATE, Lifecycle.STARTING);
-        setAttribute(SERVICE_UP, calculateServiceUp());
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             if (isQuarantineEnabled()) {
-                QuarantineGroup quarantineGroup = addChild(EntitySpec.create(QuarantineGroup.class).displayName("quarantine"));
-                Entities.manage(quarantineGroup);
-                setAttribute(QUARANTINE_GROUP, quarantineGroup);
+                QuarantineGroup quarantineGroup = getAttribute(QUARANTINE_GROUP);
+                if (quarantineGroup==null || !Entities.isManaged(quarantineGroup)) {
+                    quarantineGroup = addChild(EntitySpec.create(QuarantineGroup.class).displayName("quarantine"));
+                    Entities.manage(quarantineGroup);
+                    setAttribute(QUARANTINE_GROUP, quarantineGroup);
+                }
             }
 
             int initialSize = getConfig(INITIAL_SIZE).intValue();
@@ -307,44 +318,11 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
             for (Policy it : getPolicies()) {
                 it.resume();
             }
-            setAttribute(SERVICE_STATE, Lifecycle.RUNNING);
-            setAttribute(SERVICE_UP, calculateServiceUp());
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Exception e) {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
-        } finally {
-            connectSensors();
-        }
-    }
-
-    protected void connectSensors() {
-        subscribeToChildren(this, SERVICE_STATE, new SensorEventListener<Lifecycle>() {
-            @Override
-            public void onEvent(SensorEvent<Lifecycle> event) {
-                setAttribute(SERVICE_STATE, calculateServiceState());
-            }
-        });
-        subscribeToChildren(this, SERVICE_UP, new SensorEventListener<Boolean>() {
-            @Override
-            public void onEvent(SensorEvent<Boolean> event) {
-                setAttribute(SERVICE_UP, calculateServiceUp());
-            }
-        });
-    }
-
-    protected Lifecycle calculateServiceState() {
-        Lifecycle currentState = getAttribute(SERVICE_STATE);
-        if (EnumSet.of(Lifecycle.ON_FIRE, Lifecycle.RUNNING).contains(currentState)) {
-            Iterable<Lifecycle> memberStates = Iterables.transform(getMembers(), EntityFunctions.attribute(SERVICE_STATE));
-            int running = Iterables.frequency(memberStates, Lifecycle.RUNNING);
-            int onFire = Iterables.frequency(memberStates, Lifecycle.ON_FIRE);
-            if ((getInitialQuorumSize() > 0 ? running < getInitialQuorumSize() : true) && onFire > 0) {
-                currentState = Lifecycle.ON_FIRE;
-            } else if (onFire == 0 && running > 0) {
-                currentState = Lifecycle.RUNNING;
-            }
         }
-        return currentState;
     }
 
     protected List<Location> findSubLocations(Location loc) {
@@ -386,10 +364,8 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
 
     @Override
     public void stop() {
-        setAttribute(SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         try {
-            setAttribute(SERVICE_UP, calculateServiceUp());
-
             for (Policy it : getPolicies()) { it.suspend(); }
 
             // run shrink without mutex to make things stop even if starting,
@@ -403,10 +379,9 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
             // (this ignores the quarantine node which is not stoppable)
             StartableMethods.stop(this);
 
-            setAttribute(SERVICE_STATE, Lifecycle.STOPPED);
-            setAttribute(SERVICE_UP, calculateServiceUp());
+            ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         } catch (Exception e) {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         }
     }
@@ -697,13 +672,6 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
         }
     }
 
-    /**
-     * Default impl is to be up when running, and !up otherwise.
-     */
-    protected boolean calculateServiceUp() {
-        return getAttribute(SERVICE_STATE) == Lifecycle.RUNNING;
-    }
-
     protected Map<Entity, Throwable> waitForTasksOnEntityStart(Map<? extends Entity,? extends Task<?>> tasks) {
         // TODO Could have CompoundException, rather than propagating first
         Map<Entity, Throwable> errors = Maps.newLinkedHashMap();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/group/DynamicFabric.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicFabric.java b/core/src/main/java/brooklyn/entity/group/DynamicFabric.java
index abd7f5f..51735ec 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicFabric.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicFabric.java
@@ -65,7 +65,7 @@ public interface DynamicFabric extends AbstractGroup, Startable, Fabric {
     public static final MapConfigKey<Object> CUSTOM_CHILD_FLAGS = new MapConfigKey<Object>(
             Object.class, "dynamicfabric.customChildFlags", "Additional flags to be passed to children when they are being created", ImmutableMap.<String,Object>of());
 
-    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     public void setMemberSpec(EntitySpec<?> memberSpec);
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java
index a72f77f..8ba08d0 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java
@@ -39,6 +39,7 @@ import brooklyn.entity.basic.EntityFactoryForLocation;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Changeable;
@@ -118,7 +119,7 @@ public class DynamicFabricImpl extends AbstractGroupImpl implements DynamicFabri
         if (newLocations.isEmpty()) newLocations.addAll(getLocations());
         int locIndex = 0;
         
-        setAttribute(SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             Map<Entity, Task<?>> tasks = Maps.newLinkedHashMap();
             
@@ -158,10 +159,10 @@ public class DynamicFabricImpl extends AbstractGroupImpl implements DynamicFabri
             }
             
             waitForTasksOnStart(tasks);
-            setAttribute(SERVICE_STATE, Lifecycle.RUNNING);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
             setAttribute(SERVICE_UP, true);
         } catch (Exception e) {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         }
     }
@@ -183,15 +184,15 @@ public class DynamicFabricImpl extends AbstractGroupImpl implements DynamicFabri
     
     @Override
     public void stop() {
-        setAttribute(SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         try {
             Iterable<Entity> stoppableChildren = Iterables.filter(getChildren(), Predicates.instanceOf(Startable.class));
             Task<?> invoke = Entities.invokeEffector(this, stoppableChildren, Startable.STOP);
 	        if (invoke != null) invoke.get();
-            setAttribute(SERVICE_STATE, Lifecycle.STOPPED);
+	        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
             setAttribute(SERVICE_UP, false);
         } catch (Exception e) {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java b/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java
index 5bb864d..91420e5 100644
--- a/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java
+++ b/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java
@@ -227,8 +227,12 @@ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializab
         private final ConfigKey<T> parentKey;
         
         public BasicConfigKeyOverwriting(ConfigKey<T> key, T defaultValue) {
+            this(key, key.getDescription(), defaultValue);
+        }
+        
+        public BasicConfigKeyOverwriting(ConfigKey<T> key, String newDescription, T defaultValue) {
             super(checkNotNull(key.getTypeToken(), "type"), checkNotNull(key.getName(), "name"), 
-                    key.getDescription(), defaultValue);
+                    newDescription, defaultValue);
             parentKey = key;
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
index 8630214..9d8bf4b 100644
--- a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
+++ b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
@@ -401,12 +401,12 @@ public class DependentConfiguration {
         
         /**
          * Will wait for the attribute on the given entity.
-         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE} then it will abort. 
+         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE_ACTUAL} then it will abort. 
          */
         public <T2> Builder<T2,T2> attributeWhenReady(Entity source, AttributeSensor<T2> sensor) {
             this.source = checkNotNull(source, "source");
             this.sensor = (AttributeSensor) checkNotNull(sensor, "sensor");
-            abortIf(source, Attributes.SERVICE_STATE, Predicates.equalTo(Lifecycle.ON_FIRE));
+            abortIf(source, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.ON_FIRE));
             return (Builder<T2, T2>) this;
         }
         /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 68f8162..6b4d49d 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -377,6 +377,7 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
                 .add("name", name)
                 .add("uniqueTag", uniqueTag)
                 .add("running", isRunning())
+                .add("entity", entity)
                 .add("id", getId())
                 .toString();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
index 1252e36..59bc7e8 100644
--- a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
+++ b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
@@ -22,11 +22,15 @@ import static org.testng.Assert.assertEquals;
 
 import java.util.Map;
 
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.Iterables;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherSpec;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.flags.SetFromFlag;
@@ -91,10 +95,14 @@ public class BasicEnricherTest extends BrooklynAppUnitTestSupport {
 
     @Test
     public void testSameUniqueTagEnricherNotAddedTwice() throws Exception {
-        MyEnricher enricher1 = app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
-        MyEnricher enricher2 = app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(94).uniqueTag("x"));
-        assertEquals(enricher2, enricher1);
+        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
+        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(94).uniqueTag("x"));
+        
         assertEquals(app.getEnrichers().size(), 1);
+        // the more recent one should dominate
+        Enricher enricher = Iterables.getOnlyElement(app.getEnrichers());
+        Assert.assertTrue(enricher.getTagSupport().containsTag(94));
+        Assert.assertFalse(enricher.getTagSupport().containsTag(99));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
index 3c4acbb..f6096a4 100644
--- a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
@@ -192,7 +192,7 @@ public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
                 .attributeWhenReady(entity, TestEntity.NAME)
                 .build());
 
-        entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
         try {
             assertDoneEventually(t);
             fail();
@@ -203,7 +203,7 @@ public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
 
     @Test
     public void testAttributeWhenReadyAbortsWhenAlreadyOnfireByDefault() throws Exception {
-        entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
         
         final Task<String> t = submit(DependentConfiguration.builder()
                 .attributeWhenReady(entity, TestEntity.NAME)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java b/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java
index 12c3b55..b52332b 100644
--- a/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java
@@ -369,7 +369,6 @@ public class DynamicGroupTest {
         };
         ((EntityLocal)group2).setConfig(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(TestEntity.class));
         app.addChild(group2);
-        group2.init();
         Entities.manage(group2);
         
         for (int i = 0; i < NUM_CYCLES; i++) {
@@ -421,7 +420,6 @@ public class DynamicGroupTest {
         };
         ((EntityLocal)group2).setConfig(DynamicGroup.ENTITY_FILTER, Predicates.<Object>equalTo(e3));
         app.addChild(group2);
-        group2.init();
         
         Thread t1 = new Thread(new Runnable() {
             @Override public void run() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
index 6363a81..0a86e31 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
@@ -37,6 +37,7 @@ import brooklyn.policy.PolicySpec;
 import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.Asserts;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.test.entity.TestEntityNoEnrichersImpl;
 import brooklyn.util.flags.SetFromFlag;
 
 import com.google.common.collect.ImmutableSet;
@@ -104,7 +105,7 @@ public class EntitySpecTest extends BrooklynAppUnitTestSupport {
     
     @Test
     public void testAddsEnricherSpec() throws Exception {
-        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class, TestEntityNoEnrichersImpl.class)
                 .enricher(EnricherSpec.create(MyEnricher.class)
                         .displayName("myenrichername")
                         .configure(MyEnricher.CONF1, "myconf1val")
@@ -119,7 +120,7 @@ public class EntitySpecTest extends BrooklynAppUnitTestSupport {
     @Test
     public void testAddsEnricher() throws Exception {
         MyEnricher enricher = new MyEnricher();
-        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class, TestEntityNoEnrichersImpl.class)
                 .enricher(enricher));
         
         assertEquals(Iterables.getOnlyElement(entity.getEnrichers()), enricher);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java b/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
index ae6b384..168f72c 100644
--- a/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
@@ -38,6 +38,7 @@ import brooklyn.policy.PolicySpec;
 import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.TestUtils;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.test.entity.TestEntityNoEnrichersImpl;
 import brooklyn.util.collections.MutableMap;
 
 import com.google.common.collect.ImmutableList;
@@ -116,9 +117,10 @@ public class PolicyRegistrationTest extends BrooklynAppUnitTestSupport {
     
     @Test
     public void testAddEnricherSpec() {
-        EntitySpecTest.MyEnricher enricher = entity.addEnricher(EnricherSpec.create(EntitySpecTest.MyEnricher.class));
+        TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class, TestEntityNoEnrichersImpl.class));
+        EntitySpecTest.MyEnricher enricher = entity2.addEnricher(EnricherSpec.create(EntitySpecTest.MyEnricher.class));
         assertNotNull(enricher);
-        assertEquals(entity.getEnrichers(), ImmutableList.of(enricher));
+        assertEquals(entity2.getEnrichers(), ImmutableList.of(enricher));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java b/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java
index c862f8a..ed1d7b6 100644
--- a/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java
+++ b/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java
@@ -57,10 +57,12 @@ import brooklyn.location.Location;
 import brooklyn.location.basic.SimulatedLocation;
 import brooklyn.management.Task;
 import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.test.entity.TestEntityImpl;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 
 import com.google.common.base.Function;
@@ -143,7 +145,8 @@ public class DynamicClusterTest extends BrooklynAppUnitTestSupport {
                 .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class))
                 .configure(DynamicCluster.INITIAL_SIZE, 0));
         cluster.start(ImmutableList.of(loc));
-        assertEquals(cluster.getAttribute(Attributes.SERVICE_STATE), Lifecycle.RUNNING);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
         assertTrue(cluster.getAttribute(Attributes.SERVICE_UP));
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java
index 27e5edc..fc89ec3 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java
@@ -286,8 +286,8 @@ public class RebindEnricherTest extends RebindTestFixtureWithApp {
     
     public static class MyTestEntityWithEnricher extends TestEntityImpl {
         @Override
-        public void init() {
-            super.init();
+        protected void initEnrichers() {
+            // don't add default ones
             addEnricher(EnricherSpec.create(MyEnricher.class).uniqueTag("x").tag(Identifiers.makeRandomId(8)));
             addEnricher(EnricherSpec.create(MyEnricher.class));
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
index 4b22592..c71eaa8 100644
--- a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
+++ b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
@@ -122,7 +122,7 @@ public abstract class BrooklynMementoPersisterTestFixture {
         assertTrue(Iterables.contains(reloadedMemento.getEntityIds(), entity.getId()));
         assertEquals(Iterables.getOnlyElement(reloadedMemento.getLocationIds()), location.getId());
         assertEquals(Iterables.getOnlyElement(reloadedMemento.getPolicyIds()), policy.getId());
-        assertEquals(Iterables.getOnlyElement(reloadedMemento.getEnricherIds()), enricher.getId());
+        assertTrue(reloadedMemento.getEnricherIds().contains(enricher.getId()));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/event/feed/http/HttpFeedTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/event/feed/http/HttpFeedTest.java b/core/src/test/java/brooklyn/event/feed/http/HttpFeedTest.java
index 1a9cf9f..f112890 100644
--- a/core/src/test/java/brooklyn/event/feed/http/HttpFeedTest.java
+++ b/core/src/test/java/brooklyn/event/feed/http/HttpFeedTest.java
@@ -47,6 +47,7 @@ import brooklyn.test.Asserts;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Functionals;
 import brooklyn.util.http.BetterMockWebServer;
 import brooklyn.util.http.HttpToolResponse;
 import brooklyn.util.time.Duration;
@@ -381,9 +382,9 @@ public class HttpFeedTest extends BrooklynAppUnitTestSupport {
                             return null;
                         }
                     })
-                    .onFailureOrException(EntityFunctions.settingSensorsConstantFunction(entity, MutableMap.<AttributeSensor<?>,Object>of(
+                    .onFailureOrException(Functionals.function(EntityFunctions.settingSensorsConstant(entity, MutableMap.<AttributeSensor<?>,Object>of(
                         SENSOR_INT, -1, 
-                        SENSOR_STRING, PollConfig.REMOVE)))
+                        SENSOR_STRING, PollConfig.REMOVE))))
                 .period(100))
                 .build();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java b/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java
index fab5aa3..c3e6dce 100644
--- a/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java
+++ b/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java
@@ -131,7 +131,7 @@ public abstract class EntityCleanupLongevityTestFixture {
         // TODO would like to assert this
 //        Assert.assertTrue( schedulers.isEmpty(), "Not empty schedulers: "+schedulers);
         // but weaker form for now
-        Assert.assertTrue( schedulers.size() <= iterations, "Not empty schedulers: "+schedulers);
+        Assert.assertTrue( schedulers.size() <= 3*iterations, "Not empty schedulers: "+schedulers.size()+" after "+iterations+", "+schedulers);
         
         // memory leak detection only applies to subclasses who run lots of iterations
         if (checkMemoryLeaks())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
index 64131f5..c2a3884 100644
--- a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
@@ -69,7 +69,7 @@ public class TestEntityImpl extends AbstractEntity implements TestEntity {
         return super.configure(flags);
     }
     
-    @Override
+    @Override // made public for testing
     public boolean isLegacyConstruction() {
         return super.isLegacyConstruction();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/test/java/brooklyn/test/entity/TestEntityNoEnrichersImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityNoEnrichersImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityNoEnrichersImpl.java
new file mode 100644
index 0000000..66ef7ae
--- /dev/null
+++ b/core/src/test/java/brooklyn/test/entity/TestEntityNoEnrichersImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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 brooklyn.test.entity;
+
+
+/**
+ * Mock entity for testing.
+ */
+public class TestEntityNoEnrichersImpl extends TestEntityImpl {
+
+    @Override
+    protected void initEnrichers() {
+        // no enrichers here, so we can test the explicit enrichers we set
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
index 46b00bb..a54ef58 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
@@ -36,8 +36,10 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.java.UsesJava;
@@ -115,6 +117,8 @@ public class CumulusRDFApplication extends AbstractApplication {
      */
     @Override
     public void init() {
+        super.init();
+        
         // Cassandra cluster
         EntitySpec<CassandraDatacenter> clusterSpec = EntitySpec.create(CassandraDatacenter.class)
                 .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class)
@@ -204,17 +208,15 @@ public class CumulusRDFApplication extends AbstractApplication {
         // TODO use a multi-region web cluster
         Collection<? extends Location> first = MutableList.copyOf(Iterables.limit(locations, 1));
 
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             Entities.invokeEffector(this, cassandra, Startable.START, MutableMap.of("locations", locations)).getUnchecked();
             Entities.invokeEffector(this, webapp, Startable.START, MutableMap.of("locations", first)).getUnchecked();
         } catch (Exception e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
+        } finally {
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         }
-        setAttribute(SERVICE_UP, true);
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
-
         log.info("Started CumulusRDF in " + locations);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
index f1ed949..bfd3a1c 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
@@ -70,6 +70,8 @@ public class WebClusterDatabaseExample extends AbstractApplication {
 
     @Override
     public void init() {
+        super.init();
+        
         MySqlNode mysql = addChild(EntitySpec.create(MySqlNode.class)
                 .configure("creationScriptUrl", DB_SETUP_SQL_URL));
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
index 672158e..b1d3915 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
@@ -118,6 +118,8 @@ public class WebClusterDatabaseExampleApp extends AbstractApplication implements
 
     @Override
     public void init() {
+        super.init();
+        
         MySqlNode mysql = addChild(
                 EntitySpec.create(MySqlNode.class)
                         .configure(MySqlNode.CREATION_SCRIPT_URL, Entities.getRequiredUrlConfig(this, DB_SETUP_SQL_URL)));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
index d275418..e9ab449 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
@@ -136,7 +136,7 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr
             DynamicTasks.markInessential();
             boolean previouslyRunning = isRunning();
             try {
-                getEntity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPING);
+                ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STOPPING);
                 stop();
             } catch (Exception e) {
                 // queue a failed task so that there is visual indication that this task had a failure,
@@ -154,11 +154,11 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr
 
         if (doFullStartOnRestart()) {
             DynamicTasks.waitForLast();
-            getEntity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+            ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING);
             start();
         } else {
             DynamicTasks.queue("launch", new Runnable() { public void run() {
-                getEntity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+                ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING);
                 launch();
             }});
             DynamicTasks.queue("post-launch", new Runnable() { public void run() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
index 05fab5e..f7ddddd 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
@@ -51,7 +51,7 @@ public interface SameServerEntity extends Entity, Startable {
             "provisioning.properties", "Custom properties to be passed in when provisioning a new machine",
             MutableMap.<String, Object>of());
     
-    AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     @SuppressWarnings("rawtypes")
     AttributeSensor<MachineProvisioningLocation> PROVISIONING_LOCATION = new BasicAttributeSensor<MachineProvisioningLocation>(

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
index eb90660..0551840 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
@@ -156,7 +156,7 @@ public interface SoftwareProcess extends Entity, Startable {
     public static final AttributeSensor<Boolean> SERVICE_PROCESS_IS_RUNNING = Sensors.newBooleanSensor("service.process.isRunning", 
         "Whether the process for the service is confirmed as running");
     
-    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
  
     public static final AttributeSensor<String> PID_FILE = Sensors.newStringSensor("softwareprocess.pid.file", "PID file");
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
index 797d4a3..60e3eb0 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
@@ -63,8 +63,7 @@ public class SoftwareProcessDriverLifecycleEffectorTasks extends MachineLifecycl
         entity().getDriver().restart();
         DynamicTasks.queue("post-restart", new Runnable() { public void run() {
             postStartCustom();
-            if (entity().getAttribute(Attributes.SERVICE_STATE) == Lifecycle.STARTING) 
-                entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.RUNNING);
         }});
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index 25c23df..e264dab 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -34,7 +34,6 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.ConfigKey;
 import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ServiceStatusLogic.ServiceNotUpLogic;
 import brooklyn.entity.drivers.DriverDependentEntity;
 import brooklyn.entity.drivers.EntityDriverManager;
 import brooklyn.event.feed.function.FunctionFeed;
@@ -116,17 +115,14 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     protected MachineLocation getMachineOrNull() {
         return Iterables.get(Iterables.filter(getLocations(), MachineLocation.class), 0, null);
     }
-    
+
     @Override
-    public void init() {
-        super.init();
-        
+    protected void initEnrichers() {
+        super.initEnrichers();
         addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
             .from(SERVICE_PROCESS_IS_RUNNING)
             .computing(Functionals.ifNotEquals(true).value("The software process for this entity does not appear to be running"))
             .build());
-        
-        addEnricher(ServiceNotUpLogic.newEnricherForServiceUpIfNoNotUpIndicators());
     }
     
   	/**
@@ -251,11 +247,13 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     public void onManagementStarting() {
         super.onManagementStarting();
         
-        Lifecycle state = getAttribute(SERVICE_STATE);
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
         if (state == null || state == Lifecycle.CREATED) {
             // Expect this is a normal start() sequence (i.e. start() will subsequently be called)
             setAttribute(SERVICE_UP, false);
-            setAttribute(SERVICE_STATE, Lifecycle.CREATED);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.CREATED);
+            // force actual to be created because this is expected subsequently
+            setAttribute(SERVICE_STATE_ACTUAL, Lifecycle.CREATED);
     	}
     }
 	
@@ -263,7 +261,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     public void onManagementStarted() {
         super.onManagementStarted();
         
-        Lifecycle state = getAttribute(SERVICE_STATE);
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
         if (state != null && state != Lifecycle.CREATED) {
             postRebind();
         }
@@ -271,7 +269,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     
     @Override
     public void rebind() {
-        Lifecycle state = getAttribute(SERVICE_STATE);
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
         if (state == null || state != Lifecycle.RUNNING) {
             log.warn("On rebind of {}, not rebinding because state is {}", this, state);
             return;
@@ -309,11 +307,12 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
         Entities.waitForServiceUp(this, Duration.of(duration, units));
     }
 
+    /** @deprecated since 0.7.0, this isn't a general test for modifiability, and was hardly ever used (now never used) */
+    @Deprecated
     public void checkModifiable() {
-        Lifecycle state = getAttribute(SERVICE_STATE);
-        if (getAttribute(SERVICE_STATE) == Lifecycle.RUNNING) return;
-        if (getAttribute(SERVICE_STATE) == Lifecycle.STARTING) return;
-        // TODO this check may be redundant or even inappropriate
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.RUNNING) return;
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) return;
         throw new IllegalStateException("Cannot configure entity "+this+" in state "+state);
     }
 
@@ -400,20 +399,21 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
             try {
                 isRunningResult = driver.isRunning();
             } catch (Exception  e) {
-                setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+                ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
                 // provide extra context info, as we're seeing this happen in strange circumstances
                 if (driver==null) throw new IllegalStateException(this+" concurrent start and shutdown detected");
                 throw new IllegalStateException("Error detecting whether "+this+" is running: "+e, e);
             }
             if (log.isDebugEnabled()) log.debug("checked {}, is running returned: {}", this, isRunningResult);
-            // slow exponential delay -- 1.1^N means after 40 tries and 50s elapsed, it reaches the max of 5s intervals  
+            // slow exponential delay -- 1.1^N means after 40 tries and 50s elapsed, it reaches the max of 5s intervals
+            // TODO use Repeater 
             delay = Math.min(delay*11/10, 5000);
         }
         if (!isRunningResult) {
             String msg = "Software process entity "+this+" did not pass is-running check within "+
                     "the required "+startTimeout+" limit ("+timer.getDurationElapsed().toStringRounded()+" elapsed)";
             log.warn(msg+" (throwing)");
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
             throw new IllegalStateException(msg);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
index 8407708..24c3eeb 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
@@ -33,9 +33,7 @@ import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityFunctions;
-import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.effector.EffectorBody;
-import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
 import brooklyn.event.feed.http.HttpFeed;
 import brooklyn.event.feed.http.HttpPollConfig;
@@ -43,6 +41,7 @@ import brooklyn.util.collections.Jsonya;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Functionals;
 import brooklyn.util.http.HttpTool;
 import brooklyn.util.http.HttpTool.HttpClientBuilder;
 import brooklyn.util.http.HttpToolResponse;
@@ -53,6 +52,7 @@ import brooklyn.util.task.Tasks;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Suppliers;
 import com.google.gson.Gson;
 
 public class BrooklynEntityMirrorImpl extends AbstractEntity implements BrooklynEntityMirror {
@@ -90,10 +90,9 @@ public class BrooklynEntityMirrorImpl extends AbstractEntity implements Brooklyn
             .period(getConfig(POLL_PERIOD))
             .poll(HttpPollConfig.forMultiple()
                 .onSuccess(mirrorSensors)
-                .onFailureOrException(EntityFunctions.settingSensorsConstantFunction(this, MutableMap.<AttributeSensor<?>,Object>of(
-                    Attributes.SERVICE_STATE, Lifecycle.ON_FIRE,
-                    MIRROR_STATUS, "error contacting service"
-                    ))) )
+                .onFailureOrException(Functionals.function(EntityFunctions.updatingSensorMapEntry(this, Attributes.SERVICE_PROBLEMS, "mirror-feed",
+                        Suppliers.ofInstance("error contacting service")
+                    ))))
             .build();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
index 3272f3c..b708b61 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -41,6 +41,7 @@ import brooklyn.entity.basic.EffectorStartableImpl.StartParameters;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
@@ -191,16 +192,15 @@ public abstract class MachineLifecycleEffectorTasks {
     
     // ---------------------
     
-    /** runs the tasks needed to start, wrapped by setting {@link Attributes#SERVICE_STATE} appropriately */ 
+    /** runs the tasks needed to start, wrapped by setting {@link Attributes#SERVICE_STATE_EXPECTED} appropriately */ 
     public void start(Collection<? extends Location> locations) {
-        entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(entity(), Lifecycle.STARTING);
         try {
             startInLocations(locations);
             DynamicTasks.waitForLast();
-            if (entity().getAttribute(Attributes.SERVICE_STATE) == Lifecycle.STARTING) 
-                entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.RUNNING);
         } catch (Throwable t) {
-            entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.ON_FIRE);
             throw Exceptions.propagate(t);
         }
     }
@@ -298,6 +298,7 @@ public abstract class MachineLifecycleEffectorTasks {
             entity().setAttribute(Attributes.HOSTNAME, machine.getAddress().getHostName());
             entity().setAttribute(Attributes.ADDRESS, machine.getAddress().getHostAddress());
             if (machine instanceof SshMachineLocation) {
+                @SuppressWarnings("resource")
                 SshMachineLocation sshMachine = (SshMachineLocation) machine;
                 UserAndHostAndPort sshAddress = UserAndHostAndPort.fromParts(sshMachine.getUser(), sshMachine.getAddress().getHostName(), sshMachine.getPort());
                 entity().setAttribute(Attributes.SSH_ADDRESS, sshAddress);
@@ -396,7 +397,7 @@ public abstract class MachineLifecycleEffectorTasks {
     
     /** default restart impl, stops processes if possible, then starts the entity again */
     public void restart() {
-        entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPING);
         DynamicTasks.queue("stopping (process)", new Callable<String>() { public String call() {
             DynamicTasks.markInessential();
             stopProcessesAtMachine();
@@ -407,11 +408,10 @@ public abstract class MachineLifecycleEffectorTasks {
         DynamicTasks.queue("starting", new Runnable() { public void run() {
             // startInLocations will look up the location, and provision a machine if necessary
             // (if it remembered the provisioning location)
-            entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.STARTING);
             startInLocations(null);
             DynamicTasks.waitForLast();
-            if (entity().getAttribute(Attributes.SERVICE_STATE) == Lifecycle.STARTING) 
-                entity().setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.RUNNING);
         }});
     }
 
@@ -424,17 +424,17 @@ public abstract class MachineLifecycleEffectorTasks {
         log.info("Stopping {} in {}", entity(), entity().getLocations());
         
         DynamicTasks.queue("pre-stop", new Callable<String>() { public String call() {
-            if (entity().getAttribute(SoftwareProcess.SERVICE_STATE)==Lifecycle.STOPPED) {
+            if (entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED) {
                 log.debug("Skipping stop of entity "+entity()+" when already stopped");
                 return "Already stopped";
             }
-            entity().setAttribute(SoftwareProcess.SERVICE_STATE, Lifecycle.STOPPING);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPING);
             entity().setAttribute(SoftwareProcess.SERVICE_UP, false);
             preStopCustom();
             return null;
         }});
         
-        if (entity().getAttribute(SoftwareProcess.SERVICE_STATE)==Lifecycle.STOPPED) {
+        if (entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED) {
             return;
         }
                
@@ -448,7 +448,7 @@ public abstract class MachineLifecycleEffectorTasks {
         
         // Release this machine (even if error trying to stop process - we rethrow that after)
         Task<StopMachineDetails<Integer>> stoppingMachine = DynamicTasks.queue("stopping (machine)", new Callable<StopMachineDetails<Integer>>() { public StopMachineDetails<Integer> call() {
-            if (entity().getAttribute(SoftwareProcess.SERVICE_STATE)==Lifecycle.STOPPED) {
+            if (entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED) {
                 log.debug("Skipping stop of entity "+entity()+" when already stopped");
                 return new StopMachineDetails<Integer>("Already stopped", 0);
             }
@@ -478,9 +478,9 @@ public abstract class MachineLifecycleEffectorTasks {
             }
             
             entity().setAttribute(SoftwareProcess.SERVICE_UP, false);
-            entity().setAttribute(SoftwareProcess.SERVICE_STATE, Lifecycle.STOPPED);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPED);
         } catch (Throwable e) {
-            entity().setAttribute(SoftwareProcess.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.ON_FIRE);
             Exceptions.propagate(e);
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
index 6edd9a9..daa6109 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
@@ -117,6 +117,7 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
             FunctionFeed.builder()
                 .entity(this)
                 .period(Duration.millis(10))
+                .onlyIfServiceUp()
                 .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING)
                     .onException(Functions.constant(Boolean.FALSE))
                     .callable(new Callable<Boolean>() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
index a1347bf..f719882 100644
--- a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
+++ b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
@@ -127,10 +127,10 @@ public class VanillaJavaAppTest {
             .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
             .configure("args", ImmutableList.of()));
         app.start(ImmutableList.of(loc));
-        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE), Lifecycle.RUNNING);
+        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
 
         javaProcess.stop();
-        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE), Lifecycle.STOPPED);
+        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.STOPPED);
     }
 
     @Test(groups={"Integration"})


[21/26] git commit: make consistent `Mutable{List, Set, Map}.as{Immutable, Unmodifiable, UnmodifiableCopy}()` methods, and more forgiving if null is present, deprecatign the inconsistent and break-prone `toImmutable()`

Posted by he...@apache.org.
make consistent `Mutable{List,Set,Map}.as{Immutable,Unmodifiable,UnmodifiableCopy}()` methods, and more forgiving if null is present, deprecatign the inconsistent and break-prone `toImmutable()`


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ab1381b4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ab1381b4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ab1381b4

Branch: refs/heads/master
Commit: ab1381b413543b69de62b498fe61f05b46cf9cae
Parents: 5db500e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 26 22:17:20 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:21:22 2014 -0400

----------------------------------------------------------------------
 .../drivers/downloads/BasicDownloadTargets.java |  2 +-
 .../brooklyn/event/basic/ListConfigKey.java     |  2 +-
 .../java/brooklyn/event/basic/SetConfigKey.java |  2 +-
 .../brooklyn/entity/basic/EntityTypeTest.java   |  4 +--
 .../brooklyn/location/jclouds/JcloudsUtil.java  |  2 +-
 .../brooklyn/entity/chef/KnifeTaskFactory.java  |  4 +--
 .../rest/transform/LocationTransformer.java     |  2 +-
 .../brooklyn/util/collections/MutableList.java  | 23 +++++++++++++++-
 .../brooklyn/util/collections/MutableMap.java   | 21 +++++++++++++++
 .../brooklyn/util/collections/MutableSet.java   | 28 +++++++++++++++++++-
 .../util/collections/MutableListTest.java       | 23 ++++++++++++++++
 11 files changed, 102 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/core/src/main/java/brooklyn/entity/drivers/downloads/BasicDownloadTargets.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/drivers/downloads/BasicDownloadTargets.java b/core/src/main/java/brooklyn/entity/drivers/downloads/BasicDownloadTargets.java
index eca50ea..2b1c545 100644
--- a/core/src/main/java/brooklyn/entity/drivers/downloads/BasicDownloadTargets.java
+++ b/core/src/main/java/brooklyn/entity/drivers/downloads/BasicDownloadTargets.java
@@ -94,7 +94,7 @@ public class BasicDownloadTargets implements DownloadTargets {
     
     protected BasicDownloadTargets(Builder builder) {
         primaries = ImmutableList.copyOf(builder.primaries);
-        fallbacks = MutableList.<String>builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().toImmutable();
+        fallbacks = MutableList.<String>builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().asUnmodifiable();
         canContinueResolving = builder.canContinueResolving;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/core/src/main/java/brooklyn/event/basic/ListConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/ListConfigKey.java b/core/src/main/java/brooklyn/event/basic/ListConfigKey.java
index b56d9e9..0ad4d54 100644
--- a/core/src/main/java/brooklyn/event/basic/ListConfigKey.java
+++ b/core/src/main/java/brooklyn/event/basic/ListConfigKey.java
@@ -76,7 +76,7 @@ public class ListConfigKey<V> extends AbstractCollectionConfigKey<List<? extends
     protected List<Object> merge(boolean unmodifiable, Iterable<?>... sets) {
         MutableList<Object> result = MutableList.of();
         for (Iterable<?> set: sets) result.addAll(set);
-        if (unmodifiable) return result.toImmutable();
+        if (unmodifiable) return result.asUnmodifiable();
         return result;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/core/src/main/java/brooklyn/event/basic/SetConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/SetConfigKey.java b/core/src/main/java/brooklyn/event/basic/SetConfigKey.java
index 2f04609..fcd4afe 100644
--- a/core/src/main/java/brooklyn/event/basic/SetConfigKey.java
+++ b/core/src/main/java/brooklyn/event/basic/SetConfigKey.java
@@ -67,7 +67,7 @@ public class SetConfigKey<V> extends AbstractCollectionConfigKey<Set<? extends V
     protected Set<Object> merge(boolean unmodifiable, Iterable<?>... sets) {
         MutableSet<Object> result = MutableSet.of();
         for (Iterable<?> set: sets) result.addAll(set);
-        if (unmodifiable) return result.toImmutable();
+        if (unmodifiable) return result.asUnmodifiable();
         return result;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java b/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java
index 5c54e22..6dd5693 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java
@@ -210,7 +210,7 @@ public class EntityTypeTest extends BrooklynAppUnitTestSupport {
     public void testRemoveSensor() throws Exception {
         entity.getMutableEntityType().removeSensor(SENSOR_ADDED);
         assertEquals(entity.getEntityType().getSensors(), 
-                MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).build().toImmutable());
+                MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).build().asUnmodifiable());
         
         TestUtils.assertEventually(
                 Suppliers.ofInstance(listener.events), 
@@ -222,7 +222,7 @@ public class EntityTypeTest extends BrooklynAppUnitTestSupport {
         entity.getMutableEntityType().removeSensor(SENSOR_ADDED.getName());
         entity.getMutableEntityType().removeSensor(POLICY_ADDED.getName());
         assertEquals(entity.getEntityType().getSensors(), 
-                MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).remove(POLICY_ADDED).build().toImmutable());
+                MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).remove(POLICY_ADDED).build().asUnmodifiable());
         
         TestUtils.assertEventually(
                 CollectionFunctionals.sizeSupplier(listener.events), 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
index 9cb4830..e71f727 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
@@ -273,7 +273,7 @@ public class JcloudsUtil implements JcloudsLocationConfig {
                 .put("credential", credential)
                 .putIfNotNull("endpoint", endpoint)
                 .build()
-                .toImmutable();
+                .asUnmodifiable();
 
         if (allowReuse) {
             ComputeService result = cachedComputeServices.get(cacheKey);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/software/base/src/main/java/brooklyn/entity/chef/KnifeTaskFactory.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/chef/KnifeTaskFactory.java b/software/base/src/main/java/brooklyn/entity/chef/KnifeTaskFactory.java
index 243ff8d..8a0ffed 100644
--- a/software/base/src/main/java/brooklyn/entity/chef/KnifeTaskFactory.java
+++ b/software/base/src/main/java/brooklyn/entity/chef/KnifeTaskFactory.java
@@ -72,7 +72,7 @@ public class KnifeTaskFactory<RET> extends SystemProcessTaskFactory<KnifeTaskFac
         MutableList<Function<ProcessTaskWrapper<?>, Void>> result = MutableList.copyOf(super.getCompletionListeners());
         if (throwOnCommonKnifeErrors != Boolean.FALSE)
             insertKnifeCompletionListenerIntoCompletionListenersList(result);
-        return result.toImmutable();
+        return result.asUnmodifiable();
     }
     
     public KnifeTaskFactory<RET> notThrowingOnCommonKnifeErrors() {
@@ -127,7 +127,7 @@ public class KnifeTaskFactory<RET> extends SystemProcessTaskFactory<KnifeTaskFac
         }
         if (numKnifes==0)
             result.add(buildKnifeCommand(numKnifes++));
-        return result.toImmutable();
+        return result.asUnmodifiable();
     }
     
     /** creates the command for running knife.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/usage/rest-server/src/main/java/brooklyn/rest/transform/LocationTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/transform/LocationTransformer.java b/usage/rest-server/src/main/java/brooklyn/rest/transform/LocationTransformer.java
index d9b5ff5..79549f2 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/transform/LocationTransformer.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/transform/LocationTransformer.java
@@ -179,7 +179,7 @@ public class LocationTransformer {
             MutableMap.of("self", URI.create("/v1/locations/" + l.getId()))
                 .addIfNotNull("parent", l.getParent()!=null ? URI.create("/v1/locations/"+l.getParent().getId()) : null)
                 .addIfNotNull("spec", specId!=null ? URI.create("/v1/locations/"+specId) : null)
-                .toImmutable() );
+                .asUnmodifiable() );
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
index 2c00243..a6638da 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
@@ -20,16 +20,22 @@ package brooklyn.util.collections;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
 import javax.annotation.Nullable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.collect.ImmutableList;
 
 public class MutableList<V> extends ArrayList<V> {
     private static final long serialVersionUID = -5533940507175152491L;
 
+    private static final Logger log = LoggerFactory.getLogger(MutableList.class);
+    
     public static <V> MutableList<V> of() {
         return new MutableList<V>();
     }
@@ -74,10 +80,25 @@ public class MutableList<V> extends ArrayList<V> {
         }
     }
     
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
     public ImmutableList<V> toImmutable() {
         return ImmutableList.copyOf(this);
     }
-    
+    public List<V> asImmutableCopy() {
+        try {
+            return ImmutableList.copyOf(this);
+        } catch (Exception e) {
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    public List<V> asUnmodifiable() {
+        return Collections.unmodifiableList(this);
+    }
+    public List<V> asUnmodifiableCopy() {
+        return Collections.unmodifiableList(MutableList.copyOf(this));
+    }
+
     public static <V> Builder<V> builder() {
         return new Builder<V>();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java b/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
index 2d21d67..2630dbf 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.util.collections;
 
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -25,6 +26,9 @@ import java.util.Map.Entry;
 
 import javax.annotation.Nullable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.util.guava.Maybe;
 
 import com.google.common.base.Predicate;
@@ -33,7 +37,9 @@ import com.google.common.collect.ImmutableMap;
 /** Map impl, exposing simple builder operations (add) in a fluent-style API,
  * where the final map is mutable.  You can also toImmutable. */
 public class MutableMap<K,V> extends LinkedHashMap<K,V> {
+    
     private static final long serialVersionUID = -2463168443382874384L;
+    private static final Logger log = LoggerFactory.getLogger(MutableMap.class);
 
     public static <K,V> MutableMap<K,V> of() {
         return new MutableMap<K,V>();
@@ -136,9 +142,24 @@ public class MutableMap<K,V> extends LinkedHashMap<K,V> {
         return Maybe.absent("No entry for key '"+key+"' in this map");
     }
     
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
     public ImmutableMap<K,V> toImmutable() {
         return ImmutableMap.copyOf(this);
     }
+    public Map<K,V> asImmutableCopy() {
+        try {
+            return ImmutableMap.copyOf(this);
+        } catch (Exception e) {
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    public Map<K,V> asUnmodifiable() {
+        return Collections.unmodifiableMap(this);
+    }
+    public Map<K,V> asUnmodifiableCopy() {
+        return Collections.unmodifiableMap(MutableMap.copyOf(this));
+    }
     
     public static <K, V> Builder<K, V> builder() {
         return new Builder<K,V>();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
index cee4176..0653cfd 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
@@ -26,11 +26,16 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 public class MutableSet<V> extends LinkedHashSet<V> {
+    
     private static final long serialVersionUID = 2330133488446834595L;
+    private static final Logger log = LoggerFactory.getLogger(MutableSet.class);
 
     public static <V> MutableSet<V> of() {
         return new MutableSet<V>();
@@ -69,10 +74,25 @@ public class MutableSet<V> extends LinkedHashSet<V> {
         super((source instanceof Collection) ? (Collection<? extends V>)source : Sets.newLinkedHashSet(source));
     }
     
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
     public Set<V> toImmutable() {
-    	// Don't use ImmutableSet as that does not accept nulls
+        // Don't use ImmutableSet as that does not accept nulls
         return Collections.unmodifiableSet(Sets.newLinkedHashSet(this));
     }
+    public Set<V> asImmutableCopy() {
+        try {
+            return ImmutableSet.copyOf(this);
+        } catch (Exception e) {
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    public Set<V> asUnmodifiable() {
+        return Collections.unmodifiableSet(this);
+    }
+    public Set<V> asUnmodifiableCopy() {
+        return Collections.unmodifiableSet(MutableSet.copyOf(this));
+    }
     
     public static <V> Builder<V> builder() {
         return new Builder<V>();
@@ -103,6 +123,12 @@ public class MutableSet<V> extends LinkedHashSet<V> {
             return this;
         }
         
+        public Builder<V> addAll(V[] values) {
+            for (V v : values) {
+                result.add(v);
+            }
+            return this;
+        }
         public Builder<V> addAll(Iterable<? extends V> iterable) {
             if (iterable instanceof Collection) {
                 result.addAll((Collection<? extends V>) iterable);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ab1381b4/utils/common/src/test/java/brooklyn/util/collections/MutableListTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/collections/MutableListTest.java b/utils/common/src/test/java/brooklyn/util/collections/MutableListTest.java
index 089cfe4..e7dc5bf 100644
--- a/utils/common/src/test/java/brooklyn/util/collections/MutableListTest.java
+++ b/utils/common/src/test/java/brooklyn/util/collections/MutableListTest.java
@@ -53,4 +53,27 @@ public class MutableListTest {
         Assert.assertEquals(b, a);
     }
 
+    public void testContainingNullAndUnmodifiable() {
+        MutableList<Object> x = MutableList.<Object>of("x", null);
+        Assert.assertTrue(x.contains(null));
+        
+        List<Object> x1 = x.asUnmodifiable();
+        List<Object> x2 = x.asUnmodifiableCopy();
+        List<Object> x3 = x.asImmutableCopy();
+        
+        x.remove(null);
+        Assert.assertFalse(x.contains(null));
+        Assert.assertFalse(x1.contains(null));
+        Assert.assertTrue(x2.contains(null));
+        Assert.assertTrue(x3.contains(null));
+        
+        try { x1.remove("x"); Assert.fail(); } catch (Exception e) { /* expected */ }
+        try { x2.remove("x"); Assert.fail(); } catch (Exception e) { /* expected */ }
+        try { x3.remove("x"); Assert.fail(); } catch (Exception e) { /* expected */ }
+        
+        Assert.assertTrue(x1.contains("x"));
+        Assert.assertTrue(x2.contains("x"));
+        Assert.assertTrue(x3.contains("x"));
+    }
+    
 }


[09/26] git commit: a bunch more service-up checks (WIP)

Posted by he...@apache.org.
a bunch more service-up checks (WIP)


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/94184b2f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/94184b2f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/94184b2f

Branch: refs/heads/master
Commit: 94184b2f65bfe8a00fd6fe170f22c276ad9b29db
Parents: 843f1fc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 23:48:26 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:49 2014 -0400

----------------------------------------------------------------------
 .../basic/lifecycle/ScriptHelperTest.java       | 25 +++++++++++---------
 1 file changed, 14 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/94184b2f/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
index ef61fb8..9894a9e 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
@@ -40,6 +40,7 @@ import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.FixedListMachineProvisioningLocation;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.test.EntityTestUtils;
+import brooklyn.util.time.Duration;
 
 import com.google.common.base.Functions;
 import com.google.common.collect.ImmutableList;
@@ -88,20 +89,22 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
         @Override public Class<?> getDriverInterface() {
             return SimulatedInessentialIsRunningDriver.class;
         }
-        
+
         @Override
         public void connectServiceUpIsRunning() {
+            super.connectServiceUpIsRunning();
+            // run more often
             FunctionFeed.builder()
-                    .entity(this)
-                    .period(500)
-                    .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_UP)
-                            .onException(Functions.constant(Boolean.FALSE))
-                            .callable(new Callable<Boolean>() {
-                                public Boolean call() {
-                                    return getDriver().isRunning();
-                                }
-                            }))
-                    .build();
+                .entity(this)
+                .period(Duration.millis(10))
+                .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING)
+                    .onException(Functions.constant(Boolean.FALSE))
+                    .callable(new Callable<Boolean>() {
+                        public Boolean call() {
+                            return getDriver().isRunning();
+                        }
+                    }))
+                .build();
         }
     }
     


[06/26] git commit: fix enricher type test with suppress duplicates

Posted by he...@apache.org.
fix enricher type test with suppress duplicates


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/c1ebb8f4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/c1ebb8f4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/c1ebb8f4

Branch: refs/heads/master
Commit: c1ebb8f44cb3baa24f05021b262b9bf25c864f39
Parents: 94184b2
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Aug 7 23:37:51 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:49 2014 -0400

----------------------------------------------------------------------
 core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c1ebb8f4/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java b/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
index 4b9744b..ec8525a 100644
--- a/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
+++ b/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
@@ -46,7 +46,7 @@ public class EnricherTypeTest {
     @Test
     public void testGetConfig() throws Exception {
         EnricherType enricherType = enricher.getEnricherType();
-        assertEquals(enricherType.getConfigKeys(), ImmutableSet.of(MyEnricher.CONF1, MyEnricher.CONF2));
+        assertEquals(enricherType.getConfigKeys(), ImmutableSet.of(MyEnricher.CONF1, MyEnricher.CONF2, AbstractEnricher.SUPPRESS_DUPLICATES));
         assertEquals(enricherType.getName(), MyEnricher.class.getCanonicalName());
         assertEquals(enricherType.getConfigKey("test.conf1"), MyEnricher.CONF1);
         assertEquals(enricherType.getConfigKey("test.conf2"), MyEnricher.CONF2);


[15/26] git commit: some notes on tidying the REST API

Posted by he...@apache.org.
some notes on tidying the REST API


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/cbc103a3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/cbc103a3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/cbc103a3

Branch: refs/heads/master
Commit: cbc103a3b64cbcc7bcc9530cdd1914606e5b2520
Parents: 97eed6b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 27 02:16:18 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:18 2014 -0400

----------------------------------------------------------------------
 usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java   | 3 ++-
 usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java     | 1 +
 .../rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java | 3 +++
 3 files changed, 6 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cbc103a3/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java
index c2ec68e..812ae2f 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java
@@ -79,7 +79,8 @@ public interface EffectorApi {
       @QueryParam("timeout")
       String timeout,
       
-      @ApiParam(name = "parameters", value = "Effector parameters (as key value pairs)", required = false)
+      @ApiParam(/* FIXME: giving a `name` in swagger @ApiParam seems wrong as this object is the body, not a named argument */ name = "parameters",
+          value = "Effector parameters (as key value pairs)", required = false)
       @Valid 
       Map<String, Object> parameters
   ) ;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cbc103a3/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java
index 6f66965..7154ca5 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java
@@ -57,6 +57,7 @@ public interface PolicyApi {
   @GET
   @Path("/current-state")
   @ApiOperation(value = "Fetch policy states in batch", notes="Returns a map of policy ID to whether it is active")
+  // FIXME method name -- this is nothing to do with config!
   public Map<String, Boolean> batchConfigRead(
       @ApiParam(value = "Application ID or name", required = true)
       @PathParam("application") String application,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cbc103a3/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java
index 1c53598..cfd6670 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/PolicyConfigApi.java
@@ -82,6 +82,9 @@ public interface PolicyConfigApi {
       @ApiParam(value = "Config key ID", required = true)
       @PathParam("config") String configKeyName
   ) ;
+  
+  // TODO support a POST directly to /{config} where the body is the value, useful e.g. when it's a map
+  // TODO and deprecate the /set endpoint item below
 
   @POST
   @Path("/{config}/set")


[17/26] rewrite of ServiceFailureDetector -- part of what it did before is now done by ServiceStateLogic of course, so now this extends that, and provides options for emitting notifications of ENTITY_FAILED, ENTITY_RECOVERED, as well as suppressing ON_FI

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/software/webapp/src/test/java/brooklyn/entity/webapp/JBossExample.groovy
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/JBossExample.groovy b/software/webapp/src/test/java/brooklyn/entity/webapp/JBossExample.groovy
deleted file mode 100644
index c47ae76..0000000
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/JBossExample.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp
-
-import brooklyn.entity.basic.AbstractApplication
-import brooklyn.entity.basic.Entities
-import brooklyn.entity.webapp.jboss.JBoss7Server
-import brooklyn.entity.webapp.jboss.JBoss7ServerImpl
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation
-
-/**
- * TODO Turn into unit or integration test, or delete
- * 
- * @deprecated This should either be turned into a unit/integration test, or deleted
- */
-@Deprecated
-class JBossExample extends AbstractApplication {
-
-    JBoss7Server s;
-    
-    @Override
-    public void init() {
-        s = new JBoss7ServerImpl(this, httpPort: "8080+", war:"classpath://hello-world.war");
-    }
-
-    public static void main(String[] args) {
-        def ex = new JBossExample();
-        ex.start( [ new LocalhostMachineProvisioningLocation(name:'london') ] )
-        Entities.dumpInfo(ex)
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy
deleted file mode 100644
index 7fbf8bc..0000000
--- a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 brooklyn.test.entity
-
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import brooklyn.entity.Effector
-import brooklyn.entity.Entity
-import brooklyn.entity.basic.MethodEffector
-import brooklyn.entity.basic.SoftwareProcessImpl
-import brooklyn.entity.effector.EffectorAndBody
-import brooklyn.entity.java.VanillaJavaAppImpl
-import brooklyn.entity.webapp.WebAppServiceConstants
-import brooklyn.location.Location
-import brooklyn.util.flags.SetFromFlag
-
-/**
- * Mock web application server entity for testing.
- */
-public class TestJavaWebAppEntity extends VanillaJavaAppImpl {
-	private static final Logger LOG = LoggerFactory.getLogger(TestJavaWebAppEntity.class);
-    public static final Effector<Void> START = new EffectorAndBody<Void>(SoftwareProcessImpl.START, new MethodEffector(TestJavaWebAppEntity.class, "customStart").getBody());
-
-    public TestJavaWebAppEntity(Map properties=[:], Entity parent=null) {
-        super(properties, parent)
-    }
-    
-    @SetFromFlag public int a;
-    @SetFromFlag public int b;
-    @SetFromFlag public int c;
-
-	public void waitForHttpPort() { }
-
-    
-	public void customStart(Collection<? extends Location> loc) {
-        LOG.trace "Starting {}", this
-    }
-
-    @Override
-	protected void doStop() {
-        LOG.trace "Stopping {}", this
-    }
-
-    @Override
-    public void doRestart() {
-        throw new UnsupportedOperationException();
-    }
-
-	@Override
-    String toString() {
-        return "Entity["+id[-8..-1]+"]"
-    }
-
-	public synchronized void spoofRequest() {
-		def rc = getAttribute(WebAppServiceConstants.REQUEST_COUNT) ?: 0
-		setAttribute(WebAppServiceConstants.REQUEST_COUNT, rc+1)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
new file mode 100644
index 0000000..96edbcd
--- /dev/null
+++ b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
@@ -0,0 +1,73 @@
+/*
+ * 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 brooklyn.test.entity;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.MethodEffector;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.effector.EffectorAndBody;
+import brooklyn.entity.java.VanillaJavaAppImpl;
+import brooklyn.entity.webapp.WebAppServiceConstants;
+import brooklyn.location.Location;
+import brooklyn.util.flags.SetFromFlag;
+
+/**
+ * Mock web application server entity for testing.
+ */
+public class TestJavaWebAppEntity extends VanillaJavaAppImpl {
+	private static final Logger LOG = LoggerFactory.getLogger(TestJavaWebAppEntity.class);
+    public static final Effector<Void> START = new EffectorAndBody<Void>(SoftwareProcessImpl.START, new MethodEffector<Void>(TestJavaWebAppEntity.class, "customStart").getBody());
+
+    @SetFromFlag public int a;
+    @SetFromFlag public int b;
+    @SetFromFlag public int c;
+
+    public TestJavaWebAppEntity() {}
+    public TestJavaWebAppEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) { super(flags, parent); }
+    
+	public void waitForHttpPort() { }
+
+    
+	public void customStart(Collection<? extends Location> loc) {
+        LOG.trace("Starting {}", this);
+    }
+
+    @Override
+	protected void doStop() {
+        LOG.trace("Stopping {}", this);
+    }
+
+    @Override
+    public void doRestart() {
+        throw new UnsupportedOperationException();
+    }
+
+	public synchronized void spoofRequest() {
+		Integer rc = getAttribute(WebAppServiceConstants.REQUEST_COUNT);
+		if (rc==null) rc = 0;
+		setAttribute(WebAppServiceConstants.REQUEST_COUNT, rc+1);
+	}
+}


[03/26] git commit: add a new "updatingMap" enricher which helps when multiple enrichers contribute to a map, and use it for the new chaining of service_up behaviour

Posted by he...@apache.org.
add a new "updatingMap" enricher which helps when multiple enrichers contribute to a map, and use it for the new chaining of service_up behaviour


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/a8bff36e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/a8bff36e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/a8bff36e

Branch: refs/heads/master
Commit: a8bff36ec5cbaaa4c4f10d0adfd464ba8d75b8a7
Parents: 45e3035
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 17:05:41 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Aug 25 09:32:26 2014 +0100

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  |  72 +++++++++
 .../brooklyn/enricher/basic/UpdatingMap.java    | 149 +++++++++++++++++++
 .../java/brooklyn/enricher/EnrichersTest.java   |  50 ++++++-
 .../entity/basic/SoftwareProcessImpl.java       |  26 +---
 4 files changed, 274 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a8bff36e/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 dab423c..6ddc780 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -30,6 +30,7 @@ import brooklyn.enricher.basic.Aggregator;
 import brooklyn.enricher.basic.Combiner;
 import brooklyn.enricher.basic.Propagator;
 import brooklyn.enricher.basic.Transformer;
+import brooklyn.enricher.basic.UpdatingMap;
 import brooklyn.entity.Entity;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.Sensor;
@@ -97,6 +98,12 @@ public class Enrichers {
         public <S> AggregatorBuilder<S, Object, ?> aggregating(AttributeSensor<S> val) {
             return new ConcreteAggregatorBuilder<S,Object>(val);
         }
+        /** creates an {@link UpdatingMap} enricher: 
+         * {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required
+         **/
+        public <S,TKey,TVal> UpdatingMapBuilder<S, TKey, TVal> updatingMap(AttributeSensor<Map<TKey,TVal>> target) {
+            return new UpdatingMapBuilder<S, TKey, TVal>(target);
+        }
     }
 
 
@@ -117,6 +124,8 @@ public class Enrichers {
         public AggregatorBuilder(AttributeSensor<S> aggregating) {
             this.aggregating = aggregating;
         }
+        // TODO change the signature of this to have correct type info as done for UpdatingMapBuilder.from(Sensor)
+        // (including change *Builder to Abstract*Builder and Concrete*Builder to *Builder, for all other enricher types)
         @SuppressWarnings({ "unchecked", "rawtypes" })
         public B publishing(AttributeSensor<? extends T> val) {
             this.publishing = (AttributeSensor) checkNotNull(val);
@@ -443,6 +452,61 @@ public class Enrichers {
         }
     }
 
+    public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends Builder<B> {
+        protected AttributeSensor<Map<TKey,TVal>> targetSensor;
+        protected AttributeSensor<? extends S> fromSensor;
+        protected TKey key;
+        protected Function<S, ? extends TVal> computing;
+        protected Boolean removingIfResultIsNull;
+        
+        public AbstractUpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> target) {
+            this.targetSensor = target;
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public <S2 extends S> UpdatingMapBuilder<S2,TKey,TVal> from(AttributeSensor<S2> fromSensor) {
+            this.fromSensor = checkNotNull(fromSensor);
+            return (UpdatingMapBuilder) this;
+        }
+        public B computing(Function<S,? extends TVal> val) {
+            this.computing = checkNotNull(val);
+            return self();
+        }
+        /** sets an explicit key to use; defaults to using the name of the source sensor specified in {@link #from(AttributeSensor)} */
+        public B key(TKey key) {
+            this.key = key;
+            return self();
+        }
+        /** sets explicit behaviour for treating <code>null</code> return values;
+         * default is to remove */
+        public B removingIfResultIsNull(boolean val) {
+            this.removingIfResultIsNull = val;
+            return self();
+        }
+        public EnricherSpec<?> build() {
+            return EnricherSpec.create(UpdatingMap.class)
+                    .uniqueTag("updating:"+targetSensor+"<-"+fromSensor)
+                    .configure(MutableMap.builder()
+                            .put(UpdatingMap.TARGET_SENSOR, targetSensor)
+                            .put(UpdatingMap.SOURCE_SENSOR, fromSensor)
+                            .putIfNotNull(UpdatingMap.KEY_IN_TARGET_SENSOR, key)
+                            .put(UpdatingMap.COMPUTING, computing)
+                            .putIfNotNull(UpdatingMap.REMOVING_IF_RESULT_IS_NULL, removingIfResultIsNull)
+                            .build());
+        }
+        
+        @Override
+        public String toString() {
+            return Objects.toStringHelper(this)
+                    .omitNullValues()
+                    .add("publishing", targetSensor)
+                    .add("fromSensor", fromSensor)
+                    .add("key", key)
+                    .add("computing", computing)
+                    .add("removingIfResultIsNull", removingIfResultIsNull)
+                    .toString();
+        }
+    }
+
     private static class ConcreteInitialBuilder extends InitialBuilder<ConcreteInitialBuilder> {
     }
 
@@ -482,6 +546,12 @@ public class Enrichers {
         }
     }
 
+    public static class UpdatingMapBuilder<S, TKey, TVal> extends AbstractUpdatingMapBuilder<S, TKey, TVal, UpdatingMapBuilder<S, TKey, TVal>> {
+        public UpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> val) {
+            super(val);
+        }
+    }
+
     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);
@@ -529,4 +599,6 @@ public class Enrichers {
         }
         return result;
     }
+    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a8bff36e/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
new file mode 100644
index 0000000..f85852c
--- /dev/null
+++ b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java
@@ -0,0 +1,149 @@
+/*
+ * 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 brooklyn.enricher.basic;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Function;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Enricher which updates an entry in a sensor map ({@link #TARGET_SENSOR}) 
+ * based on the value of another sensor ({@link #SOURCE_SENSOR}.
+ * <p>
+ * The key used defaults to the name of the source sensor but can be specified with {@link #KEY_IN_TARGET_SENSOR}.
+ * The value placed in the map is the result of applying the function in {@link #COMPUTING} to the sensor value,
+ * with default behaviour being to remove an entry if <code>null</code> is returned
+ * but this can be overriden by setting {@link #REMOVING_IF_RESULT_IS_NULL} false.
+ * {@link Entities#REMOVE} and {@link Entities#UNCHANGED} are also respeced as return values for the computation
+ * (ignoring generics).  
+ *  
+ * @author alex
+ *
+ * @param <S> source sensor type
+ * @param <TKey> key type in target sensor map
+ * @param <TVal> value type in target sensor map
+ */
+@SuppressWarnings("serial")
+public class UpdatingMap<S,TKey,TVal> extends AbstractEnricher implements SensorEventListener<S> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UpdatingMap.class);
+
+    @SetFromFlag("fromSensor")
+    public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
+    @SetFromFlag("targetSensor")
+    public static final ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
+    @SetFromFlag("key")
+    public static final ConfigKey<?> KEY_IN_TARGET_SENSOR = ConfigKeys.newConfigKey(Object.class, "enricher.updatingMap.keyInTargetSensor",
+        "Key to update in the target sensor map, defaulting to the name of the source sensor");
+    @SetFromFlag("computing")
+    public static final ConfigKey<Function<?, ?>> COMPUTING = ConfigKeys.newConfigKey(new TypeToken<Function<?,?>>() {}, "enricher.updatingMap.computing");
+    @SetFromFlag("removingIfResultIsNull")
+    public static final ConfigKey<Boolean> REMOVING_IF_RESULT_IS_NULL = ConfigKeys.newBooleanConfigKey("enricher.updatingMap.removingIfResultIsNull", 
+        "Whether the key in the target map is removed if the result if the computation is null");
+
+    protected AttributeSensor<S> sourceSensor;
+    protected AttributeSensor<Map<TKey,TVal>> targetSensor;
+    protected TKey key;
+    protected Function<S,? extends TVal> computing;
+    protected Boolean removingIfResultIsNull;
+
+    public UpdatingMap() {
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        this.sourceSensor = (AttributeSensor<S>) getRequiredConfig(SOURCE_SENSOR);
+        this.targetSensor = (AttributeSensor<Map<TKey,TVal>>) getRequiredConfig(TARGET_SENSOR);
+        this.key = (TKey) getConfig(KEY_IN_TARGET_SENSOR);
+        this.computing = (Function) getRequiredConfig(COMPUTING);
+        this.removingIfResultIsNull = getConfig(REMOVING_IF_RESULT_IS_NULL);
+
+        subscribe(entity, sourceSensor, this);
+        onUpdated();
+    }
+    
+    @Override
+    public void onEvent(SensorEvent<S> event) {
+        onUpdated();
+    }
+
+    /**
+     * Called whenever the values for the set of producers changes (e.g. on an event, or on a member added/removed).
+     */
+    @SuppressWarnings("unchecked")
+    protected void onUpdated() {
+        try {
+            Object v = computing.apply(entity.getAttribute(sourceSensor));
+            if (v == null && !Boolean.FALSE.equals(removingIfResultIsNull)) {
+                v = Entities.REMOVE;
+            }
+            if (v == Entities.UNCHANGED) {
+                // nothing
+            } else {
+                // TODO check synchronization
+                TKey key = this.key;
+                if (key==null) key = (TKey) sourceSensor.getName();
+                
+                Map<TKey, TVal> map = entity.getAttribute(targetSensor);
+
+                boolean created = (map==null);
+                if (created) map = MutableMap.of();
+                
+                boolean changed;
+                if (v == Entities.REMOVE) {
+                    changed = map.containsKey(key);
+                    if (changed)
+                        map.remove(key);
+                } else {
+                    TVal oldV = map.get(key);
+                    if (oldV==null)
+                        changed = (v!=null || !map.containsKey(key));
+                    else
+                        changed = !oldV.equals(v);
+                    if (changed)
+                        map.put(key, (TVal)v);
+                }
+                if (changed || created)
+                    emit(targetSensor, map);
+            }
+        } catch (Throwable t) {
+            LOG.warn("Error calculating map update for enricher "+this, t);
+            throw Exceptions.propagate(t);
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a8bff36e/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index 8dc9615..8bfd2bb 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -19,6 +19,7 @@
 package brooklyn.enricher;
 
 import java.util.Collection;
+import java.util.Map;
 import java.util.Set;
 
 import org.testng.annotations.BeforeMethod;
@@ -33,8 +34,9 @@ import brooklyn.event.SensorEvent;
 import brooklyn.event.basic.Sensors;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
-import brooklyn.util.guava.TypeTokens;
+import brooklyn.util.guava.Functionals;
 import brooklyn.util.text.StringFunctions;
 
 import com.google.common.base.Function;
@@ -45,6 +47,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
 
+@SuppressWarnings("serial")
 public class EnrichersTest extends BrooklynAppUnitTestSupport {
 
     public static final AttributeSensor<Integer> NUM1 = Sensors.newIntegerSensor("test.num1");
@@ -54,6 +57,9 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
     public static final AttributeSensor<String> STR2 = Sensors.newStringSensor("test.str2");
     public static final AttributeSensor<Set<Object>> SET1 = Sensors.newSensor(new TypeToken<Set<Object>>() {}, "test.set1", "set1 descr");
     public static final AttributeSensor<Long> LONG1 = Sensors.newLongSensor("test.long1");
+    public static final AttributeSensor<Map<String,String>> MAP1 = Sensors.newSensor(new TypeToken<Map<String,String>>() {}, "test.map1", "map1 descr");
+    @SuppressWarnings("rawtypes")
+    public static final AttributeSensor<Map> MAP2 = Sensors.newSensor(Map.class, "test.map2");
     
     private TestEntity entity;
     private TestEntity entity2;
@@ -68,6 +74,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
     }
     
+    @SuppressWarnings("unchecked")
     @Test
     public void testAdding() {
         entity.addEnricher(Enrichers.builder()
@@ -81,6 +88,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 5);
     }
     
+    @SuppressWarnings("unchecked")
     @Test
     public void testCombiningWithCustomFunction() {
         entity.addEnricher(Enrichers.builder()
@@ -94,6 +102,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 1);
     }
     
+    @SuppressWarnings("unchecked")
     @Test(groups="Integration") // because takes a second
     public void testCombiningRespectsUnchanged() {
         entity.addEnricher(Enrichers.builder()
@@ -147,7 +156,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.addEnricher(Enrichers.builder()
                 .transforming(NUM1)
                 .publishing(LONG1)
-                .computing((Function)Functions.constant(Integer.valueOf(1)))
+                .computing(Functions.constant(Integer.valueOf(1)))
                 .build());
         
         entity.setAttribute(NUM1, 123);
@@ -312,7 +321,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
                 .aggregating(NUM1)
                 .publishing(LONG1)
                 .fromMembers()
-                .computing((Function)Functions.constant(Integer.valueOf(1)))
+                .computing(Functions.constant(Integer.valueOf(1)))
                 .build());
         
         entity.setAttribute(NUM1, 123);
@@ -342,4 +351,39 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.setAttribute(NUM1, 987654);
         EntityTestUtils.assertAttributeEqualsContinually(group, LONG1, Long.valueOf(123));
     }
+    @Test
+    public void testUpdatingMap1() {
+        entity.addEnricher(Enrichers.builder()
+                .updatingMap(MAP1)
+                .from(LONG1)
+                .computing(Functionals.when(-1L).value("-1 is not allowed"))
+                .build());
+        
+        doUpdatingMapChecks(MAP1);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testUpdatingMap2() {
+        entity.addEnricher(Enrichers.builder()
+                .updatingMap((AttributeSensor)MAP2)
+                .from(LONG1)
+                .computing(Functionals.when(-1L).value("-1 is not allowed"))
+                .build());
+        
+        doUpdatingMapChecks(MAP2);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected void doUpdatingMapChecks(AttributeSensor mapSensor) {
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of());
+        
+        entity.setAttribute(LONG1, -1L);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of(
+            LONG1.getName(), "-1 is not allowed"));
+        
+        entity.setAttribute(LONG1, 1L);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a8bff36e/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index fb872b0..56bb319 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -51,13 +51,13 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Functionals;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.time.CountdownTimer;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 
-import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -163,25 +163,11 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
                         }))
                 .build();
 
-        // FIXME quick-and-dirty change
-        Function<Boolean, Map<String,Object>> f = new Function<Boolean, Map<String,Object>>() {
-            @Override
-            public Map<String, Object> apply(Boolean input) {
-                Map<String, Object> result = getAttribute(Attributes.SERVICE_NOT_UP_INDICATORS);
-                if (result==null) result = MutableMap.of();
-                // TODO only change/publish if it needs changing...
-                if (Boolean.TRUE.equals(input)) {
-                    result.remove(SERVICE_PROCESS_IS_RUNNING.getName());
-                    return result;
-                } else {
-                    result.put(SERVICE_PROCESS_IS_RUNNING.getName(), "Process not running (according to driver checkRunning)");                    
-                    return result;
-                }
-            }
-        };
-        addEnricher(Enrichers.builder().transforming(SERVICE_PROCESS_IS_RUNNING).publishing(Attributes.SERVICE_NOT_UP_INDICATORS)
-            .computing(f).build());
-        
+        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+            .from(SERVICE_PROCESS_IS_RUNNING)
+            .computing(Functionals.when(false).value("Process not running (according to driver checkRunning)"))
+            .build());
+
         // FIXME lives elsewhere
         addEnricher(Enrichers.builder().transforming(Attributes.SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
             .computing( Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)) ).build());


[23/26] git commit: convert SERVICE_STATE to _ACTUAL or _EXPECTED in many more places, and force recompute of _ACTUAL whenever _EXPECTED is changed

Posted by he...@apache.org.
convert SERVICE_STATE to _ACTUAL or _EXPECTED in many more places, and force recompute of _ACTUAL whenever _EXPECTED is changed


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/099a5496
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/099a5496
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/099a5496

Branch: refs/heads/master
Commit: 099a5496e4ec48bb31d11a3be82c92831aef2ceb
Parents: 31c5a0c
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 27 01:10:08 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:21:23 2014 -0400

----------------------------------------------------------------------
 .../brooklyn/entity/basic/AbstractEntity.java   |  2 +-
 .../entity/basic/ServiceStateLogic.java         |  5 +++++
 .../policy/ha/MemberFailureDetectionPolicy.java |  8 ++++---
 .../brooklyn/policy/ha/ServiceReplacer.java     |  5 ++---
 .../brooklyn/policy/ha/ServiceRestarter.java    |  5 +++--
 .../brooklyn/policy/ha/ServiceReplacerTest.java |  4 ++--
 .../policy/ha/ServiceRestarterTest.java         |  4 ++--
 .../entity/chef/ChefLifecycleEffectorTasks.java |  8 +++----
 .../brooklyn/entity/pool/ServerPoolImpl.java    |  4 ++--
 .../software/MachineLifecycleEffectorTasks.java |  2 +-
 .../mysql/AbstractToyMySqlEntityTest.java       |  4 ++--
 .../cassandra/CassandraDatacenterImpl.java      | 17 +++++++++++----
 .../nosql/cassandra/CassandraFabricImpl.java    |  2 +-
 .../sharding/MongoDBShardedDeploymentImpl.java  | 22 +++++++++++++-------
 .../nosql/cassandra/CassandraFabricTest.java    |  5 +++--
 .../entity/dns/AbstractGeoDnsService.java       |  2 +-
 .../entity/dns/AbstractGeoDnsServiceImpl.java   |  9 ++++++--
 .../geoscaling/GeoscalingDnsServiceImpl.java    |  5 +++--
 .../brooklyn/JavaWebAppsIntegrationTest.java    |  5 +++--
 .../brooklyn/VanillaBashNetcatYamlTest.java     | 10 ++++-----
 .../BrooklynEntityMirrorIntegrationTest.java    |  4 ++--
 .../rest/resources/ApplicationResource.java     |  2 +-
 22 files changed, 82 insertions(+), 52 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index a612b0c..5e21fd7 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -1047,7 +1047,7 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     }
     
     /**
-     * By default, adds enrichers to populate {@link Attributes#SERVICE_UP} and {@link Attributes#SERVICE_STATE}
+     * By default, adds enrichers to populate {@link Attributes#SERVICE_UP} and {@link Attributes#SERVICE_STATE_ACTUAL}
      * based on {@link Attributes#SERVICE_NOT_UP_INDICATORS}, 
      * {@link Attributes#SERVICE_STATE_EXPECTED} and {@link Attributes#SERVICE_PROBLEMS}
      * (doing nothing if these sensors are not used).

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index 388f80d..5154bff 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -114,6 +114,11 @@ public class ServiceStateLogic {
     
     public static void setExpectedState(Entity entity, Lifecycle state) {
         ((EntityInternal)entity).setAttribute(Attributes.SERVICE_STATE_EXPECTED, new Lifecycle.Transition(state, new Date()));
+        
+        Enricher enricher = EntityAdjuncts.findWithUniqueTag(entity.getEnrichers(), ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG);
+        if (enricher instanceof ComputeServiceState) {
+            ((ComputeServiceState)enricher).onEvent(null);
+        }
     }
     public static Lifecycle getExpectedState(Entity entity) {
         Transition expected = entity.getAttribute(Attributes.SERVICE_STATE_EXPECTED);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java b/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java
index d1423a4..5a6fa0a 100644
--- a/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java
+++ b/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java
@@ -43,6 +43,7 @@ import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
 
 /**
  * Detects when members of a group have failed/recovered, and emits ENTITY_FAILED or 
@@ -83,8 +84,9 @@ public class MemberFailureDetectionPolicy extends AbstractPolicy {
     @SetFromFlag("useServiceStateRunning")
     public static final ConfigKey<Boolean> USE_SERVICE_STATE_RUNNING = ConfigKeys.newBooleanConfigKey("useServiceStateRunning", "", true);
 
+    @SuppressWarnings("serial")
     @SetFromFlag("memberFilter")
-    public static final ConfigKey<Predicate<? super Entity>> MEMBER_FILTER = (ConfigKey) ConfigKeys.newConfigKey(Predicate.class, "memberFilter", "", Predicates.alwaysTrue());
+    public static final ConfigKey<Predicate<? super Entity>> MEMBER_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() {}, "memberFilter", "", Predicates.<Entity>alwaysTrue());
     
     private final Map<Entity, Long> memberFailures = Maps.newLinkedHashMap();
     private final Map<Entity, Long> memberLastUps = Maps.newLinkedHashMap();
@@ -108,7 +110,7 @@ public class MemberFailureDetectionPolicy extends AbstractPolicy {
         super.setEntity(entity);
         
         if (getConfig(USE_SERVICE_STATE_RUNNING)) {
-            subscribeToMembers((Group)entity, Attributes.SERVICE_STATE, new SensorEventListener<Lifecycle>() {
+            subscribeToMembers((Group)entity, Attributes.SERVICE_STATE_ACTUAL, new SensorEventListener<Lifecycle>() {
                 @Override public void onEvent(SensorEvent<Lifecycle> event) {
                     if (!acceptsMember(event.getSource())) return;
                     onMemberStatus(event.getSource(), event.getValue());
@@ -165,7 +167,7 @@ public class MemberFailureDetectionPolicy extends AbstractPolicy {
     
     private synchronized void onMemberAdded(Entity member) {
         if (getConfig(USE_SERVICE_STATE_RUNNING)) {
-            Lifecycle status = member.getAttribute(Attributes.SERVICE_STATE);
+            Lifecycle status = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
             onMemberStatus(member, status);
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java b/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
index 18c6f53..9142c78 100644
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
+++ b/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
@@ -32,12 +32,11 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
-import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
 import brooklyn.entity.group.StopFailedRuntimeException;
 import brooklyn.entity.trait.MemberReplaceable;
 import brooklyn.event.Sensor;
@@ -206,7 +205,7 @@ public class ServiceReplacer extends AbstractPolicy {
         consecutiveReplacementFailureTimes.add(currentTimeMillis());
         
         if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
-            entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceProblemsLogic.updateProblemsIndicator(entity, "ServiceReplacer", "replacement failed: "+msg);
         }
         entity.emit(ENTITY_REPLACEMENT_FAILED, new FailureDescriptor(entity, msg));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
index cc86be0..06e6c77 100644
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
+++ b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
@@ -31,6 +31,7 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.trait.Startable;
 import brooklyn.event.Sensor;
 import brooklyn.event.SensorEvent;
@@ -141,7 +142,7 @@ public class ServiceRestarter extends AbstractPolicy {
             return;
         }
         try {
-            entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+            ServiceStateLogic.setExpectedState(entity, Lifecycle.STARTING);
             Entities.invokeEffector(entity, entity, Startable.RESTART).get();
         } catch (Exception e) {
             onRestartFailed("Restart failure (error "+e+") at "+entity+": "+event.getValue());
@@ -151,7 +152,7 @@ public class ServiceRestarter extends AbstractPolicy {
     protected void onRestartFailed(String msg) {
         LOG.warn("ServiceRestarter failed for "+entity+": "+msg);
         if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
-            entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
         }
         entity.emit(ENTITY_RESTART_FAILED, new FailureDescriptor(entity, msg));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
index 8d1a683..900405b 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
@@ -177,7 +177,7 @@ public class ServiceReplacerTest {
         // Configured to not mark cluster as on fire
         Asserts.succeedsContinually(new Runnable() {
             @Override public void run() {
-                assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE), Lifecycle.ON_FIRE);
+                assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
             }});
         
         // And will have received notification event about it
@@ -217,7 +217,7 @@ public class ServiceReplacerTest {
         // Failure to stop the failed member should not cause "on-fire" of cluster
         Asserts.succeedsContinually(new Runnable() {
             @Override public void run() {
-                assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE), Lifecycle.ON_FIRE);
+                assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
             }});
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
index 17df8c6..0d27b0c 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
@@ -126,7 +126,7 @@ public class ServiceRestarterTest {
                 assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), e2, "events="+events);
             }});
         
-        assertEquals(e2.getAttribute(Attributes.SERVICE_STATE), Lifecycle.ON_FIRE);
+        assertEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
     }
     
     @Test
@@ -144,7 +144,7 @@ public class ServiceRestarterTest {
         
         Asserts.succeedsContinually(new Runnable() {
             @Override public void run() {
-                assertNotEquals(e2.getAttribute(Attributes.SERVICE_STATE), Lifecycle.ON_FIRE);
+                assertNotEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
             }});
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/base/src/main/java/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
index 16a9bed..f257989 100644
--- a/software/base/src/main/java/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
@@ -288,7 +288,7 @@ public class ChefLifecycleEffectorTasks extends MachineLifecycleEffectorTasks im
         if (getServiceName()==null) return false;
         int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+getServiceName()+" stop").runAsRoot()).get();
         if (0==result) return true;
-        if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING)
+        if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING)
             return true;
         
         throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)");
@@ -298,7 +298,7 @@ public class ChefLifecycleEffectorTasks extends MachineLifecycleEffectorTasks im
         if (getWindowsServiceName()==null) return false;
                 int result = DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+getWindowsServiceName()+"\"").runAsCommand()).get();
         if (0==result) return true;
-        if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING)
+        if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING)
             return true;
 
         throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)");
@@ -307,11 +307,11 @@ public class ChefLifecycleEffectorTasks extends MachineLifecycleEffectorTasks im
     protected boolean tryStopPid() {
         Integer pid = entity().getAttribute(Attributes.PID);
         if (pid==null) {
-            if (entity().getAttribute(Attributes.SERVICE_STATE)==Lifecycle.RUNNING && getPidFile()==null)
+            if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)==Lifecycle.RUNNING && getPidFile()==null)
                 log.warn("No PID recorded for "+entity()+" when running, with PID file "+getPidFile()+"; skipping kill in "+Tasks.current());
             else 
                 if (log.isDebugEnabled())
-                    log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE)+" / "+getPidFile()+")");
+                    log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)+" / "+getPidFile()+")");
             return false;
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/base/src/main/java/brooklyn/entity/pool/ServerPoolImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/pool/ServerPoolImpl.java b/software/base/src/main/java/brooklyn/entity/pool/ServerPoolImpl.java
index 7e4f907..ddc9add 100644
--- a/software/base/src/main/java/brooklyn/entity/pool/ServerPoolImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/pool/ServerPoolImpl.java
@@ -282,7 +282,7 @@ public class ServerPoolImpl extends DynamicClusterImpl implements ServerPool {
      */
     @Override
     protected Collection<Entity> shrink(int delta) {
-        if (Lifecycle.STOPPING.equals(getAttribute(Attributes.SERVICE_STATE))) {
+        if (Lifecycle.STOPPING.equals(getAttribute(Attributes.SERVICE_STATE_ACTUAL))) {
             return super.shrink(delta);
         }
 
@@ -327,7 +327,7 @@ public class ServerPoolImpl extends DynamicClusterImpl implements ServerPool {
         public Entity apply(Collection<Entity> members) {
             synchronized (mutex) {
                 Optional<Entity> choice;
-                if (Lifecycle.STOPPING.equals(getAttribute(Attributes.SERVICE_STATE))) {
+                if (Lifecycle.STOPPING.equals(getAttribute(Attributes.SERVICE_STATE_ACTUAL))) {
                     choice = Optional.of(members.iterator().next());
                 } else {
                     // Otherwise should only choose between removable + unusable or available

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
index b708b61..746a2be 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -90,7 +90,7 @@ import com.google.common.collect.Iterables;
  *  <li> {@link #postStartCustom()}
  *  <li> {@link #preStopCustom()}
  * </ul>
- * Note methods at this level typically look after the {@link Attributes#SERVICE_STATE} sensor.
+ * Note methods at this level typically look after the {@link Attributes#SERVICE_STATE_EXPECTED} sensor.
  *  
  * @since 0.6.0
  **/

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/base/src/test/java/brooklyn/entity/software/mysql/AbstractToyMySqlEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/software/mysql/AbstractToyMySqlEntityTest.java b/software/base/src/test/java/brooklyn/entity/software/mysql/AbstractToyMySqlEntityTest.java
index 231ea8b..bbc1a69 100644
--- a/software/base/src/test/java/brooklyn/entity/software/mysql/AbstractToyMySqlEntityTest.java
+++ b/software/base/src/test/java/brooklyn/entity/software/mysql/AbstractToyMySqlEntityTest.java
@@ -81,11 +81,11 @@ public abstract class AbstractToyMySqlEntityTest extends BrooklynAppLiveTestSupp
     protected void checkStartsRunning(Entity mysql) {
         // should be starting within a few seconds (and almost certainly won't complete in that time) 
         Asserts.eventually(MutableMap.of("timeout", Duration.FIVE_SECONDS),
-                Entities.attributeSupplier(mysql, Attributes.SERVICE_STATE),
+                Entities.attributeSupplier(mysql, Attributes.SERVICE_STATE_ACTUAL),
                 Predicates.or(Predicates.equalTo(Lifecycle.STARTING), Predicates.equalTo(Lifecycle.RUNNING)));
         // should be up and running within 5m 
         Asserts.eventually(MutableMap.of("timeout", Duration.FIVE_MINUTES),
-                Entities.attributeSupplier(mysql, Attributes.SERVICE_STATE),
+                Entities.attributeSupplier(mysql, Attributes.SERVICE_STATE_ACTUAL),
                 Predicates.equalTo(Lifecycle.RUNNING));
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
index b04e362..603fb6d 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
@@ -146,7 +146,6 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
     
     protected SeedTracker seedTracker = new SeedTracker();
     protected TokenGenerator tokenGenerator = null;
-    private MemberTrackingPolicy policy;
 
     public CassandraDatacenterImpl() {
     }
@@ -176,6 +175,15 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
                 seedTracker.onServiceUpChanged(event.getSource(), event.getValue());
             }
         });
+        subscribeToMembers(this, Attributes.SERVICE_STATE_ACTUAL, new SensorEventListener<Lifecycle>() {
+            @Override
+            public void onEvent(SensorEvent<Lifecycle> event) {
+                // trigger a recomputation also when lifecycle state changes, 
+                // because it might not have ruled a seed as inviable when service up went true 
+                // because service state was not yet running
+                seedTracker.onServiceUpChanged(event.getSource(), Lifecycle.RUNNING==event.getValue());
+            }
+        });
         
         // Track the datacenters for this cluster
         subscribeToMembers(this, CassandraNode.DATACENTER_NAME, new SensorEventListener<String>() {
@@ -296,6 +304,7 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
         return super.grow(delta);
     }
     
+    @SuppressWarnings("deprecation")
     @Override
     protected Entity createNode(@Nullable Location loc, Map<?,?> flags) {
         Map<Object, Object> allflags = MutableMap.copyOf(flags);
@@ -357,7 +366,7 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
     protected void connectSensors() {
         connectEnrichers();
         
-        policy = addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
+        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
                 .displayName("Cassandra Cluster Tracker")
                 .configure("sensorsToTrack", ImmutableSet.of(Attributes.SERVICE_UP, Attributes.HOSTNAME, CassandraNode.THRIFT_PORT))
                 .configure("group", this));
@@ -587,7 +596,7 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
             boolean managed = Entities.isManaged(member);
             String hostname = member.getAttribute(Attributes.HOSTNAME);
             boolean serviceUp = Boolean.TRUE.equals(member.getAttribute(Attributes.SERVICE_UP));
-            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE);
+            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
             boolean hasFailed = !managed || (serviceState == Lifecycle.ON_FIRE) || (serviceState == Lifecycle.RUNNING && !serviceUp) || (serviceState == Lifecycle.STOPPED);
             boolean result = (hostname != null && !hasFailed);
             if (log.isTraceEnabled()) log.trace("Node {} in Cluster {}: viableSeed={}; hostname={}; serviceUp={}; serviceState={}; hasFailed={}", new Object[] {member, this, result, hostname, serviceUp, serviceState, hasFailed});
@@ -596,7 +605,7 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
         public boolean isRunningSeed(Entity member) {
             boolean viableSeed = isViableSeed(member);
             boolean serviceUp = Boolean.TRUE.equals(member.getAttribute(Attributes.SERVICE_UP));
-            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE);
+            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
             boolean result = viableSeed && serviceUp && serviceState == Lifecycle.RUNNING;
             if (log.isTraceEnabled()) log.trace("Node {} in Cluster {}: runningSeed={}; viableSeed={}; serviceUp={}; serviceState={}", new Object[] {member, this, result, viableSeed, serviceUp, serviceState});
             return result;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
index fe0b503..2f874e6 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
@@ -190,7 +190,7 @@ public class CassandraFabricImpl extends DynamicFabricImpl implements CassandraF
             boolean managed = Entities.isManaged(member);
             String hostname = member.getAttribute(Attributes.HOSTNAME);
             boolean serviceUp = Boolean.TRUE.equals(member.getAttribute(Attributes.SERVICE_UP));
-            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE);
+            Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
             boolean hasFailed = !managed || (serviceState == Lifecycle.ON_FIRE) || (serviceState == Lifecycle.RUNNING && !serviceUp) || (serviceState == Lifecycle.STOPPED);
             boolean result = (hostname != null && !hasFailed);
             if (log.isTraceEnabled()) log.trace("Node {} in Fabric {}: viableSeed={}; hostname={}; serviceUp={}; serviceState={}; hasFailed={}", new Object[] {member, CassandraFabricImpl.this, result, hostname, serviceUp, serviceState, hasFailed});

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentImpl.java
index 944e99e..9c9cd62 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentImpl.java
@@ -33,6 +33,8 @@ import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.EntitySpec;
@@ -51,6 +53,8 @@ public class MongoDBShardedDeploymentImpl extends AbstractEntity implements Mong
     
     @Override
     public void init() {
+        super.init();
+        
         setAttribute(CONFIG_SERVER_CLUSTER, addChild(EntitySpec.create(MongoDBConfigServerCluster.class)
                 .configure(DynamicCluster.INITIAL_SIZE, getConfig(CONFIG_CLUSTER_SIZE))));
         setAttribute(ROUTER_CLUSTER, addChild(EntitySpec.create(MongoDBRouterCluster.class)
@@ -62,11 +66,13 @@ public class MongoDBShardedDeploymentImpl extends AbstractEntity implements Mong
                 .propagating(MongoDBConfigServerCluster.CONFIG_SERVER_ADDRESSES)
                 .from(getAttribute(CONFIG_SERVER_CLUSTER))
                 .build());
+        
+        ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "stopped");
     }
 
     @Override
     public void start(Collection<? extends Location> locations) {
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             final MongoDBRouterCluster routers = getAttribute(ROUTER_CLUSTER);
             final MongoDBShardCluster shards = getAttribute(SHARD_CLUSTER);
@@ -79,10 +85,10 @@ public class MongoDBShardedDeploymentImpl extends AbstractEntity implements Mong
                         .displayName("Co-located router tracker")
                         .configure("group", (Group)getConfig(MongoDBShardedDeployment.CO_LOCATED_ROUTER_GROUP)));
             }
-            setAttribute(SERVICE_UP, true);
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
+            ServiceNotUpLogic.clearNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Exception e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             // no need to log here; the effector invocation should do that
             throw Exceptions.propagate(e);
         }
@@ -103,16 +109,16 @@ public class MongoDBShardedDeploymentImpl extends AbstractEntity implements Mong
 
     @Override
     public void stop() {
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         try {
             Entities.invokeEffectorList(this, ImmutableList.of(getAttribute(CONFIG_SERVER_CLUSTER), getAttribute(ROUTER_CLUSTER), 
                     getAttribute(SHARD_CLUSTER)), Startable.STOP).get();
         } catch (Exception e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         }
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPED);
-        setAttribute(SERVICE_UP, false);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
+        ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "stopped");
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/nosql/src/test/java/brooklyn/entity/nosql/cassandra/CassandraFabricTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/brooklyn/entity/nosql/cassandra/CassandraFabricTest.java b/software/nosql/src/test/java/brooklyn/entity/nosql/cassandra/CassandraFabricTest.java
index 4437faf..f4a786a 100644
--- a/software/nosql/src/test/java/brooklyn/entity/nosql/cassandra/CassandraFabricTest.java
+++ b/software/nosql/src/test/java/brooklyn/entity/nosql/cassandra/CassandraFabricTest.java
@@ -36,6 +36,7 @@ import brooklyn.entity.basic.EmptySoftwareProcess;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.entity.trait.Startable;
@@ -168,12 +169,12 @@ public class CassandraFabricTest extends BrooklynAppUnitTestSupport {
 
         @Override
         public void start(Collection<? extends Location> locations) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         }
 
         @Override
         public void stop() {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPING);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsService.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsService.java b/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsService.java
index 2b70f7c..0f053bc 100644
--- a/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsService.java
+++ b/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsService.java
@@ -38,7 +38,7 @@ public interface AbstractGeoDnsService extends Entity {
     public static final ConfigKey<Boolean> INCLUDE_HOMELESS_ENTITIES = ConfigKeys.newBooleanConfigKey("geodns.includeHomeless", "Whether to include entities whose geo-coordinates cannot be inferred", false);
     public static final ConfigKey<Boolean> USE_HOSTNAMES = ConfigKeys.newBooleanConfigKey("geodns.useHostnames", "Whether to use the hostname for the returned value for routing, rather than IP address (defaults to true)", true);
     
-    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
     public static final AttributeSensor<Boolean> SERVICE_UP = Startable.SERVICE_UP;
     public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
     public static final AttributeSensor<String> ADDRESS = Attributes.ADDRESS;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
index 3d632aa..f931bb1 100644
--- a/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
@@ -41,6 +41,8 @@ import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.DynamicGroup;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.webapp.WebAppService;
 import brooklyn.location.geo.HostGeoInfo;
@@ -104,8 +106,11 @@ public abstract class AbstractGeoDnsServiceImpl extends AbstractEntity implement
     @Override
     public void setServiceState(Lifecycle state) {
         setAttribute(HOSTNAME, getHostname());
-        setAttribute(SERVICE_STATE, state);
-        setAttribute(SERVICE_UP, state==Lifecycle.RUNNING);
+        ServiceStateLogic.setExpectedState(this, state);
+        if (state==Lifecycle.RUNNING)
+            ServiceNotUpLogic.clearNotUpIndicator(this, SERVICE_STATE_ACTUAL);
+        else
+            ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_STATE_ACTUAL, "Not in RUNNING state");
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
index 024d01b..7e470ca 100644
--- a/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
@@ -28,6 +28,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.dns.AbstractGeoDnsServiceImpl;
 import brooklyn.entity.dns.geoscaling.GeoscalingWebClient.Domain;
 import brooklyn.entity.dns.geoscaling.GeoscalingWebClient.SmartSubdomain;
@@ -69,9 +70,9 @@ public class GeoscalingDnsServiceImpl extends AbstractGeoDnsServiceImpl implemen
         try {
             applyConfig();
         } catch (Exception e) {
-            // don't prevent management coming up
+            // don't prevent management coming up, but do mark it as on fire
             log.error("Geoscaling did not come up correctly: "+e, e);
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
         }
         super.onManagementBecomingMaster();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
index df0fd6a..bda6bd9 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
@@ -52,6 +52,7 @@ import brooklyn.management.Task;
 import brooklyn.policy.Policy;
 import brooklyn.policy.autoscaling.AutoScalerPolicy;
 import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
@@ -167,7 +168,7 @@ public class JavaWebAppsIntegrationTest {
             log.info("App started:");
             Entities.dumpInfo(app);
 
-            Assert.assertEquals(app.getAttribute(Attributes.SERVICE_STATE), Lifecycle.RUNNING);
+            EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
             Assert.assertEquals(app.getAttribute(Attributes.SERVICE_UP), Boolean.TRUE);
             
             final String url = Asserts.succeedsEventually(MutableMap.of("timeout", Duration.TEN_SECONDS), new Callable<String>() {
@@ -240,7 +241,7 @@ public class JavaWebAppsIntegrationTest {
             Assert.assertEquals(policy.getConfig(AutoScalerPolicy.METRIC_UPPER_BOUND), (Integer)100);
             Assert.assertTrue(policy.isRunning());
 
-            Assert.assertEquals(app.getAttribute(Attributes.SERVICE_STATE), Lifecycle.RUNNING);
+            EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
             Assert.assertEquals(app.getAttribute(Attributes.SERVICE_UP), Boolean.TRUE);
             
             final String url = Asserts.succeedsEventually(MutableMap.of("timeout", Duration.TEN_SECONDS), new Callable<String>() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/VanillaBashNetcatYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/VanillaBashNetcatYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/VanillaBashNetcatYamlTest.java
index 950c465..336b5fc 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/VanillaBashNetcatYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/VanillaBashNetcatYamlTest.java
@@ -68,7 +68,7 @@ public class VanillaBashNetcatYamlTest extends AbstractYamlTest {
         Entity netcat = Iterables.getOnlyElement(netcatI);
         
         // make sure netcat is running
-        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE, Predicates.equalTo(Lifecycle.RUNNING));
+        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.RUNNING));
         
         // find the pinger, now comparing by name
         Iterable<Entity> pingerI = Iterables.filter(app.getChildren(), EntityPredicates.displayNameEqualTo("Simple Pinger"));
@@ -85,10 +85,10 @@ public class VanillaBashNetcatYamlTest extends AbstractYamlTest {
             netcat.getAttribute(SENSOR_OUTPUT_ALL));
 
         // netcat should now fail and restart
-        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE, Predicates.not(Predicates.equalTo(Lifecycle.RUNNING)));
-        log.info("detected failure, state is: "+netcat.getAttribute(Attributes.SERVICE_STATE));
-        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE, Predicates.equalTo(Lifecycle.RUNNING));
-        log.info("detected recovery, state is: "+netcat.getAttribute(Attributes.SERVICE_STATE));
+        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE_ACTUAL, Predicates.not(Predicates.equalTo(Lifecycle.RUNNING)));
+        log.info("detected failure, state is: "+netcat.getAttribute(Attributes.SERVICE_STATE_ACTUAL));
+        EntityTestUtils.assertAttributeEventually(netcat, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.RUNNING));
+        log.info("detected recovery, state is: "+netcat.getAttribute(Attributes.SERVICE_STATE_ACTUAL));
 
         // invoke effector again, now with a parameter
         ping = pinger.invoke(EFFECTOR_SAY_HI, MutableMap.<String,Object>of("message", "yo yo yo"));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/usage/launcher/src/test/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorIntegrationTest.java b/usage/launcher/src/test/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorIntegrationTest.java
index bb54d06..93b69f2 100644
--- a/usage/launcher/src/test/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorIntegrationTest.java
+++ b/usage/launcher/src/test/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorIntegrationTest.java
@@ -121,7 +121,7 @@ public class BrooklynEntityMirrorIntegrationTest {
         EntityTestUtils.assertAttributeEqualsEventually(mirror, TestApplication.MY_ATTRIBUTE, "bermuda");
 
         serverApp.stop();
-        EntityTestUtils.assertAttributeEqualsEventually(mirror, Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(mirror, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
 
     
@@ -156,7 +156,7 @@ public class BrooklynEntityMirrorIntegrationTest {
         EntityTestUtils.assertAttributeEqualsEventually(mirror, TestApplication.MY_ATTRIBUTE, "bermuda");
 
         serverApp.stop();
-        EntityTestUtils.assertAttributeEqualsEventually(mirror, Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(mirror, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/099a5496/usage/rest-server/src/main/java/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ApplicationResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ApplicationResource.java
index e4314fc..d6554e4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ApplicationResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ApplicationResource.java
@@ -111,7 +111,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
         Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP);
         if (serviceUp!=null) aRoot.put("serviceUp", serviceUp);
 
-        Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE);
+        Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
         if (serviceState!=null) aRoot.put("serviceState", serviceState.toString());
 
         String iconUrl = entity.getIconUrl();


[25/26] git commit: address svet code review comments

Posted by he...@apache.org.
address svet code review comments


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/225f1820
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/225f1820
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/225f1820

Branch: refs/heads/master
Commit: 225f182078b700ab6282a3aac000d3d304870bdd
Parents: 6a8af41
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Aug 29 18:35:08 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Aug 29 18:35:08 2014 -0400

----------------------------------------------------------------------
 .../brooklyn/enricher/basic/AbstractAggregator.java     |  2 +-
 .../basic/AbstractMultipleSensorAggregator.java         | 11 ++++++++---
 .../main/java/brooklyn/enricher/basic/Aggregator.java   | 12 ++++++++----
 .../main/java/brooklyn/entity/basic/AbstractEntity.java |  2 +-
 core/src/main/java/brooklyn/entity/basic/Lifecycle.java |  7 -------
 .../java/brooklyn/entity/basic/ServiceStateLogic.java   |  2 +-
 .../policy/loadbalancing/LoadBalancingPolicyTest.java   |  7 +++----
 .../java/brooklyn/util/collections/MutableList.java     |  8 ++++++++
 .../main/java/brooklyn/util/collections/MutableMap.java |  5 +++++
 .../main/java/brooklyn/util/collections/MutableSet.java |  6 ++++++
 .../src/main/java/brooklyn/util/guava/IfFunctions.java  |  6 +++---
 .../test/java/brooklyn/util/guava/IfFunctionsTest.java  |  2 +-
 12 files changed, 45 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
index ba7762c..9568332 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
@@ -85,7 +85,7 @@ public abstract class AbstractAggregator<T,U> extends AbstractEnricher implement
         if (fromHardcodedProducers == null && producer == null) producer = entity;
         checkState(fromHardcodedProducers != null ^ producer != null, "must specify one of %s (%s) or %s (%s)", 
                 PRODUCER.getName(), producer, FROM_HARDCODED_PRODUCERS.getName(), fromHardcodedProducers);
-        checkState(producer != null ? (Boolean.TRUE.equals(fromMembers) || Boolean.TRUE.equals(fromChildren)) : true, 
+        checkState(producer == null || Boolean.TRUE.equals(fromMembers) || Boolean.TRUE.equals(fromChildren), 
                 "when specifying producer, must specify at least one of fromMembers (%s) or fromChildren (%s)", fromMembers, fromChildren);
 
         if (fromHardcodedProducers != null) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
index 85c36d8..07b0477 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
@@ -55,6 +55,7 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
         Preconditions.checkNotNull(getSourceSensors(), "sourceSensors must be set");
     }
     
+    @Override
     protected void setEntityBeforeSubscribingProducerChildrenEvents() {
         if (LOG.isDebugEnabled()) LOG.debug("{} subscribing to children of {}", new Object[] {this, producer });
         for (Sensor<?> sourceSensor: getSourceSensors()) {
@@ -62,6 +63,7 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
         }
     }
 
+    @Override
     protected void addProducerHardcoded(Entity producer) {
         for (Sensor<?> sourceSensor: getSourceSensors()) {
             subscribe(producer, sourceSensor, this);
@@ -69,17 +71,18 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
         onProducerAdded(producer);
     }
 
+    @Override
     protected void addProducerChild(Entity producer) {
-        // not required due to subscribeToChildren call
-//        subscribe(producer, sourceSensor, this);
+        // no `subscribe` call needed here, due to previous subscribeToChildren call
         onProducerAdded(producer);
     }
 
+    @Override
     protected void addProducerMember(Entity producer) {
         addProducerHardcoded(producer);
     }
 
-    
+    @Override
     protected void onProducerAdded(Entity producer) {
         if (LOG.isDebugEnabled()) LOG.debug("{} listening to {}", new Object[] {this, producer});
         synchronized (values) {
@@ -106,6 +109,7 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
         }
     }
     
+    @Override
     protected void onProducerRemoved(Entity producer) {
         synchronized (values) {
             for (Sensor<?> sensor: getSourceSensors()) {
@@ -140,5 +144,6 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
         }
     }
     
+    @Override
     protected abstract Object compute();
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/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 034a604..e865799 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -70,29 +70,31 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
         this.transformation = (Function<? super Collection<T>, ? extends U>) getRequiredConfig(TRANSFORMATION);
     }
         
-
+    @Override
     protected void setEntityBeforeSubscribingProducerChildrenEvents() {
         if (LOG.isDebugEnabled()) LOG.debug("{} subscribing to children of {}", new Object[] {this, producer });
         subscribeToChildren(producer, sourceSensor, this);
     }
 
+    @Override
     protected void addProducerHardcoded(Entity producer) {
         subscribe(producer, sourceSensor, this);
         onProducerAdded(producer);
     }
 
+    @Override
     protected void addProducerChild(Entity producer) {
-        // not required due to subscribeToChildren call
-//        subscribe(producer, sourceSensor, this);
+        // no subscription needed here, due to the subscribeToChildren call
         onProducerAdded(producer);
     }
 
+    @Override
     protected void addProducerMember(Entity producer) {
         subscribe(producer, sourceSensor, this);
         onProducerAdded(producer);
     }
 
-    
+    @Override
     protected void onProducerAdded(Entity producer) {
         if (LOG.isDebugEnabled()) LOG.debug("{} listening to {}", new Object[] {this, producer});
         synchronized (values) {
@@ -115,6 +117,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
         }
     }
     
+    @Override
     protected void onProducerRemoved(Entity producer) {
         values.remove(producer);
         onUpdated();
@@ -142,6 +145,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
         }
     }
     
+    @Override
     protected Object compute() {
         synchronized (values) {
             // TODO Could avoid copying when filter not needed

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 5e21fd7..ec2d5e9 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -1058,7 +1058,7 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     // these enrichers do nothing unless Attributes.SERVICE_NOT_UP_INDICATORS are used
     // and/or SERVICE_STATE_EXPECTED 
     protected void initEnrichers() {
-        addEnricher(ServiceNotUpLogic.newEnricherForServiceUpIfNoNotUpIndicators());
+        addEnricher(ServiceNotUpLogic.newEnricherForServiceUpIfNotUpIndicatorsEmpty());
         addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
index 5e7d0fe..26d3916 100644
--- a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
+++ b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
@@ -46,15 +46,8 @@ public enum Lifecycle {
      * When this completes the entity will normally transition to 
      * {@link Lifecycle#RUNNING}. 
      */
-//    * {@link Lifecycle#STARTED} or 
     STARTING,
 
-//    /**
-//     * The entity has been started and no further start-up steps are needed from the management plane,
-//     * but the entity has not yet been confirmed as running.
-//     */
-//    STARTED,
-//
     /**
      * The entity service is expected to be running. In healthy operation, {@link Attributes#SERVICE_UP} will be true,
      * or will shortly be true if all service start actions have been completed and we are merely waiting for it to be running. 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index 5154bff..95d5e57 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -136,7 +136,7 @@ public class ServiceStateLogic {
         private ServiceNotUpLogic() {}
         
         @SuppressWarnings({ "unchecked", "rawtypes" })
-        public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
+        public static final EnricherSpec<?> newEnricherForServiceUpIfNotUpIndicatorsEmpty() {
             return Enrichers.builder()
                 .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
                 .computing( /* cast hacks to support removing */ (Function)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
index c0ff7b7..e0a720b 100644
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
+++ b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
@@ -27,8 +27,6 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.test.Asserts;
 import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -235,7 +233,7 @@ public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
                 ImmutableList.of(item1, item2, item3, item4, item5, item6), 
                 ImmutableList.of(30d, 30d));
         
-        MockItemEntity item7 = newItem(app, containerA, "7", 40);
+        newItem(app, containerA, "7", 40);
         
         assertWorkratesEventually(
                 ImmutableList.of(containerA, containerB), 
@@ -305,6 +303,7 @@ public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
                 ImmutableList.of(40d, 40d));
     }
     
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Test
     public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() {
         pool.removePolicy(policy);
@@ -312,7 +311,7 @@ public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
         
         // Set-up containers and items.
         final MockContainerEntity containerA = newContainer(app, "A", 10, 100);
-        MockItemEntity item1 = newItem(app, containerA, "1", 10);
+        newItem(app, containerA, "1", 10);
 
         policy = new LoadBalancingPolicy(MutableMap.of(), TEST_METRIC, model);
         pool.addPolicy(policy);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
index a6638da..1c1b2d2 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
@@ -29,6 +29,8 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.exceptions.Exceptions;
+
 import com.google.common.collect.ImmutableList;
 
 public class MutableList<V> extends ArrayList<V> {
@@ -84,17 +86,23 @@ public class MutableList<V> extends ArrayList<V> {
     public ImmutableList<V> toImmutable() {
         return ImmutableList.copyOf(this);
     }
+    /** creates an {@link ImmutableList} which is a copy of this list.  note that the list should not contain nulls.  */
     public List<V> asImmutableCopy() {
         try {
             return ImmutableList.copyOf(this);
         } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
             log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
             return asUnmodifiableCopy();
         }
     }
+    /** creates a {@link Collections#unmodifiableList(List)} wrapper around this list. the method is efficient,
+     * as there is no copying, but the returned view might change if the list here is changed.  */
     public List<V> asUnmodifiable() {
         return Collections.unmodifiableList(this);
     }
+    /** creates a {@link Collections#unmodifiableList(List)} of a copy of this list.
+     * the returned item is immutable, but unlike {@link #asImmutableCopy()} nulls are permitted. */
     public List<V> asUnmodifiableCopy() {
         return Collections.unmodifiableList(MutableList.copyOf(this));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java b/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
index 2630dbf..f8b2f6c 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableMap.java
@@ -29,6 +29,7 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
 
 import com.google.common.base.Predicate;
@@ -146,17 +147,21 @@ public class MutableMap<K,V> extends LinkedHashMap<K,V> {
     public ImmutableMap<K,V> toImmutable() {
         return ImmutableMap.copyOf(this);
     }
+    /** as {@link MutableList#asImmutableCopy()} */
     public Map<K,V> asImmutableCopy() {
         try {
             return ImmutableMap.copyOf(this);
         } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
             log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
             return asUnmodifiableCopy();
         }
     }
+    /** as {@link MutableList#asUnmodifiable()} */
     public Map<K,V> asUnmodifiable() {
         return Collections.unmodifiableMap(this);
     }
+    /** as {@link MutableList#asUnmodifiableCopy()} */
     public Map<K,V> asUnmodifiableCopy() {
         return Collections.unmodifiableMap(MutableMap.copyOf(this));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
index 0653cfd..e795158 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
@@ -29,6 +29,8 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.exceptions.Exceptions;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
@@ -79,17 +81,21 @@ public class MutableSet<V> extends LinkedHashSet<V> {
         // Don't use ImmutableSet as that does not accept nulls
         return Collections.unmodifiableSet(Sets.newLinkedHashSet(this));
     }
+    /** as {@link MutableList#asImmutableCopy()()} */
     public Set<V> asImmutableCopy() {
         try {
             return ImmutableSet.copyOf(this);
         } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
             log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
             return asUnmodifiableCopy();
         }
     }
+    /** as {@link MutableList#asUnmodifiable()} */
     public Set<V> asUnmodifiable() {
         return Collections.unmodifiableSet(this);
     }
+    /** as {@link MutableList#asUnmodifiableCopy()} */
     public Set<V> asUnmodifiableCopy() {
         return Collections.unmodifiableSet(MutableSet.copyOf(this));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java b/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
index 5384436..ceea8c3 100644
--- a/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
+++ b/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
@@ -38,13 +38,13 @@ public class IfFunctions {
         return new IfFunctionBuilder<I,O>();
     }
     
-    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifPredicate(Predicate<? super I> test) {
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifPredicate(Predicate<? super I> test) {
         return new IfFunctionBuilderApplyingFirst<I>(test);
     }
-    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifEquals(I test) {
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifEquals(I test) {
         return ifPredicate(Predicates.equalTo(test));
     }
-    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifNotEquals(I test) {
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifNotEquals(I test) {
         return ifPredicate(Predicates.not(Predicates.equalTo(test)));
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/225f1820/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java b/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
index 74a5d5b..e52dc34 100644
--- a/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
+++ b/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
@@ -67,7 +67,7 @@ public class IfFunctionsTest {
 
     @Test
     public void testWithCast() {
-        Function<Boolean, String> f = IfFunctions.<Boolean,String>ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build();
+        Function<Boolean, String> f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build();
         checkTF(f, "?");
     }
 


[20/26] git commit: fix all tests and related entities to get service lifecycle, including REST app creation attaching all interfaces (previously it did not get "startable" so entities did not start)

Posted by he...@apache.org.
fix all tests and related entities to get service lifecycle, including REST app creation attaching all interfaces (previously it did not get "startable" so entities did not start)


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5db500e4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5db500e4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5db500e4

Branch: refs/heads/master
Commit: 5db500e43fbc1973d5b941ff41f681e51c134217
Parents: bfb8737
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 26 08:46:02 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:21:22 2014 -0400

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  |  4 +--
 .../entity/basic/AbstractApplication.java       |  3 +-
 .../entity/basic/ServiceStateLogic.java         | 23 +++++++------
 .../java/brooklyn/enricher/EnrichersTest.java   | 34 +++++++++++++++++++-
 .../enricher/basic/BasicEnricherTest.java       | 13 +++++++-
 .../entity/BrooklynAppUnitTestSupport.java      |  4 +++
 .../entity/brooklyn/BrooklynMetricsTest.java    | 13 +++++---
 .../autoscaling/AutoScalerPolicyTest.java       |  2 +-
 .../policy/ha/ServiceFailureDetectorTest.java   | 10 ++++--
 .../brooklyn/entity/basic/SameServerEntity.java |  4 +++
 .../entity/basic/SameServerEntityImpl.java      |  6 ++++
 .../ControlledDynamicWebAppClusterTest.java     | 17 ++++++----
 .../entity/webapp/DynamicWebAppClusterTest.java | 12 ++++---
 .../test/entity/TestJavaWebAppEntity.java       |  6 ++++
 .../camp/brooklyn/EnrichersYamlTest.java        | 23 +++++++------
 .../camp/brooklyn/EntitiesYamlTest.java         |  2 +-
 .../rest/util/BrooklynRestResourceUtils.java    |  2 +-
 .../rest/resources/ApplicationResourceTest.java |  9 ++++--
 .../rest/testing/BrooklynRestResourceTest.java  | 26 ++++++++++++---
 .../rest/testing/mocks/RestMockAppBuilder.java  |  5 ++-
 .../testing/mocks/RestMockSimpleEntity.java     | 11 ++++---
 .../java/brooklyn/test/EntityTestUtils.java     |  1 +
 22 files changed, 171 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/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 73b7a3b..48b7a56 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -95,11 +95,11 @@ public class Enrichers {
 
         protected abstract String getDefaultUniqueTag();
         
-        protected EnricherSpec<?> build() {
+        protected EnricherSpec<? extends Enricher> build() {
             EnricherSpec<? extends Enricher> spec = EnricherSpec.create(enricherType);
             
             String uniqueTag2 = uniqueTag;
-            if (uniqueTag!=null)
+            if (uniqueTag2==null)
                 uniqueTag2 = getDefaultUniqueTag();
             if (uniqueTag2!=null)
                 spec.uniqueTag(uniqueTag2);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
index 35e0083..961906f 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
@@ -27,7 +27,6 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
 import brooklyn.entity.trait.StartableMethods;
 import brooklyn.location.Location;
@@ -58,8 +57,8 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
     }
 
     public void init() { 
-        initApp();
         super.init();
+        initApp();
     }
     
     protected void initApp() {}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index e3d5090..749c83d 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -121,6 +121,8 @@ public class ServiceStateLogic {
     }
     
     public static class ServiceNotUpLogic {
+        public static final String DEFAULT_ENRICHER_UNIQUE_TAG = "service.isUp if no service.notUp.indicators";
+        
         /** static only; not for instantiation */
         private ServiceNotUpLogic() {}
         
@@ -132,7 +134,7 @@ public class ServiceStateLogic {
                     Functionals.<Map<String,?>>
                         ifNotEquals(null).<Object>apply(Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)))
                         .defaultValue(Entities.REMOVE) )
-                .uniqueTag("service.isUp if no service.notUp.indicators")
+                .uniqueTag(DEFAULT_ENRICHER_UNIQUE_TAG)
                 .build();
         }
         
@@ -170,13 +172,16 @@ public class ServiceStateLogic {
      * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity.
      */
     public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener<Object> {
+        
+        public static final String DEFAULT_ENRICHER_UNIQUE_TAG = "service.state.actual";
+
         public ComputeServiceState() {}
         public ComputeServiceState(Map<?,?> flags) { super(flags); }
             
         @Override
         public void init() {
             super.init();
-            if (uniqueTag==null) uniqueTag = "service.state.actual";
+            if (uniqueTag==null) uniqueTag = DEFAULT_ENRICHER_UNIQUE_TAG;
         }
         
         public void setEntity(EntityLocal entity) {
@@ -277,10 +282,10 @@ public class ServiceStateLogic {
     
     public static class ComputeServiceIndicatorsFromChildrenAndMembers extends AbstractMultipleSensorAggregator<Void> implements SensorEventListener<Object> {
         /** standard unique tag identifying instances of this enricher at runtime, also used for the map sensor if no unique tag specified */
-        public final static String IDENTIFIER_DEFAULT = "service-lifecycle-indicators-from-children-and-members";
+        public final static String DEFAULT_UNIQUE_TAG = "service-lifecycle-indicators-from-children-and-members";
         
-        /** as {@link #IDENTIFIER_LIFECYCLE}, but when a second distinct instance is responsible for computing service up */
-        public final static String IDENTIFIER_UP = "service-not-up-indicators-from-children-and-members";
+        /** as {@link #DEFAULT_UNIQUE_TAG}, but when a second distinct instance is responsible for computing service up */
+        public final static String DEFAULT_UNIQUE_TAG_UP = "service-not-up-indicators-from-children-and-members";
 
         public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKey(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.up", 
             "Logic for checking whether this service is up, based on children and/or members, defaulting to allowing none but if there are any requiring at least one to be up", QuorumCheck.QuorumChecks.atLeastOneUnlessEmpty());
@@ -457,22 +462,22 @@ public class ServiceStateLogic {
     }
 
     /** provides the default {@link ComputeServiceIndicatorsFromChildrenAndMembers} enricher, 
-     * using the default unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#IDENTIFIER_DEFAULT}),
+     * using the default unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#DEFAULT_UNIQUE_TAG}),
      * configured here to require none on fire, and either no children or at least one up child,
      * the spec can be further configured as appropriate */
     public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildren() {
         return new ComputeServiceIndicatorsFromChildrenAndMembersSpec()
-            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.IDENTIFIER_DEFAULT);
+            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG);
     }
 
     /** as {@link #newEnricherFromChildren()} but only publishing service not-up indicators, 
-     * using a different unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#IDENTIFIER_UP}),
+     * using a different unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#DEFAULT_UNIQUE_TAG_UP}),
      * listening to children only, ignoring lifecycle/service-state,
      * and using the same logic 
      * (viz looking only at children (not members) and requiring either no children or at least one child up) by default */
     public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildrenUp() {
         return newEnricherFromChildren()
-            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.IDENTIFIER_UP)
+            .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG_UP)
             .checkChildrenOnly()
             .configure(ComputeServiceIndicatorsFromChildrenAndMembers.DERIVE_SERVICE_PROBLEMS, false);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index 66d1bdd..aeffe6a 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -19,24 +19,33 @@
 package brooklyn.enricher;
 
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BasicGroup;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntitySubscriptionTest.RecordingSensorEventListener;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.basic.Sensors;
+import brooklyn.policy.Enricher;
 import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.guava.Functionals;
@@ -54,6 +63,27 @@ import com.google.common.reflect.TypeToken;
 @SuppressWarnings("serial")
 public class EnrichersTest extends BrooklynAppUnitTestSupport {
 
+    public static List<Enricher> getNonSystemEnrichers(Entity entity) {
+        List<Enricher> result = MutableList.copyOf(entity.getEnrichers());
+        Iterator<Enricher> ri = result.iterator();
+        while (ri.hasNext()) {
+            if (isSystemEnricher(ri.next())) ri.remove();
+        }
+        return result;
+    }
+
+    public static final List<String> SYSTEM_ENRICHER_UNIQUE_TAGS = ImmutableList.of(
+        ServiceNotUpLogic.DEFAULT_ENRICHER_UNIQUE_TAG,
+        ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG,
+        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG,
+        ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG_UP);
+    
+    public static boolean isSystemEnricher(Enricher enr) {
+        if (enr.getUniqueTag()==null) return false;
+        if (SYSTEM_ENRICHER_UNIQUE_TAGS.contains(enr.getUniqueTag())) return true;
+        return false;
+    }
+    
     public static final AttributeSensor<Integer> NUM1 = Sensors.newIntegerSensor("test.num1");
     public static final AttributeSensor<Integer> NUM2 = Sensors.newIntegerSensor("test.num2");
     public static final AttributeSensor<Integer> NUM3 = Sensors.newIntegerSensor("test.num3");
@@ -81,12 +111,14 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
     @SuppressWarnings("unchecked")
     @Test
     public void testAdding() {
-        entity.addEnricher(Enrichers.builder()
+        Enricher enr = entity.addEnricher(Enrichers.builder()
                 .combining(NUM1, NUM2)
                 .publishing(NUM3)
                 .computingSum()
                 .build());
         
+        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(entity), ImmutableList.of(enr));
+        
         entity.setAttribute(NUM1, 2);
         entity.setAttribute(NUM2, 3);
         EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 5);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
index 59bc7e8..8d6ed80 100644
--- a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
+++ b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
@@ -29,9 +29,14 @@ import com.google.common.collect.Iterables;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BrooklynConfigKeys;
+import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherSpec;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestApplicationNoEnrichersImpl;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.flags.SetFromFlag;
 
@@ -42,7 +47,13 @@ public class BasicEnricherTest extends BrooklynAppUnitTestSupport {
     
     // TODO These tests are a copy of BasicPolicyTest, which is a code smell.
     // However, the src/main/java code does not contain as much duplication.
-    
+
+    protected void setUpApp() {
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
+        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+    }
+
     public static class MyEnricher extends AbstractEnricher {
         @SetFromFlag("intKey")
         public static final BasicConfigKey<Integer> INT_KEY = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java b/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java
index 523a6e2..bad3b1a 100644
--- a/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java
+++ b/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java
@@ -52,6 +52,10 @@ public class BrooklynAppUnitTestSupport {
         if (mgmt == null) {
             mgmt = new LocalManagementContextForTests();
         }
+        setUpApp();
+    }
+
+    protected void setUpApp() {
         EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class)
                 .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
         app = ApplicationBuilder.newManagedApp(appSpec, mgmt);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
index 319b345..1c12319 100644
--- a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
+++ b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
@@ -26,13 +26,17 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BrooklynConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.SensorEventListener;
 import brooklyn.location.basic.SimulatedLocation;
 import brooklyn.test.Asserts;
+import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestApplicationNoEnrichersImpl;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.test.entity.TestEntityNoEnrichersImpl;
 import brooklyn.util.collections.MutableMap;
@@ -43,7 +47,7 @@ import com.google.common.collect.ImmutableList;
 public class BrooklynMetricsTest {
 
     private static final long TIMEOUT_MS = 2*1000;
-    private final static int DEFAULT_SUBSCRIPTIONS_PER_ENTITY = 2;
+    private final static int NUM_SUBSCRIPTIONS_PER_ENTITY = 4;
     
     TestApplication app;
     SimulatedLocation loc;
@@ -52,7 +56,8 @@ public class BrooklynMetricsTest {
     @BeforeMethod(alwaysRun=true)
     public void setUp() {
         loc = new SimulatedLocation();
-        app = TestApplication.Factory.newManagedInstanceForTests();
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class),
+            LocalManagementContextForTests.newInstance());
         brooklynMetrics = app.createAndManageChild(EntitySpec.create(BrooklynMetrics.class).configure("updatePeriod", 10L));
     }
     
@@ -73,7 +78,7 @@ public class BrooklynMetricsTest {
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_ACTIVE_TASKS), (Long)0L);
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_PUBLISHED) > 0);
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_DELIVERED), (Long)0L);
-                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)(2L*DEFAULT_SUBSCRIPTIONS_PER_ENTITY));
+                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)(0L+NUM_SUBSCRIPTIONS_PER_ENTITY));
             }});
     }
     
@@ -112,7 +117,7 @@ public class BrooklynMetricsTest {
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_PUBLISHED) > eventsPublished);
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_DELIVERED) > eventsDelivered);
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)
-                    (1L + 2*DEFAULT_SUBSCRIPTIONS_PER_ENTITY));
+                    (1L + NUM_SUBSCRIPTIONS_PER_ENTITY));
             }});
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
index 1bc8a3b..eeab7cd 100644
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
+++ b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
@@ -69,7 +69,7 @@ public class AutoScalerPolicyTest {
     
     @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        app = TestApplication.Factory.newManagedInstanceForTests();
         cluster = app.createAndManageChild(EntitySpec.create(TestCluster.class).configure(TestCluster.INITIAL_SIZE, 1));
         resizable = new LocallyResizableEntity(cluster, cluster);
         Entities.manage(resizable);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
index 5c9dd66..2741ef6 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
@@ -30,6 +30,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic;
@@ -55,7 +56,7 @@ import com.google.common.base.Predicates;
 
 public class ServiceFailureDetectorTest {
 
-    private static final int TIMEOUT_MS = 1*1000;
+    private static final int TIMEOUT_MS = 10*1000;
 
     private ManagementContext managementContext;
     private TestApplication app;
@@ -206,13 +207,14 @@ public class ServiceFailureDetectorTest {
     }
     
     @Test
-    public void testDisablingSetsOnFireOnFailure() throws Exception {
+    public void testDisablingOnFire() throws Exception {
         e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
             .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.PRACTICALLY_FOREVER));
         
         // Make the entity fail
         e1.setAttribute(TestEntity.SERVICE_UP, true);
         ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
@@ -226,6 +228,8 @@ public class ServiceFailureDetectorTest {
         // Make the entity fail
         e1.setAttribute(TestEntity.SERVICE_UP, true);
         ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
@@ -244,7 +248,7 @@ public class ServiceFailureDetectorTest {
         e1.setAttribute(TestEntity.SERVICE_UP, true);
         ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
 
-        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
         ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
         
         assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
index f7ddddd..09a2cc4 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
@@ -22,6 +22,7 @@ import java.util.Map;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.entity.trait.Startable;
 import brooklyn.event.AttributeSensor;
@@ -51,6 +52,9 @@ public interface SameServerEntity extends Entity, Startable {
             "provisioning.properties", "Custom properties to be passed in when provisioning a new machine",
             MutableMap.<String, Object>of());
     
+    ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK;
+    ConfigKey<QuorumCheck> RUNNING_QUORUM_CHECK = ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK;
+
     AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     @SuppressWarnings("rawtypes")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/software/base/src/main/java/brooklyn/entity/basic/SameServerEntityImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntityImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntityImpl.java
index f509249..40a7db2 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntityImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntityImpl.java
@@ -32,6 +32,12 @@ public class SameServerEntityImpl extends AbstractEntity implements SameServerEn
 
     private static final MachineLifecycleEffectorTasks LIFECYCLE_TASKS = new SameServerDriverLifecycleEffectorTasks();
 
+    @Override
+    protected void initEnrichers() {
+        super.initEnrichers();
+        addEnricher(ServiceStateLogic.newEnricherFromChildren());
+    }
+    
     /**
      * Restarts the entity and its children.
      * <p/>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
index df486aa..f80d10c 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
@@ -52,15 +52,15 @@ import brooklyn.test.EntityTestUtils;
 import brooklyn.test.HttpTestUtils;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestJavaWebAppEntity;
+import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Functionals;
 
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
-/**
- * TODO clarify test purpose
- */
 public class ControlledDynamicWebAppClusterTest {
     private static final Logger log = LoggerFactory.getLogger(ControlledDynamicWebAppClusterTest.class);
 
@@ -76,7 +76,7 @@ public class ControlledDynamicWebAppClusterTest {
         String warPath = "hello-world.war";
         warUrl = getClass().getClassLoader().getResource(warPath);
         
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        app = TestApplication.Factory.newManagedInstanceForTests();
         loc = new LocalhostMachineProvisioningLocation();
         locs = ImmutableList.of(loc);
     }
@@ -201,14 +201,19 @@ public class ControlledDynamicWebAppClusterTest {
                 .configure("initialSize", 1)
                 .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
         
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
         RecordingSensorEventListener<Lifecycle> listener = new RecordingSensorEventListener<Lifecycle>(true);
-        app.subscribe(cluster, Attributes.SERVICE_STATE, listener);
+        app.subscribe(cluster, Attributes.SERVICE_STATE_ACTUAL, listener);
         app.start(locs);
-
+        
+        Asserts.eventually(Suppliers.ofInstance(listener.getValues()), CollectionFunctionals.sizeEquals(2));
         assertEquals(listener.getValues(), ImmutableList.of(Lifecycle.STARTING, Lifecycle.RUNNING), "vals="+listener.getValues());
         listener.getValues().clear();
         
         app.stop();
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        Asserts.eventually(Suppliers.ofInstance(listener.getValues()), CollectionFunctionals.sizeEquals(2));
         assertEquals(listener.getValues(), ImmutableList.of(Lifecycle.STOPPING, Lifecycle.STOPPED), "vals="+listener.getValues());
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
index 1cec3c9..8def438 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
@@ -109,21 +109,23 @@ public class DynamicWebAppClusterTest {
     
         app.start(ImmutableList.of(new SimulatedLocation()));
         
-        // Should initially be false (when child has no service_up value) 
-        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, false);
+        // Should initially be true (now that TestJavaWebAppEntity sets true) 
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, true);
         
-        // When child is !service_up, should continue to report false
+        // When child is !service_up, should report false
         ((EntityLocal)Iterables.get(cluster.getMembers(), 0)).setAttribute(Startable.SERVICE_UP, false);
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, false);
         EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, false);
         
         cluster.resize(2);
         
         // When one of the two children is service_up, should report true
-        ((EntityLocal)Iterables.get(cluster.getMembers(), 0)).setAttribute(Startable.SERVICE_UP, true);
         EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, true);
 
         // And if that serviceUp child goes away, should again report false
-        Entities.unmanage(Iterables.get(cluster.getMembers(), 0));
+        Entities.unmanage(Iterables.get(cluster.getMembers(), 1));
+        ((EntityLocal)Iterables.get(cluster.getMembers(), 0)).setAttribute(Startable.SERVICE_UP, false);
+        
         EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, false);
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
index 96edbcd..9a3e08a 100644
--- a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
+++ b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
@@ -26,7 +26,10 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.MethodEffector;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.effector.EffectorAndBody;
 import brooklyn.entity.java.VanillaJavaAppImpl;
@@ -52,7 +55,10 @@ public class TestJavaWebAppEntity extends VanillaJavaAppImpl {
 
     
 	public void customStart(Collection<? extends Location> loc) {
+	    ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         LOG.trace("Starting {}", this);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+        setAttribute(Attributes.SERVICE_UP, true);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
index e3b0352..7d3feaf 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersYamlTest.java
@@ -18,6 +18,7 @@
  */
 package io.brooklyn.camp.brooklyn;
 
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
@@ -27,6 +28,7 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import brooklyn.config.ConfigKey;
+import brooklyn.enricher.EnrichersTest;
 import brooklyn.enricher.basic.Propagator;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
@@ -55,8 +57,8 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         log.info("App started:");
         Entities.dumpInfo(app);
         
-        Assert.assertEquals(app.getEnrichers().size(), 1);
-        final Enricher enricher = app.getEnrichers().iterator().next();
+        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(app).size(), 1);
+        final Enricher enricher = EnrichersTest.getNonSystemEnrichers(app).iterator().next();
         Assert.assertTrue(enricher instanceof TestEnricher, "enricher="+enricher);
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_NAME), "Name from YAML");
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_FROM_FUNCTION), "$brooklyn: is a fun place");
@@ -88,16 +90,16 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         log.info("App started:");
         Entities.dumpInfo(app);
 
-        Assert.assertEquals(app.getEnrichers().size(), 0);
+        Assert.assertEquals(EnrichersTest.getNonSystemEnrichers(app).size(), 0);
         Assert.assertEquals(app.getChildren().size(), 1);
         final Entity child = app.getChildren().iterator().next();
         Asserts.eventually(new Supplier<Integer>() {
             @Override
             public Integer get() {
-                return child.getEnrichers().size();
+                return EnrichersTest.getNonSystemEnrichers(child).size();
             }
         }, Predicates.<Integer> equalTo(1));        
-        final Enricher enricher = child.getEnrichers().iterator().next();
+        final Enricher enricher = EnrichersTest.getNonSystemEnrichers(child).iterator().next();
         Assert.assertNotNull(enricher);
         Assert.assertTrue(enricher instanceof TestEnricher, "enricher=" + enricher + "; type=" + enricher.getClass());
         Assert.assertEquals(enricher.getConfig(TestEnricher.CONF_NAME), "Name from YAML");
@@ -149,10 +151,10 @@ public class EnrichersYamlTest extends AbstractYamlTest {
         Asserts.eventually(new Supplier<Integer>() {
             @Override
             public Integer get() {
-                return parentEntity.getEnrichers().size();
+                return EnrichersTest.getNonSystemEnrichers(parentEntity).size();
             }
         }, Predicates.<Integer>equalTo(1));
-        Enricher enricher = parentEntity.getEnrichers().iterator().next();
+        Enricher enricher = EnrichersTest.getNonSystemEnrichers(parentEntity).iterator().next();
         Asserts.assertTrue(enricher instanceof Propagator, "Expected enricher to be Propagator, found:" + enricher);
         final Propagator propagator = (Propagator)enricher;
         Entity producer = ((EntityInternal)parentEntity).getExecutionContext().submit(MutableMap.of(), new Callable<Entity>() {
@@ -240,9 +242,10 @@ public class EnrichersYamlTest extends AbstractYamlTest {
     }
     
     private Enricher getEnricher(Entity entity) {
-        Assert.assertEquals(entity.getEnrichers().size(), 1);
-        Enricher enricher = entity.getEnrichers().iterator().next();
-        Assert.assertTrue(enricher instanceof TestReferencingEnricher);
+        List<Enricher> enrichers = EnrichersTest.getNonSystemEnrichers(entity);
+        Assert.assertEquals(enrichers.size(), 1, "Wrong number of enrichers: "+enrichers);
+        Enricher enricher = enrichers.iterator().next();
+        Assert.assertTrue(enricher instanceof TestReferencingEnricher, "Wrong enricher: "+enricher);
         return enricher;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EntitiesYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EntitiesYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EntitiesYamlTest.java
index 14c7c10..53bb4aa 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EntitiesYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EntitiesYamlTest.java
@@ -602,7 +602,7 @@ public class EntitiesYamlTest extends AbstractYamlTest {
         Entity app = createAndStartApplication(loadYaml("same-server-entity-test.yaml"));
         waitForApplicationTasks(app);
         assertNotNull(app);
-        assertEquals(app.getAttribute(Attributes.SERVICE_STATE), Lifecycle.RUNNING, "service state");
+        assertEquals(app.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING, "service state");
         assertTrue(app.getAttribute(Attributes.SERVICE_UP), "service up");
 
         assertEquals(app.getChildren().size(), 1);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/rest-server/src/main/java/brooklyn/rest/util/BrooklynRestResourceUtils.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/BrooklynRestResourceUtils.java b/usage/rest-server/src/main/java/brooklyn/rest/util/BrooklynRestResourceUtils.java
index 7075fa5..7c86d6a 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/util/BrooklynRestResourceUtils.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/util/BrooklynRestResourceUtils.java
@@ -341,7 +341,7 @@ public class BrooklynRestResourceUtils {
         if (clazz.isInterface()) {
             result = brooklyn.entity.proxying.EntitySpec.create(clazz);
         } else {
-            result = brooklyn.entity.proxying.EntitySpec.create(Entity.class).impl(clazz);
+            result = brooklyn.entity.proxying.EntitySpec.create(Entity.class).impl(clazz).additionalInterfaces(Reflections.getAllInterfaces(clazz));
         }
         if (!Strings.isEmpty(name)) result.displayName(name);
         result.configure( convertFlagsToKeys(result.getType(), config) );

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java
index 47e4b2c..2b36c5d 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -71,6 +71,8 @@ import brooklyn.test.Asserts;
 import brooklyn.test.HttpTestUtils;
 import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
 
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
@@ -82,6 +84,7 @@ import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.GenericType;
 import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 @Test(singleThreaded = true)
@@ -196,7 +199,7 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
     
     // Expect app to be running
     URI appUri = response.getLocation();
-    waitForApplicationToBeRunning(response.getLocation());
+    waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
     assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
     
     // Expect app to have the child-entity
@@ -452,8 +455,8 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
 
   @Test(dependsOnMethods = "testTriggerSampleEffector")
   public void testBatchSensorValues() {
-    Map<String,String> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state")
-        .get(new GenericType<Map<String,String>>() {});
+    WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state");
+    Map<String,Object> sensors = resource.get(new GenericType<Map<String,Object>>() {});
     assertTrue(sensors.size() > 0);
     assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
   }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java
index 8ec14b2..672537e 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -19,9 +19,9 @@
 package brooklyn.rest.testing;
 
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 import java.net.URI;
+import java.util.Collection;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
@@ -29,14 +29,20 @@ import java.util.logging.Level;
 import javax.ws.rs.core.MediaType;
 
 import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.Entities;
 import brooklyn.rest.domain.ApplicationSpec;
 import brooklyn.rest.domain.ApplicationSummary;
 import brooklyn.rest.domain.Status;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.repeat.Repeater;
+import brooklyn.util.time.Duration;
 
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
@@ -44,6 +50,8 @@ import com.sun.jersey.spi.inject.Errors;
 
 public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
 
+    private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
+    
     @BeforeClass(alwaysRun = true)
     public void setUp() throws Exception {
         // need this to debug jersey inject errors
@@ -71,6 +79,9 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
     }
     
     protected void waitForApplicationToBeRunning(final URI applicationRef) {
+        waitForApplicationToBeRunning(applicationRef, Duration.minutes(3));
+    }
+    protected void waitForApplicationToBeRunning(final URI applicationRef, Duration timeout) {
         if (applicationRef==null)
             throw new NullPointerException("No application URI available (consider using BrooklynRestResourceTest.clientDeploy)");
         
@@ -80,14 +91,21 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
                     public Boolean call() throws Exception {
                         Status status = getApplicationStatus(applicationRef);
                         if (status == Status.ERROR) {
-                            fail("Application failed with ERROR");
+                            Assert.fail("Application failed with ERROR");
                         }
                         return status == Status.RUNNING;
                     }
                 })
-                .every(100, TimeUnit.MILLISECONDS)
-                .limitTimeTo(3, TimeUnit.MINUTES)
+                .every(Duration.millis(100))
+                .limitTimeTo(timeout)
                 .run();
+        
+        if (!started) {
+            log.warn("Did not start application "+applicationRef+":");
+            Collection<Application> apps = getManagementContext().getApplications();
+            for (Application app: apps)
+                Entities.dumpInfo(app);
+        }
         assertTrue(started);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockAppBuilder.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
index 82d1ab7..cb68cf3 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
@@ -22,6 +22,7 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.util.javalang.Reflections;
 
 public class RestMockAppBuilder extends ApplicationBuilder {
 
@@ -31,6 +32,8 @@ public class RestMockAppBuilder extends ApplicationBuilder {
     
     @Override
     protected void doBuild() {
-        addChild(EntitySpec.create(Entity.class).impl(RestMockSimpleEntity.class).displayName("child1"));
+        addChild(EntitySpec.create(Entity.class).impl(RestMockSimpleEntity.class)
+            .additionalInterfaces(Reflections.getAllInterfaces(RestMockSimpleEntity.class))
+            .displayName("child1"));
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
index 10ee1cc..d1c6cb2 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
@@ -56,6 +56,12 @@ public class RestMockSimpleEntity extends SoftwareProcessImpl {
     public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags) {
         super(flags);
     }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        connectServiceUpIsRunning();
+    }
 
     @SetFromFlag("sampleConfig")
     public static final ConfigKey<String> SAMPLE_CONFIG = new BasicConfigKey<String>(
@@ -75,11 +81,6 @@ public class RestMockSimpleEntity extends SoftwareProcessImpl {
         return result;
     }
 
-    @Override
-    public void waitForServiceUp() {
-        return;
-    }
-    
     @SuppressWarnings("rawtypes")
     @Override
     public Class getDriverInterface() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5db500e4/usage/test-support/src/main/java/brooklyn/test/EntityTestUtils.java
----------------------------------------------------------------------
diff --git a/usage/test-support/src/main/java/brooklyn/test/EntityTestUtils.java b/usage/test-support/src/main/java/brooklyn/test/EntityTestUtils.java
index 3b8dab2..693c807 100644
--- a/usage/test-support/src/main/java/brooklyn/test/EntityTestUtils.java
+++ b/usage/test-support/src/main/java/brooklyn/test/EntityTestUtils.java
@@ -160,4 +160,5 @@ public class EntityTestUtils {
         assertAttributeEventually(entity, attribute, 
             Predicates.not(Predicates.equalTo(entity.getAttribute(attribute))));
     }
+    
 }


[18/26] git commit: rewrite of ServiceFailureDetector -- part of what it did before is now done by ServiceStateLogic of course, so now this extends that, and provides options for emitting notifications of ENTITY_FAILED, ENTITY_RECOVERED, as well as suppr

Posted by he...@apache.org.
rewrite of ServiceFailureDetector -- part of what it did before is now done by ServiceStateLogic of course,
so now this extends that, and provides options for emitting notifications of ENTITY_FAILED, ENTITY_RECOVERED, as well as suppressing ON_FIRE


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/b2daedf8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/b2daedf8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/b2daedf8

Branch: refs/heads/master
Commit: b2daedf8336a891167602f67e7f8e576256dc5e8
Parents: d3886a0
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 25 15:17:53 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:18 2014 -0400

----------------------------------------------------------------------
 api/src/main/java/brooklyn/entity/Entity.java   |   2 +
 .../entity/basic/ServiceStateLogic.java         |  33 +-
 .../internal/LocalSubscriptionManager.java      |   6 +-
 .../util/task/BasicExecutionManager.java        |   7 +-
 .../enricher/CustomAggregatingEnricherTest.java |   2 +-
 .../basic/MultiLocationResolverTest.java        |   3 +-
 .../brooklyn/test/entity/TestClusterImpl.java   |   9 +
 .../java/brooklyn/test/entity/TestEntity.java   |   4 +-
 .../brooklyn/test/entity/TestEntityImpl.java    |  10 +-
 .../brooklyn/demo/CumulusRDFApplication.java    |   6 +-
 .../demo/HighAvailabilityCassandraCluster.java  |   5 +-
 .../java/brooklyn/demo/ResilientMongoDbApp.java |   2 +-
 .../java/brooklyn/demo/RiakClusterExample.java  |   9 +-
 .../brooklyn/demo/WideAreaCassandraCluster.java |   5 +-
 .../policy/ha/ServiceFailureDetector.java       | 409 +++++--------------
 .../entity/brooklyn/BrooklynMetricsTest.java    |  20 +-
 .../autoscaling/AutoScalerPolicyMetricTest.java |   5 +-
 .../brooklyn/policy/ha/HaPolicyRebindTest.java  |   9 +-
 ...ServiceFailureDetectorStabilizationTest.java |  31 +-
 .../policy/ha/ServiceFailureDetectorTest.java   | 203 +++++----
 .../brooklyn/policy/ha/ServiceReplacerTest.java |   2 +-
 .../brooklyn/entity/webapp/JBossExample.groovy  |  48 ---
 .../test/entity/TestJavaWebAppEntity.groovy     |  75 ----
 .../test/entity/TestJavaWebAppEntity.java       |  73 ++++
 24 files changed, 421 insertions(+), 557 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/api/src/main/java/brooklyn/entity/Entity.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java
index 88a10fa..9a09392 100644
--- a/api/src/main/java/brooklyn/entity/Entity.java
+++ b/api/src/main/java/brooklyn/entity/Entity.java
@@ -25,6 +25,8 @@ import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import com.google.common.annotations.Beta;
+
 import brooklyn.basic.BrooklynObject;
 import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigKey.HasConfigKey;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index 9e66381..e3d5090 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -70,6 +70,12 @@ public class ServiceStateLogic {
     /** static only; not for instantiation */
     private ServiceStateLogic() {}
 
+    public static <TKey,TVal> TVal getMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key) {
+        Map<TKey, TVal> map = entity.getAttribute(sensor);
+        if (map==null) return null;
+        return map.get(key);
+    }
+    
     @SuppressWarnings("unchecked")
     public static <TKey,TVal> void clearMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key) {
         updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE);
@@ -164,6 +170,15 @@ public class ServiceStateLogic {
      * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity.
      */
     public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener<Object> {
+        public ComputeServiceState() {}
+        public ComputeServiceState(Map<?,?> flags) { super(flags); }
+            
+        @Override
+        public void init() {
+            super.init();
+            if (uniqueTag==null) uniqueTag = "service.state.actual";
+        }
+        
         public void setEntity(EntityLocal entity) {
             super.setEntity(entity);
             if (suppressDuplicates==null) {
@@ -206,7 +221,13 @@ public class ServiceStateLogic {
             } else if (problems!=null && !problems.isEmpty()) {
                 return Lifecycle.ON_FIRE;
             } else {
-                return (up==null ? null : up ? Lifecycle.RUNNING : Lifecycle.STOPPED);
+                // no expected transition
+                // if the problems map is non-null, then infer, else leave unchanged
+                if (problems!=null)
+                    return (up==null ? null /* remove if up is not set */ : 
+                        up ? Lifecycle.RUNNING : Lifecycle.STOPPED);
+                else
+                    return entity.getAttribute(SERVICE_STATE_ACTUAL);
             }
         }
 
@@ -220,7 +241,7 @@ public class ServiceStateLogic {
         return newEnricherForServiceState(ComputeServiceState.class);
     }
     public static final EnricherSpec<?> newEnricherForServiceState(Class<? extends Enricher> type) {
-        return EnricherSpec.create(type).uniqueTag("service.state.actual from service.state.expected and service.problems");
+        return EnricherSpec.create(type);
     }
     
     public static class ServiceProblemsLogic {
@@ -244,6 +265,14 @@ public class ServiceStateLogic {
         public static void clearProblemsIndicator(EntityLocal entity, Effector<?> eff) {
             clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, eff.getName());
         }
+        /** as {@link #updateProblemsIndicator(EntityLocal, Sensor, Object)} */
+        public static void updateProblemsIndicator(EntityLocal entity, String key, Object value) {
+            updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, key, value);
+        }
+        /** as {@link #clearProblemsIndicator(EntityLocal, Sensor)} */
+        public static void clearProblemsIndicator(EntityLocal entity, String key) {
+            clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, key);
+        }
     }
     
     public static class ComputeServiceIndicatorsFromChildrenAndMembers extends AbstractMultipleSensorAggregator<Void> implements SensorEventListener<Object> {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java b/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
index 0bd8578..83cec75 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
@@ -185,7 +185,11 @@ public class LocalSubscriptionManager extends AbstractSubscriptionManager {
                         return "LSM.publish("+event+")";
                     }
                     public void run() {
-                        sAtClosureCreation.listener.onEvent(event);
+                        try {
+                            sAtClosureCreation.listener.onEvent(event);
+                        } catch (Throwable t) {
+                            LOG.warn("Error in "+this+": "+t, t);
+                        }
                     }});
                 totalEventsDeliveredCount.incrementAndGet();
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java b/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java
index da8c456..2f6e396 100644
--- a/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java
+++ b/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java
@@ -397,8 +397,13 @@ public class BasicExecutionManager implements ExecutionManager {
                         afterEnd(flags, task);
                     }
                     if (error!=null) {
+                        /* we throw, after logging debug.
+                         * the throw means the error is available for task submitters to monitor.
+                         * however it is possible no one is monitoring it, in which case we will have debug logging only for errors.
+                         * (the alternative, of warn-level logging in lots of places where we don't want it, seems worse!) 
+                         */
                         if (log.isDebugEnabled()) {
-                            // debug only here, because we rethrow
+                            // debug only here, because most submitters will handle failures
                             log.debug("Exception running task "+task+" (rethrowing): "+error.getMessage(), error);
                             if (log.isTraceEnabled())
                                 log.trace("Trace for exception running task "+task+" (rethrowing): "+error.getMessage(), error);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java b/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java
index f3e06f6..70d0c10 100644
--- a/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java
+++ b/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java
@@ -48,7 +48,7 @@ public class CustomAggregatingEnricherTest extends BrooklynAppUnitTestSupport {
     public static final Logger log = LoggerFactory.getLogger(CustomAggregatingEnricherTest.class);
             
     private static final long TIMEOUT_MS = 10*1000;
-    private static final long SHORT_WAIT_MS = 250;
+    private static final long SHORT_WAIT_MS = 50;
     
     TestEntity entity;
     SimulatedLocation loc;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java b/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java
index 937679d..85bcfbd 100644
--- a/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java
+++ b/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java
@@ -44,6 +44,7 @@ import brooklyn.location.MachineProvisioningLocation;
 import brooklyn.location.NoMachinesAvailableException;
 import brooklyn.location.cloud.AvailabilityZoneExtension;
 import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
@@ -63,7 +64,7 @@ public class MultiLocationResolverTest {
 
     @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
-        managementContext = new LocalManagementContext(BrooklynProperties.Factory.newEmpty());
+        managementContext = LocalManagementContextForTests.newInstance();
         brooklynProperties = managementContext.getBrooklynProperties();
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java b/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java
index 0bd2521..3663520 100644
--- a/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.test.entity;
 
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.trait.Startable;
 
@@ -38,6 +39,14 @@ public class TestClusterImpl extends DynamicClusterImpl implements TestCluster {
     }
     
     @Override
+    protected void initEnrichers() {
+        // say this is up if it has no children 
+        setConfig(UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty());
+        
+        super.initEnrichers();
+    }
+    
+    @Override
     public Integer resize(Integer desiredSize) {
         this.size = desiredSize;
         return size;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/test/java/brooklyn/test/entity/TestEntity.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestEntity.java b/core/src/test/java/brooklyn/test/entity/TestEntity.java
index ac88acd..c3ca9c2 100644
--- a/core/src/test/java/brooklyn/test/entity/TestEntity.java
+++ b/core/src/test/java/brooklyn/test/entity/TestEntity.java
@@ -76,7 +76,9 @@ public interface TestEntity extends Entity, Startable, EntityLocal, EntityIntern
     public static final AttributeSensor<String> NAME = Sensors.newStringSensor("test.name", "Test name");
     public static final BasicNotificationSensor<Integer> MY_NOTIF = new BasicNotificationSensor<Integer>(Integer.class, "test.myNotif", "Test notification");
     
-    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
+    @Deprecated
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE_ACTUAL;
     
     public static final MethodEffector<Void> MY_EFFECTOR = new MethodEffector<Void>(TestEntity.class, "myEffector");
     public static final MethodEffector<Object> IDENTITY_EFFECTOR = new MethodEffector<Object>(TestEntity.class, "identityEffector");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
index c2a3884..3ccf614 100644
--- a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.location.Location;
 import brooklyn.util.collections.MutableMap;
@@ -122,19 +123,20 @@ public class TestEntityImpl extends AbstractEntity implements TestEntity {
     public void start(Collection<? extends Location> locs) {
         LOG.trace("Starting {}", this);
         callHistory.add("start");
-        setAttribute(SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         counter.incrementAndGet();
         addLocations(locs);
-        setAttribute(SERVICE_STATE, Lifecycle.RUNNING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+        setAttribute(SERVICE_UP, true);
     }
 
     @Override
     public void stop() { 
         LOG.trace("Stopping {}", this);
         callHistory.add("stop");
-        setAttribute(SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         counter.decrementAndGet();
-        setAttribute(SERVICE_STATE, Lifecycle.STOPPED);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
index a54ef58..eaabaff 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
@@ -31,15 +31,12 @@ import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.AbstractApplication;
-import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.StartableApplication;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.java.UsesJava;
@@ -58,6 +55,7 @@ import brooklyn.event.basic.DependentConfiguration;
 import brooklyn.launcher.BrooklynLauncher;
 import brooklyn.location.Location;
 import brooklyn.location.basic.PortRanges;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.ha.ServiceFailureDetector;
 import brooklyn.policy.ha.ServiceReplacer;
@@ -127,7 +125,7 @@ public class CumulusRDFApplication extends AbstractApplication {
                         .configure(UsesJmx.JMX_PORT, PortRanges.fromString("11099+"))
                         .configure(UsesJmx.RMI_REGISTRY_PORT, PortRanges.fromString("9001+"))
                         .configure(CassandraNode.THRIFT_PORT, PortRanges.fromInteger(getConfig(CASSANDRA_THRIFT_PORT)))
-                        .policy(PolicySpec.create(ServiceFailureDetector.class))
+                        .enricher(EnricherSpec.create(ServiceFailureDetector.class))
                         .policy(PolicySpec.create(ServiceRestarter.class)
                                 .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, ServiceFailureDetector.ENTITY_FAILED)))
                 .policy(PolicySpec.create(ServiceReplacer.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
index 2dce1ea..c21c4cf 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
@@ -23,14 +23,15 @@ import java.util.List;
 import brooklyn.catalog.Catalog;
 import brooklyn.catalog.CatalogConfig;
 import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.entity.nosql.cassandra.CassandraDatacenter;
 import brooklyn.entity.nosql.cassandra.CassandraNode;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.launcher.BrooklynLauncher;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.ha.ServiceFailureDetector;
 import brooklyn.policy.ha.ServiceReplacer;
@@ -64,7 +65,7 @@ public class HighAvailabilityCassandraCluster extends AbstractApplication {
                 //.configure(CassandraCluster.AVAILABILITY_ZONE_NAMES, ImmutableList.of("us-east-1b", "us-east-1c", "us-east-1e"))
                 .configure(CassandraDatacenter.ENDPOINT_SNITCH_NAME, "GossipingPropertyFileSnitch")
                 .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class)
-                        .policy(PolicySpec.create(ServiceFailureDetector.class))
+                        .enricher(EnricherSpec.create(ServiceFailureDetector.class))
                         .policy(PolicySpec.create(ServiceRestarter.class)
                                 .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, ServiceFailureDetector.ENTITY_FAILED)))
                 .policy(PolicySpec.create(ServiceReplacer.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
index 8506b27..290f25e 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
@@ -84,7 +84,7 @@ public class ResilientMongoDbApp extends AbstractApplication implements Startabl
 
     /** invoked whenever a new MongoDB server is added (the server may not be started yet) */
     protected void initSoftwareProcess(SoftwareProcess p) {
-        p.addPolicy(new ServiceFailureDetector());
+        p.addEnricher(new ServiceFailureDetector());
         p.addPolicy(new ServiceRestarter(ServiceFailureDetector.ENTITY_FAILED));
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
index d53ce86..0134e27 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
@@ -20,9 +20,6 @@ package brooklyn.demo;
 
 import java.util.List;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-
 import brooklyn.catalog.Catalog;
 import brooklyn.catalog.CatalogConfig;
 import brooklyn.config.ConfigKey;
@@ -34,11 +31,15 @@ import brooklyn.entity.nosql.riak.RiakCluster;
 import brooklyn.entity.nosql.riak.RiakNode;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.launcher.BrooklynLauncher;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.ha.ServiceFailureDetector;
 import brooklyn.policy.ha.ServiceRestarter;
 import brooklyn.util.CommandLineUtil;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
 @Catalog(name = "Riak Cluster Application", description = "Riak ring deployment blueprint")
 public class RiakClusterExample extends AbstractApplication {
 
@@ -67,7 +68,7 @@ public class RiakClusterExample extends AbstractApplication {
         addChild(EntitySpec.create(RiakCluster.class)
                 .configure(RiakCluster.INITIAL_SIZE, getConfig(RIAK_RING_SIZE))
                 .configure(RiakCluster.MEMBER_SPEC, EntitySpec.create(RiakNode.class)
-                        .policy(PolicySpec.create(ServiceFailureDetector.class))
+                        .enricher(EnricherSpec.create(ServiceFailureDetector.class))
                         .policy(PolicySpec.create(ServiceRestarter.class)
                                 .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, ServiceFailureDetector.ENTITY_FAILED))));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
index c40eb27..8e30fc4 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
@@ -24,8 +24,8 @@ import java.util.List;
 import brooklyn.catalog.Catalog;
 import brooklyn.catalog.CatalogConfig;
 import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.entity.nosql.cassandra.CassandraDatacenter;
@@ -33,6 +33,7 @@ import brooklyn.entity.nosql.cassandra.CassandraFabric;
 import brooklyn.entity.nosql.cassandra.CassandraNode;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.launcher.BrooklynLauncher;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.ha.ServiceFailureDetector;
 import brooklyn.policy.ha.ServiceReplacer;
@@ -61,7 +62,7 @@ public class WideAreaCassandraCluster extends AbstractApplication {
                 .configure(CassandraNode.CUSTOM_SNITCH_JAR_URL, "classpath://brooklyn/entity/nosql/cassandra/cassandra-multicloud-snitch.jar")
                 .configure(CassandraFabric.MEMBER_SPEC, EntitySpec.create(CassandraDatacenter.class)
                         .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class)
-                                .policy(PolicySpec.create(ServiceFailureDetector.class))
+                                .enricher(EnricherSpec.create(ServiceFailureDetector.class))
                                 .policy(PolicySpec.create(ServiceRestarter.class)
                                         .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, ServiceFailureDetector.ENTITY_FAILED)))
                         .policy(PolicySpec.create(ServiceReplacer.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
index 5efad2b..c523aba 100644
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
+++ b/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
@@ -18,13 +18,10 @@
  */
 package brooklyn.policy.ha;
 
-import static brooklyn.util.time.Time.makeTimeStringRounded;
-
-import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,15 +30,12 @@ import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.trait.Startable;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
 import brooklyn.event.SensorEvent;
-import brooklyn.event.SensorEventListener;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.management.SubscriptionHandle;
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.policy.ha.HASensors.FailureDescriptor;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
@@ -50,26 +44,17 @@ import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.task.BasicTask;
 import brooklyn.util.task.ScheduledTask;
 import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
 
-/** attaches to a SoftwareProcess (or anything emitting SERVICE_UP and SERVICE_STATE)
- * and emits HASensors.ENTITY_FAILED and ENTITY_RECOVERED as appropriate
- * @see MemberFailureDetectionPolicy
+/** 
+ * emits {@link HASensors#ENTITY_FAILED} whenever the parent's default logic ({@link ComputeServiceState}) would detect a problem,
+ * and similarly {@link HASensors#ENTITY_RECOVERED} when recovered.
+ * <p>
+ * gives more control over suppressing {@link Lifecycle#ON_FIRE}, 
+ * for some period of time
+ * (or until another process manually sets {@link Attributes#SERVICE_STATE_ACTUAL} to {@value Lifecycle#ON_FIRE},
+ * which this enricher will not clear until all problems have gone away)
  */
-public class ServiceFailureDetector extends AbstractPolicy {
-
-    // TODO Remove duplication between this and MemberFailureDetectionPolicy.
-    // The latter could be re-written to use this. Or could even be deprecated
-    // in favour of this.
-
-    public enum LastPublished {
-        NONE,
-        FAILED,
-        RECOVERED;
-    }
+public class ServiceFailureDetector extends ServiceStateLogic.ComputeServiceState {
 
     private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureDetector.class);
 
@@ -77,47 +62,45 @@ public class ServiceFailureDetector extends AbstractPolicy {
 
     public static final BasicNotificationSensor<FailureDescriptor> ENTITY_FAILED = HASensors.ENTITY_FAILED;
 
-    // TODO delay before reporting failure (give it time to fix itself, e.g. transient failures)
-    
     @SetFromFlag("onlyReportIfPreviouslyUp")
-    public static final ConfigKey<Boolean> ONLY_REPORT_IF_PREVIOUSLY_UP = ConfigKeys.newBooleanConfigKey("onlyReportIfPreviouslyUp", "", true);
+    public static final ConfigKey<Boolean> ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP = ConfigKeys.newBooleanConfigKey("onlyReportIfPreviouslyUp", 
+        "Prevents the policy from emitting ENTITY_FAILED if the entity fails on startup (ie has never been up)", true);
     
-    @SetFromFlag("useServiceStateRunning")
-    public static final ConfigKey<Boolean> USE_SERVICE_STATE_RUNNING = ConfigKeys.newBooleanConfigKey("useServiceStateRunning", "", true);
+    public static final ConfigKey<Boolean> MONITOR_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("monitorServiceProblems", 
+        "Whether to monitor service problems, and emit on failures there (if set to false, this monitors only service up)", true);
 
-    @SetFromFlag("setOnFireOnFailure")
-    public static final ConfigKey<Boolean> SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true);
+    @SetFromFlag("serviceOnFireStabilizationDelay")
+    public static final ConfigKey<Duration> SERVICE_ON_FIRE_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+            .name("serviceOnFire.stabilizationDelay")
+            .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before concluding ON_FIRE")
+            .defaultValue(Duration.ZERO)
+            .build();
 
-    @SetFromFlag("serviceFailedStabilizationDelay")
-    public static final ConfigKey<Duration> SERVICE_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
-            .name("serviceRestarter.serviceFailedStabilizationDelay")
-            .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before concluding failure")
+    @SetFromFlag("entityFailedStabilizationDelay")
+    public static final ConfigKey<Duration> ENTITY_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+            .name("entityFailed.stabilizationDelay")
+            .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before emitting ENTITY_FAILED")
             .defaultValue(Duration.ZERO)
             .build();
 
-    @SetFromFlag("serviceRecoveredStabilizationDelay")
-    public static final ConfigKey<Duration> SERVICE_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
-            .name("serviceRestarter.serviceRecoveredStabilizationDelay")
-            .description("For a failed entity, time period for which the service must be consistently up for (e.g. doesn't report up-down-up) before concluding recovered")
+    @SetFromFlag("entityRecoveredStabilizationDelay")
+    public static final ConfigKey<Duration> ENTITY_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+            .name("entityRecovered.stabilizationDelay")
+            .description("For a failed entity, time period for which the service must be consistently up for (e.g. doesn't report up-down-up) before emitting ENTITY_RECOVERED")
             .defaultValue(Duration.ZERO)
             .build();
 
-    protected final AtomicReference<Boolean> serviceIsUp = new AtomicReference<Boolean>();
-    protected final AtomicReference<Lifecycle> serviceState = new AtomicReference<Lifecycle>();
-    protected final AtomicReference<Long> serviceLastUp = new AtomicReference<Long>();
-    protected final AtomicReference<Long> serviceLastDown = new AtomicReference<Long>();
+    protected Long firstUpTime;
     
     protected Long currentFailureStartTime = null;
     protected Long currentRecoveryStartTime = null;
-
-    protected LastPublished lastPublished = LastPublished.NONE;
-    protected boolean weSetItOnFire = false;
+    
+    protected Long publishEntityFailedTime = null;
+    protected Long publishEntityRecoveredTime = null;
 
     private final AtomicBoolean executorQueued = new AtomicBoolean(false);
     private volatile long executorTime = 0;
 
-    private List<SubscriptionHandle> subscriptionHandles = Lists.newCopyOnWriteArrayList();
-
     public ServiceFailureDetector() {
         this(new ConfigBag());
     }
@@ -130,161 +113,98 @@ public class ServiceFailureDetector extends AbstractPolicy {
         // TODO hierarchy should use ConfigBag, and not change flags
         super(configBag.getAllConfigMutable());
     }
-
-    @Override
-    public void setEntity(EntityLocal entity) {
-        super.setEntity(entity);
-        doSubscribe();
-        onMemberAdded();
-    }
-
+    
     @Override
-    public void suspend() {
-        super.suspend();
-        doUnsubscribe();
+    public void onEvent(SensorEvent<Object> event) {
+        if (firstUpTime==null && event!=null && Attributes.SERVICE_UP.equals(event.getSensor()) && Boolean.TRUE.equals(event.getValue())) {
+            firstUpTime = event.getTimestamp();
+        }
+        
+        super.onEvent(event);
     }
     
     @Override
-    public void resume() {
-        serviceIsUp.set(null);
-        serviceState.set(null);
-        serviceLastUp.set(null);
-        serviceLastDown.set(null);
-        currentFailureStartTime = null;
-        currentRecoveryStartTime = null;
-        lastPublished = LastPublished.NONE;
-        weSetItOnFire = false;
-        executorQueued.set(false);
-        executorTime = 0;
-
-        super.resume();
-        doSubscribe();
-        onMemberAdded();
-    }
-
-    protected void doSubscribe() {
-        if (subscriptionHandles.isEmpty()) {
-            if (getConfig(USE_SERVICE_STATE_RUNNING)) {
-                SubscriptionHandle handle = subscribe(entity, Attributes.SERVICE_STATE, new SensorEventListener<Lifecycle>() {
-                    @Override public void onEvent(SensorEvent<Lifecycle> event) {
-                        onServiceState(event.getValue());
-                    }
-                });
-                subscriptionHandles.add(handle);
+    protected void setActualState(Lifecycle state) {
+        if (state==Lifecycle.ON_FIRE) {
+            if (currentFailureStartTime==null) {
+                currentFailureStartTime = System.currentTimeMillis();
+                publishEntityFailedTime = currentFailureStartTime + getConfig(ENTITY_FAILED_STABILIZATION_DELAY).toMilliseconds();
             }
+            // cancel any existing recovery
+            currentRecoveryStartTime = null;
+            publishEntityRecoveredTime = null;
             
-            SubscriptionHandle handle = subscribe(entity, Startable.SERVICE_UP, new SensorEventListener<Boolean>() {
-                @Override public void onEvent(SensorEvent<Boolean> event) {
-                    onServiceUp(event.getValue());
-                }
-            });
-            subscriptionHandles.add(handle);
-        }
-    }
-    
-    protected void doUnsubscribe() {
-        // TODO Could be more defensive with synchronization, but things shouldn't be calling resume + suspend concurrently
-        for (SubscriptionHandle handle : subscriptionHandles) {
-            unsubscribe(entity, handle);
-        }
-        subscriptionHandles.clear();
-    }
-    
-    private Duration getServiceFailedStabilizationDelay() {
-        return getConfig(SERVICE_FAILED_STABILIZATION_DELAY);
-    }
-
-    private Duration getServiceRecoveredStabilizationDelay() {
-        return getConfig(SERVICE_RECOVERED_STABILIZATION_DELAY);
-    }
-
-    private synchronized void onServiceUp(Boolean isNowUp) {
-        if (isNowUp != null) {
-            Boolean old = serviceIsUp.getAndSet(isNowUp);
-            if (isNowUp) {
-                serviceLastUp.set(System.currentTimeMillis());
+            long now = System.currentTimeMillis();
+            
+            long delayBeforeCheck = currentFailureStartTime+getConfig(SERVICE_ON_FIRE_STABILIZATION_DELAY).toMilliseconds() - now;
+            if (delayBeforeCheck<=0) {
+                super.setActualState(state);
             } else {
-                serviceLastDown.set(System.currentTimeMillis());
-            }
-            if (!Objects.equal(old, serviceIsUp)) {
-                checkHealth();
+                recomputeAfterDelay(delayBeforeCheck);
             }
-        }
-    }
-    
-    private synchronized void onServiceState(Lifecycle status) {
-        if (status != null) {
-            Lifecycle old = serviceState.getAndSet(status);
-            if (!Objects.equal(old, status)) {
-                checkHealth();
-            }
-        }
-    }
-    
-    private synchronized void onMemberAdded() {
-        if (getConfig(USE_SERVICE_STATE_RUNNING)) {
-            Lifecycle status = entity.getAttribute(Attributes.SERVICE_STATE);
-            onServiceState(status);
-        }
-        
-        Boolean isUp = entity.getAttribute(Startable.SERVICE_UP);
-        onServiceUp(isUp);
-    }
-
-    private synchronized void checkHealth() {
-        CalculatedStatus status = calculateStatus();
-        boolean failed = status.failed;
-        boolean healthy = status.healthy;
-        long now = System.currentTimeMillis();
-        
-        if (healthy) {
-            if (lastPublished == LastPublished.FAILED) {
-                if (currentRecoveryStartTime == null) {
-                    LOG.info("{} health-check for {}, component now recovering: {}", new Object[] {this, entity, status.getDescription()});
-                    currentRecoveryStartTime = now;
-                    schedulePublish();
+            
+            if (publishEntityFailedTime!=null) {
+                delayBeforeCheck = publishEntityFailedTime - now;
+                if (firstUpTime==null && getConfig(ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP)) {
+                    // suppress
+                    publishEntityFailedTime = null;
+                } else if (delayBeforeCheck<=0) {
+                    publishEntityFailedTime = null;
+                    entity.emit(HASensors.ENTITY_FAILED, new HASensors.FailureDescriptor(entity, getFailureDescription(now)));
                 } else {
-                    if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing recovering: {}", new Object[] {this, entity, status.getDescription()});
+                    recomputeAfterDelay(delayBeforeCheck);
                 }
-            } else {
-                if (currentFailureStartTime != null) {
-                    LOG.info("{} health-check for {}, component now healthy: {}", new Object[] {this, entity, status.getDescription()});
+            }
+            
+        } else {
+            if (state == Lifecycle.RUNNING) {
+                if (currentFailureStartTime!=null) {
                     currentFailureStartTime = null;
-                } else {
-                    if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still healthy: {}", new Object[] {this, entity, status.getDescription()});
+                    publishEntityFailedTime = null;
+
+                    currentRecoveryStartTime = System.currentTimeMillis();
+                    publishEntityRecoveredTime = currentRecoveryStartTime + getConfig(ENTITY_RECOVERED_STABILIZATION_DELAY).toMilliseconds();
                 }
             }
-        } else if (failed) {
-            if (lastPublished != LastPublished.FAILED) {
-                if (currentFailureStartTime == null) {
-                    LOG.info("{} health-check for {}, component now failing: {}", new Object[] {this, entity, status.getDescription()});
-                    currentFailureStartTime = now;
-                    schedulePublish();
-                } else {
-                    if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing failing: {}", new Object[] {this, entity, status.getDescription()});
-                }
-            } else {
-                if (currentRecoveryStartTime != null) {
-                    LOG.info("{} health-check for {}, component now failing: {}", new Object[] {this, entity, status.getDescription()});
-                    currentRecoveryStartTime = null;
+
+            super.setActualState(state);
+            
+            if (publishEntityRecoveredTime!=null) {
+                long now = System.currentTimeMillis();
+                long delayBeforeCheck = publishEntityRecoveredTime - now;
+                if (delayBeforeCheck<=0) {
+                    entity.emit(HASensors.ENTITY_RECOVERED, new HASensors.FailureDescriptor(entity, null));
+                    publishEntityRecoveredTime = null;
                 } else {
-                    if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still failed: {}", new Object[] {this, entity, status.getDescription()});
+                    recomputeAfterDelay(delayBeforeCheck);
                 }
             }
-        } else {
-            if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, in unconfirmed sate: {}", new Object[] {this, entity, status.getDescription()});
         }
     }
-    
-    protected CalculatedStatus calculateStatus() {
-        return new CalculatedStatus();
-    }
 
-    protected void schedulePublish() {
-        schedulePublish(0);
+    private String getFailureDescription(long now) {
+        String description = null;
+        Map<String, Object> serviceProblems = entity.getAttribute(Attributes.SERVICE_PROBLEMS);
+        if (serviceProblems!=null && !serviceProblems.isEmpty()) {
+            Entry<String, Object> problem = serviceProblems.entrySet().iterator().next();
+            description = problem.getKey()+": "+problem.getValue();
+            if (serviceProblems.size()>1) {
+                description = serviceProblems.size()+" service problems, including "+description;
+            } else {
+                description = "service problem: "+description;
+            }
+        } else if (Boolean.FALSE.equals(entity.getAttribute(Attributes.SERVICE_UP))) {
+            description = "service not up";
+        } else {
+            description = "service failure detected";
+        }
+        if (publishEntityFailedTime!=null && currentFailureStartTime!=null && publishEntityFailedTime > currentFailureStartTime)
+            description = " (stabilized for "+Duration.of(now - currentFailureStartTime, TimeUnit.MILLISECONDS)+")";
+        return description;
     }
     
-    protected void schedulePublish(long delay) {
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void recomputeAfterDelay(long delay) {
         if (isRunning() && executorQueued.compareAndSet(false, true)) {
             long now = System.currentTimeMillis();
             delay = Math.max(0, Math.max(delay, (executorTime + MIN_PERIOD_BETWEEN_EXECS_MILLIS) - now));
@@ -296,16 +216,16 @@ public class ServiceFailureDetector extends AbstractPolicy {
                         executorTime = System.currentTimeMillis();
                         executorQueued.set(false);
 
-                        publishNow();
+                        onEvent(null);
                         
                     } catch (Exception e) {
                         if (isRunning()) {
-                            LOG.error("Error resizing: "+e, e);
+                            LOG.error("Error in enricher "+this+": "+e, e);
                         } else {
-                            if (LOG.isDebugEnabled()) LOG.debug("Error resizing, but no longer running: "+e, e);
+                            if (LOG.isDebugEnabled()) LOG.debug("Error in enricher "+this+" (but no longer running): "+e, e);
                         }
                     } catch (Throwable t) {
-                        LOG.error("Error in service-failure-detector: "+t, t);
+                        LOG.error("Error in enricher "+this+": "+t, t);
                         throw Exceptions.propagate(t);
                     }
                 }
@@ -316,115 +236,4 @@ public class ServiceFailureDetector extends AbstractPolicy {
         }
     }
     
-    private synchronized void publishNow() {
-        if (!isRunning()) return;
-        
-        CalculatedStatus calculatedStatus = calculateStatus();
-        
-        Long lastUpTime = serviceLastUp.get();
-        Long lastDownTime = serviceLastDown.get();
-        Boolean isUp = serviceIsUp.get();
-        Lifecycle status = serviceState.get();
-        boolean failed = calculatedStatus.failed;
-        boolean healthy = calculatedStatus.healthy;
-        long serviceFailedStabilizationDelay = getServiceFailedStabilizationDelay().toMilliseconds();
-        long serviceRecoveredStabilizationDelay = getServiceRecoveredStabilizationDelay().toMilliseconds();
-        long now = System.currentTimeMillis();
-        
-        if (failed) {
-            if (lastPublished != LastPublished.FAILED) {
-                // only publish if consistently down for serviceFailedStabilizationDelay
-                long currentFailurePeriod = getTimeDiff(now, currentFailureStartTime);
-                long sinceLastUpPeriod = getTimeDiff(now, lastUpTime);
-                if (currentFailurePeriod > serviceFailedStabilizationDelay && sinceLastUpPeriod > serviceFailedStabilizationDelay) {
-                    String description = calculatedStatus.getDescription();
-                    LOG.warn("{} health-check for {}, publishing component failed: {}", new Object[] {this, entity, description});
-                    if (getConfig(USE_SERVICE_STATE_RUNNING) && getConfig(SET_ON_FIRE_ON_FAILURE) && status != Lifecycle.ON_FIRE) {
-                        weSetItOnFire = true;
-                        entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
-                    }
-                    entity.emit(HASensors.ENTITY_FAILED, new HASensors.FailureDescriptor(entity, description));
-                    lastPublished = LastPublished.FAILED;
-                    currentRecoveryStartTime = null;
-                } else {
-                    long nextAttemptTime = Math.max(serviceFailedStabilizationDelay - currentFailurePeriod, serviceFailedStabilizationDelay - sinceLastUpPeriod);
-                    schedulePublish(nextAttemptTime);
-                }
-            }
-        } else if (healthy) {
-            if (lastPublished == LastPublished.FAILED) {
-                // only publish if consistently up for serviceRecoveredStabilizationDelay
-                long currentRecoveryPeriod = getTimeDiff(now, currentRecoveryStartTime);
-                long sinceLastDownPeriod = getTimeDiff(now, lastDownTime);
-                if (currentRecoveryPeriod > serviceRecoveredStabilizationDelay && sinceLastDownPeriod > serviceRecoveredStabilizationDelay) {
-                    String description = calculatedStatus.getDescription();
-                    LOG.warn("{} health-check for {}, publishing component recovered: {}", new Object[] {this, entity, description});
-                    if (weSetItOnFire) {
-                        if (status == Lifecycle.ON_FIRE) {
-                            entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
-                        }
-                        weSetItOnFire = false;
-                    }
-                    entity.emit(HASensors.ENTITY_RECOVERED, new HASensors.FailureDescriptor(entity, description));
-                    lastPublished = LastPublished.RECOVERED;
-                    currentFailureStartTime = null;
-                } else {
-                    long nextAttemptTime = Math.max(serviceRecoveredStabilizationDelay - currentRecoveryPeriod, serviceRecoveredStabilizationDelay - sinceLastDownPeriod);
-                    schedulePublish(nextAttemptTime);
-                }
-            }
-        }
-    }
-
-    public class CalculatedStatus {
-        public final boolean failed;
-        public final boolean healthy;
-        
-        public CalculatedStatus() {
-            Long lastUpTime = serviceLastUp.get();
-            Boolean isUp = serviceIsUp.get();
-            Lifecycle status = serviceState.get();
-
-            failed = 
-                    (getConfig(USE_SERVICE_STATE_RUNNING) && status == Lifecycle.ON_FIRE && !weSetItOnFire) ||
-                    (Boolean.FALSE.equals(isUp) &&
-                            (getConfig(USE_SERVICE_STATE_RUNNING) ? status == Lifecycle.RUNNING : true) && 
-                            (getConfig(ONLY_REPORT_IF_PREVIOUSLY_UP) ? lastUpTime != null : true));
-            healthy = 
-                    (getConfig(USE_SERVICE_STATE_RUNNING) ? (status == Lifecycle.RUNNING || (weSetItOnFire && status == Lifecycle.ON_FIRE)) : 
-                        true) && 
-                    Boolean.TRUE.equals(isUp);
-        }
-        
-        public String getDescription() {
-            Long lastUpTime = serviceLastUp.get();
-            Boolean isUp = serviceIsUp.get();
-            Lifecycle status = serviceState.get();
-            Duration serviceFailedStabilizationDelay = getServiceFailedStabilizationDelay();
-            Duration serviceRecoveredStabilizationDelay = getServiceRecoveredStabilizationDelay();
-
-            return String.format("location=%s; isUp=%s; status=%s; timeNow=%s; lastReportedUp=%s; lastPublished=%s; "+
-                        "currentFailurePeriod=%s; currentRecoveryPeriod=%s",
-                    entity.getLocations(), 
-                    (isUp != null ? isUp : "<unreported>"),
-                    (status != null ? status : "<unreported>"),
-                    Time.makeDateString(System.currentTimeMillis()),
-                    (lastUpTime != null ? Time.makeDateString(lastUpTime) : "<never>"),
-                    lastPublished,
-                    (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceFailedStabilizationDelay) + ")",
-                    (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceRecoveredStabilizationDelay) + ")");
-        }
-    }
-    
-    private long getTimeDiff(Long recent, Long previous) {
-        return (previous == null) ? recent : (recent - previous);
-    }
-    
-    private String getTimeStringSince(Long time) {
-        return time == null ? null : Time.makeTimeStringRounded(System.currentTimeMillis() - time);
-    }
-    
-    private String getTimeStringSince(AtomicReference<Long> timeRef) {
-        return getTimeStringSince(timeRef.get());
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
index 39be9d4..319b345 100644
--- a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
+++ b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java
@@ -26,22 +26,24 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.SensorEventListener;
 import brooklyn.location.basic.SimulatedLocation;
 import brooklyn.test.Asserts;
-import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.test.entity.TestEntityNoEnrichersImpl;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
 
 import com.google.common.collect.ImmutableList;
 
 public class BrooklynMetricsTest {
 
     private static final long TIMEOUT_MS = 2*1000;
+    private final static int DEFAULT_SUBSCRIPTIONS_PER_ENTITY = 2;
     
     TestApplication app;
     SimulatedLocation loc;
@@ -52,7 +54,6 @@ public class BrooklynMetricsTest {
         loc = new SimulatedLocation();
         app = TestApplication.Factory.newManagedInstanceForTests();
         brooklynMetrics = app.createAndManageChild(EntitySpec.create(BrooklynMetrics.class).configure("updatePeriod", 10L));
-        Entities.manage(brooklynMetrics);
     }
     
     @AfterMethod(alwaysRun=true)
@@ -64,7 +65,7 @@ public class BrooklynMetricsTest {
     public void testInitialBrooklynMetrics() {
         app.start(ImmutableList.of(loc));
 
-        Asserts.succeedsEventually(new Runnable() {
+        Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Runnable() {
             public void run() {
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EFFECTORS_INVOKED), (Long)1L);
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_TASKS_SUBMITTED) > 0);
@@ -72,16 +73,16 @@ public class BrooklynMetricsTest {
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_ACTIVE_TASKS), (Long)0L);
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_PUBLISHED) > 0);
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_DELIVERED), (Long)0L);
-                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)0L);
+                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)(2L*DEFAULT_SUBSCRIPTIONS_PER_ENTITY));
             }});
     }
     
     @Test
     public void testBrooklynMetricsIncremented() {
-        TestEntity e = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity e = app.createAndManageChild(EntitySpec.create(TestEntity.class, TestEntityNoEnrichersImpl.class));
         app.start(ImmutableList.of(loc));
 
-        Asserts.succeedsEventually(new Runnable() {
+        Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Runnable() {
             public void run() {
                 assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EFFECTORS_INVOKED), (Long)2L); // for app and testEntity's start
             }});
@@ -106,11 +107,12 @@ public class BrooklynMetricsTest {
         app.subscribe(e, TestEntity.SEQUENCE, SensorEventListener.NOOP);
         e.setAttribute(TestEntity.SEQUENCE, 1);
         
-        Asserts.succeedsEventually(new Runnable() {
+        Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Runnable() {
             public void run() {
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_PUBLISHED) > eventsPublished);
                 assertTrue(brooklynMetrics.getAttribute(BrooklynMetrics.TOTAL_EVENTS_DELIVERED) > eventsDelivered);
-                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)1L);
+                assertEquals(brooklynMetrics.getAttribute(BrooklynMetrics.NUM_SUBSCRIPTIONS), (Long)
+                    (1L + 2*DEFAULT_SUBSCRIPTIONS_PER_ENTITY));
             }});
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
index 2290a33..7cec2ed 100644
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
+++ b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
@@ -31,7 +31,6 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.AttributeSensor;
@@ -49,7 +48,7 @@ import com.google.common.collect.Lists;
 public class AutoScalerPolicyMetricTest {
     
     private static long TIMEOUT_MS = 10000;
-    private static long SHORT_WAIT_MS = 250;
+    private static long SHORT_WAIT_MS = 50;
     
     private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
     TestApplication app;
@@ -57,7 +56,7 @@ public class AutoScalerPolicyMetricTest {
     
     @BeforeMethod(alwaysRun=true)
     public void before() {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        app = TestApplication.Factory.newManagedInstanceForTests();
         tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
                 .configure("initialSize", 1));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java b/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
index 9d8f427..0cccf14 100644
--- a/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
@@ -31,6 +31,7 @@ import org.testng.annotations.Test;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.rebind.RebindTestFixtureWithApp;
@@ -40,6 +41,7 @@ import brooklyn.event.SensorEventListener;
 import brooklyn.location.Location;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.SimulatedLocation;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.ha.HASensors.FailureDescriptor;
 import brooklyn.test.Asserts;
@@ -131,7 +133,7 @@ public class HaPolicyRebindTest extends RebindTestFixtureWithApp {
     
     @Test
     public void testServiceFailureDetectorWorksAfterRebind() throws Exception {
-        origEntity.addPolicy(PolicySpec.create(ServiceFailureDetector.class));
+        origEntity.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
 
         // rebind
         TestApplication newApp = rebind();
@@ -139,9 +141,10 @@ public class HaPolicyRebindTest extends RebindTestFixtureWithApp {
 
         newApp.getManagementContext().getSubscriptionManager().subscribe(newEntity, HASensors.ENTITY_FAILED, eventListener);
 
-        // stimulate the policy
-        newEntity.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         newEntity.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(newEntity, Lifecycle.RUNNING);
+        
+        // trigger the failure
         newEntity.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(newEntity), null);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
index 3426598..4649b36 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
@@ -31,12 +31,14 @@ import org.testng.annotations.Test;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogicTest;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.Sensor;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 import brooklyn.management.ManagementContext;
-import brooklyn.policy.PolicySpec;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.ha.HASensors.FailureDescriptor;
 import brooklyn.test.Asserts;
 import brooklyn.test.entity.LocalManagementContextForTests;
@@ -49,6 +51,7 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
 
+/** also see more primitive tests in {@link ServiceStateLogicTest} */
 public class ServiceFailureDetectorStabilizationTest {
 
     private static final int TIMEOUT_MS = 10*1000;
@@ -67,8 +70,8 @@ public class ServiceFailureDetectorStabilizationTest {
         managementContext = new LocalManagementContextForTests();
         app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
         e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         
         app.getManagementContext().getSubscriptionManager().subscribe(
                 e1, 
@@ -95,8 +98,8 @@ public class ServiceFailureDetectorStabilizationTest {
     
     @Test(groups="Integration") // Because slow
     public void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws Exception {
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
         Thread.sleep(100);
@@ -109,8 +112,8 @@ public class ServiceFailureDetectorStabilizationTest {
     public void testNotifiedOfFailureAfterStabilisationDelay() throws Exception {
         final int stabilisationDelay = 1000;
         
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
@@ -122,8 +125,8 @@ public class ServiceFailureDetectorStabilizationTest {
     public void testFailuresThenUpDownResetsStabilisationCount() throws Exception {
         final long stabilisationDelay = 1000;
         
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
         assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
@@ -139,8 +142,8 @@ public class ServiceFailureDetectorStabilizationTest {
     public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws Exception {
         final long stabilisationDelay = 1000;
         
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
@@ -157,8 +160,8 @@ public class ServiceFailureDetectorStabilizationTest {
     public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception {
         final int stabilisationDelay = 1000;
         
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
@@ -173,8 +176,8 @@ public class ServiceFailureDetectorStabilizationTest {
     public void testRecoversThenDownUpResetsStabilisationCount() throws Exception {
         final long stabilisationDelay = 1000;
         
-        e1.addPolicy(PolicySpec.create(ServiceFailureDetector.class)
-                .configure(ServiceFailureDetector.SERVICE_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+                .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
         
         e1.setAttribute(TestEntity.SERVICE_UP, false);
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
index 98c28de..5c9dd66 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
@@ -32,11 +32,14 @@ import org.testng.annotations.Test;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.Sensor;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 import brooklyn.management.ManagementContext;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.ha.HASensors.FailureDescriptor;
 import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
@@ -44,19 +47,19 @@ import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
 
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
 
 public class ServiceFailureDetectorTest {
 
-    private static final int TIMEOUT_MS = 10*1000;
+    private static final int TIMEOUT_MS = 1*1000;
 
     private ManagementContext managementContext;
     private TestApplication app;
     private TestEntity e1;
-    private ServiceFailureDetector policy;
     
     private List<SensorEvent<FailureDescriptor>> events;
     private SensorEventListener<FailureDescriptor> eventListener;
@@ -73,6 +76,7 @@ public class ServiceFailureDetectorTest {
         managementContext = new LocalManagementContextForTests();
         app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
         e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        e1.addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
         
         app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_FAILED, eventListener);
         app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_RECOVERED, eventListener);
@@ -86,181 +90,220 @@ public class ServiceFailureDetectorTest {
     @Test(groups="Integration") // Has a 1 second wait
     public void testNotNotifiedOfFailuresForHealthy() throws Exception {
         // Create members before and after the policy is registered, to test both scenarios
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
         assertNoEventsContinually();
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
     }
     
     @Test
     public void testNotifiedOfFailure() throws Exception {
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+        
+        assertEquals(events.size(), 0, "events="+events);
+        
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
         assertEquals(events.size(), 1, "events="+events);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
     
     @Test
-    public void testNotifiedOfFailureOnStateOnFire() throws Exception {
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+    public void testNotifiedOfFailureOnProblem() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+        
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.ON_FIRE);
+        assertEquals(events.size(), 0, "events="+events);
+        
+        ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
         assertEquals(events.size(), 1, "events="+events);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+    }
+    
+    @Test
+    public void testNotifiedOfFailureOnStateOnFire() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
+
+        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        assertEquals(events.size(), 1, "events="+events);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
     
     @Test
     public void testNotifiedOfRecovery() throws Exception {
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
-        // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+        // Make the entity fail
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
 
         // And make the entity recover
         e1.setAttribute(TestEntity.SERVICE_UP, true);
         assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
         assertEquals(events.size(), 2, "events="+events);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
     }
     
-    @Test(groups="Integration") // Has a 1 second wait
-    public void testOnlyReportsFailureIfPreviouslyUp() throws Exception {
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+    @Test
+    public void testNotifiedOfRecoveryFromProblems() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
-        e1.setAttribute(TestEntity.SERVICE_UP, false);
+        ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
 
-        assertNoEventsContinually();
+        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+        // And make the entity recover
+        ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
+        assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+        assertEquals(events.size(), 2, "events="+events);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
     }
     
-    @Test
-    public void testDisablingOnlyReportsFailureIfPreviouslyUp() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+    
+    @Test(groups="Integration") // Has a 1 second wait
+    public void testEmitsEntityFailureOnlyIfPreviouslyUp() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
         // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
 
-        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        assertNoEventsContinually();
     }
     
     @Test
-    public void testSetsOnFireOnFailure() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+    public void testDisablingPreviouslyUpRequirementForEntityFailed() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
         
-        // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
 
-        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
     }
     
     @Test
     public void testDisablingSetsOnFireOnFailure() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("setOnFireOnFailure", false, "onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.PRACTICALLY_FOREVER));
         
         // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
-        EntityTestUtils.assertAttributeEqualsContinually(e1, TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
     }
     
     @Test(groups="Integration") // Has a 1 second wait
-    public void testUsesServiceStateRunning() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+    public void testOnFireAfterDelay() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND));
         
-        // entity no counted as failed, because serviceState != running || onfire
+        // Make the entity fail
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
-        assertNoEventsContinually();
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        Time.sleep(Duration.millis(100));
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
-
-    @Test
-    public void testDisablingUsesServiceStateRunning() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("useServiceStateRunning", false, "onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+    
+    @Test(groups="Integration") // Has a 1 second wait
+    public void testOnFailureDelayFromProblemAndRecover() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND)
+            .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.ONE_SECOND));
         
         // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_UP, false);
+        e1.setAttribute(TestEntity.SERVICE_UP, true);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
 
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
+        
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        Time.sleep(Duration.millis(100));
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // Now recover
+        ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
+        EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        assertEquals(events.size(), 1, "events="+events);
+        
+        assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+        assertEquals(events.size(), 2, "events="+events);
     }
-
+    
     @Test(groups="Integration") // Has a 1 second wait
-    public void testOnlyReportsFailureIfRunning() throws Exception {
-        policy = new ServiceFailureDetector();
-        e1.addPolicy(policy);
+    public void testAttendsToServiceState() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
-        // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.STARTING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
+        // not counted as failed because not expected to be running
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
         assertNoEventsContinually();
     }
-    
-    @Test
-    public void testReportsFailureWhenNotPreviouslyUp() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
-        
-        // Make the entity fail
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
-        e1.setAttribute(TestEntity.SERVICE_UP, false);
 
-        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
-    }
-    
-    @Test
-    public void testReportsFailureWhenNoServiceState() throws Exception {
-        policy = new ServiceFailureDetector(ImmutableMap.of("useServiceStateRunning", false));
-        e1.addPolicy(policy);
+    @Test(groups="Integration") // Has a 1 second wait
+    public void testOnlyReportsFailureIfRunning() throws Exception {
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
         
         // Make the entity fail
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.STARTING);
         e1.setAttribute(TestEntity.SERVICE_UP, true);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
-        assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+        assertNoEventsContinually();
     }
     
     @Test
     public void testReportsFailureWhenAlreadyDownOnRegisteringPolicy() throws Exception {
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.RUNNING);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
         e1.setAttribute(TestEntity.SERVICE_UP, false);
 
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
     }
     
     @Test
     public void testReportsFailureWhenAlreadyOnFireOnRegisteringPolicy() throws Exception {
-        e1.setAttribute(TestEntity.SERVICE_STATE, Lifecycle.ON_FIRE);
+        ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
 
-        policy = new ServiceFailureDetector(ImmutableMap.of("onlyReportIfPreviouslyUp", false));
-        e1.addPolicy(policy);
+        e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+            .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
 
         assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2daedf8/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
index 4b5a0d9..8d1a683 100644
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
+++ b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
@@ -138,7 +138,7 @@ public class ServiceReplacerTest {
         e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
         
         // Expect cluster to go on-fire when fails to start replacement
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
         
         // And expect to have the second failed entity still kicking around as proof (in quarantine)
         Iterable<Entity> members = Iterables.filter(managementContext.getEntityManager().getEntities(), Predicates.instanceOf(FailingEntity.class));


[04/26] git commit: add WhenFunctions which allows `Functionals.when(false).value("F")` approach to building up functions

Posted by he...@apache.org.
add WhenFunctions which allows `Functionals.when(false).value("F")` approach to building up functions


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/45e3035d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/45e3035d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/45e3035d

Branch: refs/heads/master
Commit: 45e3035de648e32487535bf7a8050d5b3926b057
Parents: 238fab7
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 6 15:38:40 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Aug 25 09:32:26 2014 +0100

----------------------------------------------------------------------
 .../java/brooklyn/util/guava/Functionals.java   |  25 +++
 .../java/brooklyn/util/guava/WhenFunctions.java | 190 +++++++++++++++++++
 .../brooklyn/util/guava/FunctionalsTest.java    |  58 ++++++
 .../guava/KeyTransformingLoadingCacheTest.java  |   1 -
 .../brooklyn/util/guava/WhenFunctionsTest.java  |  91 +++++++++
 5 files changed, 364 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/45e3035d/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
index 6eb65e5..caebc7b 100644
--- a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
+++ b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
@@ -18,8 +18,13 @@
  */
 package brooklyn.util.guava;
 
+import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilder;
+import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilderWhenFirst;
+
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
 
 public class Functionals {
 
@@ -38,4 +43,24 @@ public class Functionals {
         return chain(f1, chain(f2, chain(f3, f4)));
     }
 
+    /** @see WhenFunctions */
+    public static <I> WhenFunctionBuilderWhenFirst<I> when(I test) {
+        return WhenFunctions.when(test);
+    }
+    
+    /** @see WhenFunctions */
+    public static <I> WhenFunctionBuilderWhenFirst<I> when(Predicate<I> test) {
+        return WhenFunctions.when(test);
+    }
+
+    /** @see WhenFunctions */
+    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
+        return WhenFunctions.when(test, supplier);
+    }
+    
+    /** @see WhenFunctions */
+    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
+        return WhenFunctions.when(test, value);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/45e3035d/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java b/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
new file mode 100644
index 0000000..50a1e35
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
@@ -0,0 +1,190 @@
+/*
+ * 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 brooklyn.util.guava;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
+/** Utilities for building {@link Function} instances which return specific values
+ * (or {@link Supplier} instances) when certain predicates are satisfied,
+ * tested in order and returning the first matching,
+ * with support for an "else" default value if none are satisfied (null by default). */
+public class WhenFunctions {
+
+    public static <I,O> WhenFunctionBuilder<I,O> newInstance(Class<I> testType, Class<O> returnType) {
+        return new WhenFunctionBuilder<I,O>();
+    }
+    
+    public static <I,O> WhenFunctionBuilderWhenFirst<I> when(Predicate<I> test) {
+        return new WhenFunctionBuilderWhenFirst<I>(test);
+    }
+    public static <I,O> WhenFunctionBuilderWhenFirst<I> when(I test) {
+        return new WhenFunctionBuilderWhenFirst<I>(test);
+    }
+    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
+        return new WhenFunctionBuilder<I,O>().when(test, supplier);
+    }
+    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
+        return new WhenFunctionBuilder<I,O>().when(test, value);
+    }
+    
+    public static class WhenFunction<I,O> implements Function<I,O> {
+        protected final Map<Predicate<I>,Supplier<O>> tests = new LinkedHashMap<Predicate<I>,Supplier<O>>();
+        protected Supplier<O> defaultValue = null;
+        
+        protected WhenFunction(WhenFunction<I,O> input) {
+            this.tests.putAll(input.tests);
+            this.defaultValue = input.defaultValue;
+        }
+
+        protected WhenFunction() {
+        }
+        
+        @Override
+        public O apply(I input) {
+            for (Map.Entry<Predicate<I>,Supplier<O>> test: tests.entrySet()) {
+                if (test.getKey().apply(input)) 
+                    return test.getValue().get();
+            }
+            return defaultValue==null ? null : defaultValue.get();
+        }
+        
+        @Override
+        public String toString() {
+            return "if["+tests+"]"+(defaultValue!=null ? "-else["+defaultValue+"]" : "");
+        }
+    }
+    
+    public static class WhenFunctionBuilder<I,O> extends WhenFunction<I,O> {
+        protected WhenFunctionBuilder() { super(); }
+        protected WhenFunctionBuilder(WhenFunction<I,O> input) { super(input); }
+        
+        public WhenFunction<I,O> build() {
+            return new WhenFunction<I,O>(this);
+        }
+        
+        public WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
+            return when(test).value(supplier);
+        }
+
+        public WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
+            return when(test).value(value);
+        }
+
+        public WhenFunctionBuilderWhen<I,O> when(Predicate<I> test) {
+            return whenUnchecked(test);
+        }
+        public WhenFunctionBuilderWhen<I,O> when(I test) {
+            return whenUnchecked(test);
+        }
+        @SuppressWarnings("unchecked")
+        protected WhenFunctionBuilderWhen<I,O> whenUnchecked(Object test) {
+            if (!(test instanceof Predicate)) {
+                test = Predicates.equalTo(test);
+            }
+            return new WhenFunctionBuilderWhen<I,O>(this, (Predicate<I>)test);
+        }
+
+        public WhenFunctionBuilder<I,O> defaultValue(O defaultValue) {
+            return defaultValueUnchecked(defaultValue);
+        }
+        public WhenFunctionBuilder<I,O> defaultValue(Supplier<O> defaultValue) {
+            return defaultValueUnchecked(defaultValue);
+        }
+        @SuppressWarnings("unchecked")
+        protected WhenFunctionBuilder<I,O> defaultValueUnchecked(Object defaultValue) {
+            if (!(defaultValue instanceof Supplier)) {
+                defaultValue = Suppliers.ofInstance(defaultValue);
+            }
+            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>(this);
+            result.defaultValue = (Supplier<O>)defaultValue;
+            return result;
+        }
+    }
+
+    public static class WhenFunctionBuilderWhen<I,O> {
+        private WhenFunction<I, O> input;
+        private Predicate<I> test;
+        
+        private WhenFunctionBuilderWhen(WhenFunction<I,O> input, Predicate<I> test) {
+            this.input = input;
+            this.test = test;
+        }
+        
+        public WhenFunctionBuilder<I,O> value(O value) {
+            return valueUnchecked(value);
+        }
+        public WhenFunctionBuilder<I,O> value(Supplier<O> value) {
+            return valueUnchecked(value);
+        }
+        @SuppressWarnings("unchecked")
+        protected WhenFunctionBuilder<I,O> valueUnchecked(Object value) {
+            if (!(value instanceof Supplier)) {
+                value = Suppliers.ofInstance(value);
+            }
+            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>(input);
+            result.tests.put(test, (Supplier<O>) value);
+            return result;
+        }
+    }
+
+    public static class WhenFunctionBuilderWhenFirst<I> {
+        private Predicate<I> test;
+        
+        private WhenFunctionBuilderWhenFirst(Predicate<I> test) {
+            whenUnchecked(test);
+        }
+        
+        public WhenFunctionBuilderWhenFirst(I test) {
+            whenUnchecked(test);
+        }
+
+        @SuppressWarnings("unchecked")
+        protected void whenUnchecked(Object test) {
+            if (!(test instanceof Predicate)) {
+                this.test = Predicates.equalTo((I)test);
+            } else {
+                this.test = (Predicate<I>) test;
+            }
+        }
+        
+        public <O> WhenFunctionBuilder<I,O> value(O value) {
+            return valueUnchecked(value);
+        }
+        public <O> WhenFunctionBuilder<I,O> value(Supplier<O> value) {
+            return valueUnchecked(value);
+        }
+        @SuppressWarnings("unchecked")
+        protected <O> WhenFunctionBuilder<I,O> valueUnchecked(Object value) {
+            if (!(value instanceof Supplier)) {
+                value = Suppliers.ofInstance(value);
+            }
+            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>();
+            result.tests.put(test, (Supplier<O>) value);
+            return result;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/45e3035d/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java b/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
new file mode 100644
index 0000000..7e2ab35
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 brooklyn.util.guava;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.math.MathFunctions;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Suppliers;
+
+public class FunctionalsTest {
+
+    @Test
+    public void testChain() {
+        Assert.assertEquals(Functionals.chain(MathFunctions.plus(1), MathFunctions.times(2)).apply(3), (Integer)8);
+        Assert.assertEquals(Functionals.chain(MathFunctions.times(2), MathFunctions.plus(1)).apply(3), (Integer)7);
+    }
+
+    @Test
+    public void testWhen() {
+        WhenFunctionsTest.checkTF(Functionals.when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
+    }
+
+    @Test
+    public void testWhenNoBuilder() {
+        WhenFunctionsTest.checkTF(Functionals.when(false).value("F").when(true).value("T").defaultValue("?"), "?");
+    }
+    
+    @Test
+    public void testWhenPredicateAndSupplier() {
+        WhenFunctionsTest.checkTF(Functionals.when(Predicates.equalTo(false)).value(Suppliers.ofInstance("F"))
+            .when(true).value("T").defaultValue(Suppliers.ofInstance("?")).build(), "?");
+    }
+
+    @Test
+    public void testWhenTwoArgs() {
+        WhenFunctionsTest.checkTF(Functionals.when(Predicates.equalTo(false), "F").when(Predicates.equalTo(true), "T").defaultValue("?").build(), "?");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/45e3035d/utils/common/src/test/java/brooklyn/util/guava/KeyTransformingLoadingCacheTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/KeyTransformingLoadingCacheTest.java b/utils/common/src/test/java/brooklyn/util/guava/KeyTransformingLoadingCacheTest.java
index 1f85b23..ed3c3ad 100644
--- a/utils/common/src/test/java/brooklyn/util/guava/KeyTransformingLoadingCacheTest.java
+++ b/utils/common/src/test/java/brooklyn/util/guava/KeyTransformingLoadingCacheTest.java
@@ -34,7 +34,6 @@ import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 
 public class KeyTransformingLoadingCacheTest {
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/45e3035d/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java b/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
new file mode 100644
index 0000000..2b01b14
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 brooklyn.util.guava;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilder;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.base.Suppliers;
+
+public class WhenFunctionsTest {
+
+    @Test
+    public void testWhen() {
+        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
+    }
+
+    @Test
+    public void testWhenNoBuilder() {
+        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?"), "?");
+    }
+    
+    @Test
+    public void testWhenPredicateAndSupplier() {
+        checkTF(WhenFunctions.when(Predicates.equalTo(false)).value(Suppliers.ofInstance("F"))
+            .when(true).value("T").defaultValue(Suppliers.ofInstance("?")).build(), "?");
+    }
+
+    @Test
+    public void testWhenTwoArgs() {
+        checkTF(WhenFunctions.when(Predicates.equalTo(false), "F").when(Predicates.equalTo(true), "T").defaultValue("?").build(), "?");
+    }
+    
+    @Test
+    public void testWhenNoDefault() {
+        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").build(), null);
+    }
+
+    @Test
+    public void testWhenWithCast() {
+        Function<Boolean, String> f = WhenFunctions.<Boolean,String>when(false).value("F").when(true).value("T").defaultValue("?").build();
+        checkTF(f, "?");
+    }
+
+    @Test
+    public void testWhenWithoutCast() {
+        Function<Boolean, String> f = WhenFunctions.newInstance(Boolean.class, String.class).when(false).value("F").when(true).value("T").defaultValue("?").build();
+        checkTF(f, "?");
+    }
+
+    @Test
+    public void testWhenSupportsReplace() {
+        checkTF(WhenFunctions.when(false).value("false").when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
+    }
+
+    @Test
+    public void testWhenIsImmutableAndSupportsReplace() {
+        WhenFunctionBuilder<Boolean, String> f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?");
+        WhenFunctionBuilder<Boolean, String> f2 = f.when(false).value("false").defaultValue("X");
+        WhenFunctionBuilder<Boolean, String> f3 = f2.when(false).value("F");
+        checkTF(f, "?");
+        checkTF(f3, "X");
+        Assert.assertEquals(f2.apply(false), "false");
+    }
+
+    static void checkTF(Function<Boolean, String> f, Object defaultValue) {
+        Assert.assertEquals(f.apply(true), "T");
+        Assert.assertEquals(f.apply(false), "F");
+        Assert.assertEquals(f.apply(null), defaultValue);
+    }
+    
+}


[11/26] add SERVICE_PROBLEMS and enrichers in ServiceStateLogic to compute state from that. add enrichers to populate PROBLEMS and NOT_UP_INDICATORS from children and members: this removes a lot of ad hoc service-up computations; some quorum up logic e.g

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/messaging/src/main/java/brooklyn/entity/messaging/jms/JMSBrokerImpl.java
----------------------------------------------------------------------
diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/jms/JMSBrokerImpl.java b/software/messaging/src/main/java/brooklyn/entity/messaging/jms/JMSBrokerImpl.java
index 24c570b..fa69522 100644
--- a/software/messaging/src/main/java/brooklyn/entity/messaging/jms/JMSBrokerImpl.java
+++ b/software/messaging/src/main/java/brooklyn/entity/messaging/jms/JMSBrokerImpl.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.messaging.Queue;
 import brooklyn.entity.messaging.Topic;
@@ -132,9 +133,17 @@ public abstract class JMSBrokerImpl<Q extends JMSDestination & Queue, T extends
         addQueue(name, MutableMap.of());
     }
     
+    public void checkStartingOrRunning() {
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.RUNNING) return;
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) return;
+        // TODO this check may be redundant or even inappropriate
+        throw new IllegalStateException("Cannot run against "+this+" in state "+state);
+    }
+
     @Override
     public void addQueue(String name, Map properties) {
-		checkModifiable();
+		checkStartingOrRunning();
         properties.put("name", name);
         queues.put(name, createQueue(properties));
     }
@@ -149,7 +158,7 @@ public abstract class JMSBrokerImpl<Q extends JMSDestination & Queue, T extends
     
     @Override
     public void addTopic(String name, Map properties) {
-		checkModifiable();
+		checkStartingOrRunning();
         properties.put("name", name);
         topics.put(name, createTopic(properties));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperEnsembleImpl.java
----------------------------------------------------------------------
diff --git a/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperEnsembleImpl.java b/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperEnsembleImpl.java
index 520e0af..511bb6b 100644
--- a/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperEnsembleImpl.java
+++ b/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperEnsembleImpl.java
@@ -79,26 +79,23 @@ public class ZooKeeperEnsembleImpl extends DynamicClusterImpl implements ZooKeep
             if (member.getAttribute(ZooKeeperNode.MY_ID) == null) {
                 ((EntityInternal) member).setAttribute(ZooKeeperNode.MY_ID, myId.incrementAndGet());
             }
-            entity.setAttribute(SERVICE_UP, ((ZooKeeperEnsembleImpl)entity).calculateServiceUp());
         }
 
         @Override
         protected void onEntityRemoved(Entity member) {
-            entity.setAttribute(SERVICE_UP, ((ZooKeeperEnsembleImpl)entity).calculateServiceUp());
         }
     };
 
     @Override
-    public synchronized boolean addMember(Entity member) {
-        boolean result = super.addMember(member);
-        setAttribute(SERVICE_UP, calculateServiceUp());
-        return result;
+    protected void initEnrichers() {
+        super.initEnrichers();
+        
     }
-
+    
     @Override
     public void start(Collection<? extends Location> locations) {
         super.start(locations);
-        setAttribute(Startable.SERVICE_UP, calculateServiceUp());
+        
         List<String> zookeeperServers = Lists.newArrayList();
         for (Entity zookeeper : getMembers()) {
             zookeeperServers.add(zookeeper.getAttribute(Attributes.HOSTNAME));
@@ -106,13 +103,4 @@ public class ZooKeeperEnsembleImpl extends DynamicClusterImpl implements ZooKeep
         setAttribute(ZOOKEEPER_SERVERS, zookeeperServers);
     }
 
-    @Override
-    protected boolean calculateServiceUp() {
-        boolean up = false;
-        for (Entity member : getMembers()) {
-            if (Boolean.TRUE.equals(member.getAttribute(SERVICE_UP))) up = true;
-        }
-        return up;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
index 9bf442b..b04e362 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraDatacenterImpl.java
@@ -38,6 +38,7 @@ import brooklyn.entity.basic.DynamicGroup;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicClusterImpl;
@@ -111,7 +112,7 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
                     return ImmutableSet.of();
                 } else if (hasPublishedSeeds) {
                     Set<Entity> currentSeeds = getAttribute(CURRENT_SEEDS);
-                    if (getAttribute(SERVICE_STATE) == Lifecycle.STARTING) {
+                    if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) {
                         if (Sets.intersection(currentSeeds, potentialSeeds).isEmpty()) {
                             log.warn("Cluster {} lost all its seeds while starting! Subsequent failure likely, but changing seeds during startup would risk split-brain: seeds={}", new Object[] {CassandraDatacenterImpl.this, currentSeeds});
                         }
@@ -426,12 +427,6 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
                     .build());
 
         }
-
-        subscribeToMembers(this, SERVICE_UP, new SensorEventListener<Boolean>() {
-            @Override public void onEvent(SensorEvent<Boolean> event) {
-                setAttribute(SERVICE_UP, calculateServiceUp());
-            }
-        });
     }
 
     @Override
@@ -477,19 +472,11 @@ public class CassandraDatacenterImpl extends DynamicClusterImpl implements Cassa
                 setAttribute(THRIFT_PORT, null);
                 setAttribute(CASSANDRA_CLUSTER_NODES, Collections.<String>emptyList());
             }
-            
-            setAttribute(SERVICE_UP, upNode.isPresent() && calculateServiceUp());
+
+            ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyList(this, CASSANDRA_CLUSTER_NODES);
         }
     }
     
-    @Override
-    protected boolean calculateServiceUp() {
-        if (!super.calculateServiceUp()) return false;
-        List<String> nodes = getAttribute(CASSANDRA_CLUSTER_NODES);
-        if (nodes==null || nodes.isEmpty()) return false;
-        return true;
-    }
-    
     /**
      * For tracking our seeds. This gets fiddly! High-level logic is:
      * <ul>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
index 5d3c219..fe0b503 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraFabricImpl.java
@@ -72,8 +72,6 @@ public class CassandraFabricImpl extends DynamicFabricImpl implements CassandraF
     // Mutex for synchronizing during re-size operations
     private final Object mutex = new Object[0];
 
-    private MemberTrackingPolicy policy;
-
     private final Supplier<Set<Entity>> defaultSeedSupplier = new Supplier<Set<Entity>>() {
         @Override public Set<Entity> get() {
             // TODO Remove duplication from CassandraClusterImpl.defaultSeedSupplier
@@ -95,7 +93,7 @@ public class CassandraFabricImpl extends DynamicFabricImpl implements CassandraF
                 
                 if (hasPublishedSeeds) {
                     Set<Entity> currentSeeds = getAttribute(CURRENT_SEEDS);
-                    Lifecycle serviceState = getAttribute(SERVICE_STATE);
+                    Lifecycle serviceState = getAttribute(SERVICE_STATE_ACTUAL);
                     if (serviceState == Lifecycle.STARTING) {
                         if (Sets.intersection(currentSeeds, ImmutableSet.copyOf(Iterables.concat(potentialSeeds.values()))).isEmpty()) {
                             log.warn("Fabric {} lost all its seeds while starting! Subsequent failure likely, but changing seeds during startup would risk split-brain: seeds={}", new Object[] {CassandraFabricImpl.this, currentSeeds});
@@ -211,7 +209,7 @@ public class CassandraFabricImpl extends DynamicFabricImpl implements CassandraF
             setConfig(CassandraDatacenter.SEED_SUPPLIER, getSeedSupplier());
         
         // track members
-        policy = addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
+        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
                 .displayName("Cassandra Fabric Tracker")
                 .configure("group", this));
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
index 11c56d7..b47f133 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
@@ -35,13 +35,13 @@ import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.QuorumCheck;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Startable;
 import brooklyn.event.AttributeSensor;
-import brooklyn.event.SensorEvent;
-import brooklyn.event.SensorEventListener;
 import brooklyn.location.Location;
 import brooklyn.policy.PolicySpec;
 import brooklyn.util.collections.MutableSet;
@@ -129,8 +129,7 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas
         super.start(locations);
 
         connectSensors();
-        connectEnrichers();
-
+        
         //start timeout before adding the servers
         Time.sleep(getConfig(SERVICE_UP_TIME_OUT));
 
@@ -165,7 +164,7 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas
                 //check Repeater.
             }
         } else {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
         }
 
     }
@@ -175,16 +174,6 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas
         super.stop();
     }
 
-    public void connectEnrichers() {
-
-        subscribeToMembers(this, SERVICE_UP, new SensorEventListener<Boolean>() {
-            @Override
-            public void onEvent(SensorEvent<Boolean> event) {
-                setAttribute(SERVICE_UP, calculateServiceUp());
-            }
-        });
-    }
-
     protected void connectSensors() {
         addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
                 .displayName("Controller targets tracker")
@@ -292,13 +281,22 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas
     }
 
     @Override
-    protected boolean calculateServiceUp() {
-        if (!super.calculateServiceUp()) return false;
-        Set<Entity> upNodes = getAttribute(COUCHBASE_CLUSTER_UP_NODES);
-        if (upNodes == null || upNodes.isEmpty() || upNodes.size() < getQuorumSize()) return false;
-        return true;
+    protected void initEnrichers() {
+        if (getConfigRaw(UP_QUORUM_CHECK, false).isAbsent()) {
+            class CouchbaseQuorumCheck implements QuorumCheck {
+                @Override
+                public boolean isQuorate(int sizeHealthy, int totalSize) {
+                    // check members count passed in AND the sensor  
+                    if (sizeHealthy < getQuorumSize()) return false;
+                    Set<Entity> upNodes = getAttribute(COUCHBASE_CLUSTER_UP_NODES);
+                    return (upNodes != null && !upNodes.isEmpty() && upNodes.size() >= getQuorumSize());
+                }
+            }
+            setConfig(UP_QUORUM_CHECK, new CouchbaseQuorumCheck());
+        }
+        super.initEnrichers();
     }
-
+    
     protected void addServers(Set<Entity> serversToAdd) {
         Preconditions.checkNotNull(serversToAdd);
         for (Entity e : serversToAdd) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java
index a149140..4835f72 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java
@@ -18,22 +18,18 @@
  */
 package brooklyn.entity.nosql.couchdb;
 
-import java.util.Collection;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.entity.Entity;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Startable;
-import brooklyn.location.Location;
 
 /**
  * Implementation of {@link CouchDBCluster}.
  */
 public class CouchDBClusterImpl extends DynamicClusterImpl implements CouchDBCluster {
 
+    @SuppressWarnings("unused")
     private static final Logger log = LoggerFactory.getLogger(CouchDBClusterImpl.class);
 
     public CouchDBClusterImpl() {
@@ -52,19 +48,4 @@ public class CouchDBClusterImpl extends DynamicClusterImpl implements CouchDBClu
         return getAttribute(CLUSTER_NAME);
     }
 
-    @Override
-    public void start(Collection<? extends Location> locations) {
-        super.start(locations);
-
-        setAttribute(Startable.SERVICE_UP, calculateServiceUp());
-    }
-
-    @Override
-    protected boolean calculateServiceUp() {
-        boolean up = false;
-        for (Entity member : getMembers()) {
-            if (Boolean.TRUE.equals(member.getAttribute(SERVICE_UP))) up = true;
-        }
-        return up;
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java
index da719dd..a773006 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java
@@ -20,7 +20,6 @@ package brooklyn.entity.nosql.elasticsearch;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
-import brooklyn.entity.Entity;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.proxying.EntitySpec;
 
@@ -29,15 +28,6 @@ public class ElasticSearchClusterImpl extends DynamicClusterImpl implements Elas
     private AtomicInteger nextMemberId = new AtomicInteger(0);
 
     @Override
-    protected boolean calculateServiceUp() {
-        boolean up = false;
-        for (Entity member : getMembers()) {
-            if (Boolean.TRUE.equals(member.getAttribute(SERVICE_UP))) up = true;
-        }
-        return up;
-    }
-    
-    @Override
     protected EntitySpec<?> getMemberSpec() {
         EntitySpec<?> spec = EntitySpec.create(getConfig(MEMBER_SPEC, EntitySpec.create(ElasticSearchNode.class)));
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
index b859765..b1e2393 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.proxying.EntitySpec;
@@ -200,7 +201,7 @@ public class MongoDBReplicaSetImpl extends DynamicClusterImpl implements MongoDB
                 setAttribute(PRIMARY_ENTITY, server);
                 setAttribute(Startable.SERVICE_UP, true);
             } else {
-                setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+                ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             }
         } else {
             if (LOG.isDebugEnabled())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerClusterImpl.java
index 28ee33a..df85c36 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerClusterImpl.java
@@ -38,23 +38,11 @@ public class MongoDBConfigServerClusterImpl extends DynamicClusterImpl implement
         return EntitySpec.create(MongoDBConfigServer.class);
     }
     
-
-    @Override
-    protected boolean calculateServiceUp() {
-        // Number of config servers is fixed at INITIAL_SIZE
-        int requiredMembers = this.getConfig(INITIAL_SIZE);
-        int availableMembers = 0;
-        for (Entity entity : getMembers()) {
-            if (entity instanceof MongoDBConfigServer & entity.getAttribute(SERVICE_UP)) {
-                availableMembers++;
-            }
-        }
-        return availableMembers >= requiredMembers;
-    }
-    
     @Override
     public void start(Collection<? extends Location> locs) {
         super.start(locs);
+        
+        // TODO this should be an enricher
         Iterable<String> memberHostNamesAndPorts = Iterables.transform(getMembers(), new Function<Entity, String>() {
             @Override
             public String apply(Entity entity) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakClusterImpl.java
index 9826300..a75c422 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakClusterImpl.java
@@ -21,6 +21,7 @@ package brooklyn.entity.nosql.riak;
 import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -39,6 +40,8 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.entity.proxying.EntitySpec;
@@ -80,7 +83,7 @@ public class RiakClusterImpl extends DynamicClusterImpl implements RiakCluster {
             setAttribute(IS_CLUSTER_INIT, true);
         } else {
             log.warn("No Riak Nodes are found on the cluster: {}. Initialization Failed", getId());
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
         }
     }
 
@@ -100,11 +103,11 @@ public class RiakClusterImpl extends DynamicClusterImpl implements RiakCluster {
         if (log.isTraceEnabled()) log.trace("For {}, considering membership of {} which is in locations {}",
                 new Object[]{this, member, member.getLocations()});
 
+        Map<Entity, String> nodes = getAttribute(RIAK_CLUSTER_NODES);
         if (belongsInServerPool(member)) {
             // TODO can we discover the nodes by asking the riak cluster, rather than assuming what we add will be in there?
             // TODO and can we do join as part of node starting?
 
-            Map<Entity, String> nodes = getAttribute(RIAK_CLUSTER_NODES);
             if (nodes == null) nodes = Maps.newLinkedHashMap();
             String riakName = getRiakName(member);
 
@@ -151,7 +154,6 @@ public class RiakClusterImpl extends DynamicClusterImpl implements RiakCluster {
                 }
             }
         } else {
-            Map<Entity, String> nodes = getAttribute(RIAK_CLUSTER_NODES);
             if (nodes != null && nodes.containsKey(member)) {
                 final Entity memberToBeRemoved = member;
 
@@ -172,6 +174,8 @@ public class RiakClusterImpl extends DynamicClusterImpl implements RiakCluster {
 
             }
         }
+        
+        ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyMap(this, RIAK_CLUSTER_NODES);
         if (log.isTraceEnabled()) log.trace("Done {} checkEntity {}", this, member);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
index 9e0b151..d405fab 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
@@ -325,7 +325,7 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
     @Override
     protected void postRebind() {
         super.postRebind();
-        Lifecycle state = getAttribute(SERVICE_STATE);
+        Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL);
         if (state != null && state == Lifecycle.RUNNING) {
             isActive = true;
             update();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancerClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancerClusterImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancerClusterImpl.java
index 8c4f389..bc8f88d 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancerClusterImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancerClusterImpl.java
@@ -18,14 +18,10 @@
  */
 package brooklyn.entity.proxy;
 
-import java.util.Collection;
 import java.util.Map;
 
 import brooklyn.entity.Entity;
-import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.group.DynamicClusterImpl;
-import brooklyn.location.Location;
-import brooklyn.policy.PolicySpec;
 
 /**
  * A cluster of load balancers, where configuring the cluster (through the LoadBalancer interface)
@@ -49,33 +45,6 @@ public class LoadBalancerClusterImpl extends DynamicClusterImpl implements LoadB
         super();
     }
 
-    @Override
-    public void start(Collection<? extends Location> locs) {
-        super.start(locs);
-        
-        // TODO Is there a race here, where (dispite super.stop() calling policy.suspend),
-        // this could still be executing setAttribute(true) and hence incorrectly leave
-        // the cluster in a service_up==true state after stop() returns?
-        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
-                .configure("group", this));
-    }
-
-    public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
-        @Override protected void onEntityEvent(EventType type, Entity member) {
-            entity.setAttribute(SERVICE_UP, ((LoadBalancerClusterImpl)entity).calculateServiceUp());
-        }
-    }
-
-    /**
-     * Up if running and has at least one load-balancer in the cluster.
-     * 
-     * TODO Could also look at service_up of each load-balancer, but currently does not do that.
-     */
-    @Override
-    protected boolean calculateServiceUp() {
-        return super.calculateServiceUp() && getCurrentSize() > 0;
-    }
-
     /* NOTE The following methods come from {@link LoadBalancer} but are probably safe to ignore */
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
index f482422..3e36ada 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
@@ -66,7 +66,7 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
     public void reload() {
         NginxSshDriver driver = (NginxSshDriver)getDriver();
         if (driver==null) {
-            Lifecycle state = getAttribute(NginxController.SERVICE_STATE);
+            Lifecycle state = getAttribute(NginxController.SERVICE_STATE_ACTUAL);
             throw new IllegalStateException("Cannot reload (no driver instance; stopped? (state="+state+")");
         }
 
@@ -181,7 +181,7 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
         if (driver==null) {
             if (LOG.isDebugEnabled())
                 LOG.debug("No driver for {}, so not deploying archive (is entity stopping? state={})",
-                        this, getAttribute(NginxController.SERVICE_STATE));
+                        this, getAttribute(NginxController.SERVICE_STATE_ACTUAL));
             return;
         }
 
@@ -248,7 +248,7 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
         if (driver==null) {
             if (LOG.isDebugEnabled())
                 LOG.debug("No driver for {}, so not generating config file (is entity stopping? state={})",
-                        this, getAttribute(NginxController.SERVICE_STATE));
+                        this, getAttribute(NginxController.SERVICE_STATE_ACTUAL));
             return null;
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
index 81e74fd..8a3f2c6 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
@@ -380,8 +380,8 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
         // calling waitForEntityStart()), we can guarantee that the start-thread's call to update will happen after
         // this call to reload. So we this can be a no-op, and just rely on that subsequent call to update.
 
-        Lifecycle lifecycle = entity.getAttribute(NginxController.SERVICE_STATE);
         if (!isRunning()) {
+            Lifecycle lifecycle = entity.getAttribute(NginxController.SERVICE_STATE_ACTUAL);
             log.debug("Ignoring reload of nginx "+entity+", because service is not running (state "+lifecycle+")");
             return;
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java b/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java
index 0c263d9..72b2bb6 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java
@@ -93,7 +93,7 @@ public interface ControlledDynamicWebAppCluster extends DynamicGroup, Entity, St
 
     public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
 
-    public static final AttributeSensor<Lifecycle> SERVICE_STATE = Attributes.SERVICE_STATE;
+    public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     
     public LoadBalancer getController();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterImpl.java
index 47e7ec9..acff382 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterImpl.java
@@ -19,10 +19,9 @@
 package brooklyn.entity.webapp;
 
 import java.util.Collection;
-import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
+import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,6 +34,9 @@ import brooklyn.entity.basic.DynamicGroupImpl;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.QuorumCheck;
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.proxy.LoadBalancer;
 import brooklyn.entity.proxy.nginx.NginxController;
 import brooklyn.entity.proxying.EntitySpec;
@@ -71,7 +73,6 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
     @Deprecated
     public ControlledDynamicWebAppClusterImpl(Map<?,?> flags, Entity parent) {
         super(flags, parent);
-        setAttribute(SERVICE_UP, false);
     }
 
     @Override
@@ -137,6 +138,14 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
         
         doBind();
     }
+
+    @Override
+    protected void initEnrichers() {
+        if (getConfigRaw(UP_QUORUM_CHECK, false).isAbsent()) {
+            setConfig(UP_QUORUM_CHECK, QuorumChecks.newInstance(2, 1.0, false));
+        }
+        super.initEnrichers();
+    }
     
     @Override
     public void rebind() {
@@ -174,7 +183,7 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
     
     @Override
     public void start(Collection<? extends Location> locations) {
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
 
         try {
             if (isLegacyConstruction()) {
@@ -202,13 +211,9 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
             // (will happen asynchronously as members come online, but we want to force it to happen)
             getController().update();
 
-            setAttribute(SERVICE_UP, getCluster().getAttribute(SERVICE_UP));
-            setAttribute(SERVICE_STATE, Lifecycle.RUNNING);
-        } catch (InterruptedException e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
-            throw Exceptions.propagate(e);
-        } catch (ExecutionException e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+        } catch (Exception e) {
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         } finally {
             connectSensors();
@@ -217,7 +222,7 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
 
     @Override
     public void stop() {
-        setAttribute(SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
 
         try {
             List<Startable> tostop = Lists.newArrayList();
@@ -228,10 +233,9 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
 
             clearLocations();
 
-            setAttribute(SERVICE_STATE, Lifecycle.STOPPED);
-            setAttribute(SERVICE_UP, false);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         } catch (Exception e) {
-            setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(e);
         }
     }
@@ -246,8 +250,9 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
     }
     
     void connectSensors() {
+        // FIXME no longer needed
         addEnricher(Enrichers.builder()
-                .propagatingAllBut(SERVICE_STATE, SERVICE_UP, ROOT_URL, GROUP_MEMBERS, GROUP_SIZE)
+                .propagatingAllButUsualAnd(ROOT_URL, GROUP_MEMBERS, GROUP_SIZE)
                 .from(getCluster())
                 .build());
         addEnricher(Enrichers.builder()
@@ -255,40 +260,6 @@ public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl impleme
                 .propagating(LoadBalancer.HOSTNAME, Attributes.ADDRESS, ROOT_URL)
                 .from(getController())
                 .build());
-
-        SensorEventListener<Boolean> updateServiceUp = new SensorEventListener<Boolean>() {
-            @Override
-            public void onEvent(SensorEvent<Boolean> event) {
-                setAttribute(SERVICE_UP, calculateServiceUp());
-            }
-        };
-        SensorEventListener<Lifecycle> updateServiceState = new SensorEventListener<Lifecycle>() {
-            @Override
-            public void onEvent(SensorEvent<Lifecycle> event) {
-                setAttribute(SERVICE_STATE, calculateServiceState());
-            }
-        };
-        
-        subscribe(getCluster(), SERVICE_STATE, updateServiceState);
-        subscribe(getController(), SERVICE_STATE, updateServiceState);
-        subscribe(getCluster(), SERVICE_UP, updateServiceUp);
-        subscribe(getController(), SERVICE_UP, updateServiceUp);
-    }
-
-    protected Lifecycle calculateServiceState() {
-        Lifecycle currentState = getAttribute(SERVICE_STATE);
-        if (EnumSet.of(Lifecycle.ON_FIRE, Lifecycle.RUNNING).contains(currentState)) {
-            if (getCluster().getAttribute(SERVICE_STATE) == Lifecycle.ON_FIRE) currentState = Lifecycle.ON_FIRE;
-            if (getController().getAttribute(SERVICE_STATE) == Lifecycle.ON_FIRE) currentState = Lifecycle.ON_FIRE;
-        }
-        return currentState;
-    }
-
-    /**
-     * Default impl is to be up when running, and !up otherwise.
-     */
-    protected boolean calculateServiceUp() {
-        return getAttribute(SERVICE_STATE) == Lifecycle.RUNNING;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/webapp/DynamicWebAppClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/DynamicWebAppClusterImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/DynamicWebAppClusterImpl.java
index bfdc666..951e3b4 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/DynamicWebAppClusterImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/DynamicWebAppClusterImpl.java
@@ -37,8 +37,6 @@ import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.event.AttributeSensor;
-import brooklyn.event.SensorEvent;
-import brooklyn.event.SensorEventListener;
 import brooklyn.management.Task;
 import brooklyn.management.TaskAdaptable;
 import brooklyn.util.collections.MutableMap;
@@ -123,45 +121,6 @@ public class DynamicWebAppClusterImpl extends DynamicClusterImpl implements Dyna
         }
     }
     
-    public void onManagementStarted() {
-        super.onManagementStarted();
-        
-        subscribeToMembers(this, SERVICE_UP, new SensorEventListener<Boolean>() {
-            @Override public void onEvent(SensorEvent<Boolean> event) {
-                if (!isRebinding()) {
-                    setAttribute(SERVICE_UP, calculateServiceUp());
-                }
-            }
-        });
-    }
-
-    @Override
-    public synchronized boolean addMember(Entity member) {
-        boolean result = super.addMember(member);
-        if (!isRebinding()) {
-            setAttribute(SERVICE_UP, calculateServiceUp());
-        }
-        return result;
-    }
-    
-    @Override
-    public synchronized boolean removeMember(Entity member) {
-        boolean result = super.removeMember(member);
-        if (!isRebinding()) {
-            setAttribute(SERVICE_UP, calculateServiceUp());
-        }
-        return result;
-    }
-
-    @Override    
-    protected boolean calculateServiceUp() {
-        boolean up = false;
-        for (Entity member : getMembers()) {
-            if (Boolean.TRUE.equals(member.getAttribute(SERVICE_UP))) up = true;
-        }
-        return up;
-    }
-    
     // TODO this will probably be useful elsewhere ... but where to put it?
     // TODO add support for this in DependentConfiguration (see TODO there)
     /** Waits for the given target to report service up, then runs the given task

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
index ce8fdec..9d2c7ec 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
@@ -130,7 +130,7 @@ public class Jetty6ServerImpl extends JavaWebAppSoftwareProcessImpl implements J
     
     protected void restartIfRunning() {
         // TODO for now we simply restart jetty to achieve "hot deployment"; should use the config mechanisms
-        Lifecycle serviceState = getAttribute(SERVICE_STATE);
+        Lifecycle serviceState = getAttribute(SERVICE_STATE_ACTUAL);
         if (serviceState == Lifecycle.RUNNING)
             restart();
         // may need a restart also if deploy effector is done in parallel to starting

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java
index 74e23b5..f731eca 100644
--- a/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java
@@ -50,6 +50,7 @@ import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
 import brooklyn.management.ManagementContext;
 import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
 import brooklyn.test.WebAppMonitor;
 
 import com.google.common.base.Predicates;
@@ -134,7 +135,7 @@ public class NginxRebindIntegrationTest extends RebindTestFixtureWithApp {
 
         assertEquals(newNginx.getConfigFile(), origConfigFile);
         
-        assertEquals(newNginx.getAttribute(NginxController.SERVICE_STATE), Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(newNginx, NginxController.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
         assertEquals(newNginx.getAttribute(NginxController.PROXY_HTTP_PORT), (Integer)nginxPort);
         assertEquals(newNginx.getAttribute(NginxController.ROOT_URL), rootUrl);
         assertEquals(newNginx.getAttribute(NginxController.PROXY_HTTP_PORT), origNginx.getAttribute(NginxController.PROXY_HTTP_PORT));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
index d5d960b..df486aa 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
@@ -317,6 +317,6 @@ public class ControlledDynamicWebAppClusterTest {
         Entities.unmanage(controller);
         
         cluster.stop();
-        EntityTestUtils.assertAttributeEquals(cluster, ControlledDynamicWebAppCluster.SERVICE_STATE, Lifecycle.STOPPED);
+        EntityTestUtils.assertAttributeEquals(cluster, ControlledDynamicWebAppCluster.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/usage/rest-server/src/main/java/brooklyn/rest/transform/ApplicationTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/transform/ApplicationTransformer.java b/usage/rest-server/src/main/java/brooklyn/rest/transform/ApplicationTransformer.java
index 7067898..88e8da4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/transform/ApplicationTransformer.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/transform/ApplicationTransformer.java
@@ -57,7 +57,7 @@ public class ApplicationTransformer {
 
     public static Status statusFromApplication(Application application) {
         if (application == null) return UNKNOWN;
-        Lifecycle state = application.getAttribute(Attributes.SERVICE_STATE);
+        Lifecycle state = application.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
         if (state != null) return statusFromLifecycle(state);
         Boolean up = application.getAttribute(Startable.SERVICE_UP);
         if (up != null && up.booleanValue()) return RUNNING;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
index a93c551..ef66ded 100644
--- a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
+++ b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
@@ -23,6 +23,7 @@ import brooklyn.util.guava.IfFunctions.IfFunctionBuilderApplyingFirst;
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
 
 public class Functionals {
 
@@ -80,4 +81,34 @@ public class Functionals {
         }
     }
 
+    /** like guava {@link Functions#forSupplier(Supplier)} but parametrises the input generic type */
+    public static <I,O> Function<I,O> function(final Supplier<O> supplier) {
+        class SupplierAsFunction implements Function<I,O> {
+            @Override public O apply(I input) {
+                return supplier.get();
+            }
+        }
+        return new SupplierAsFunction();
+    }
+
+    public static <I> Function<I,Void> function(final Runnable runnable) {
+        class RunnableAsFunction implements Function<I,Void> {
+            @Override public Void apply(I input) {
+                runnable.run();
+                return null;
+            }
+        }
+        return new RunnableAsFunction();
+    }
+
+    public static Runnable runnable(final Supplier<?> supplier) {
+        class SupplierAsRunnable implements Runnable {
+            @Override
+            public void run() {
+                supplier.get();
+            }
+        }
+        return new SupplierAsRunnable();
+    }
+
 }


[08/26] git commit: rename WhenFunctions class to IfFunctions, and tidies/cleanups there and in enrichers

Posted by he...@apache.org.
rename WhenFunctions class to IfFunctions, and tidies/cleanups there and in enrichers


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/bc1d19f7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/bc1d19f7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/bc1d19f7

Branch: refs/heads/master
Commit: bc1d19f755bc32a5ed5ec84ba50198fa39ba47bb
Parents: c1ebb8f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 12 10:50:46 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:07:49 2014 -0400

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/EntityLocal.java |   1 -
 .../enricher/basic/AbstractEnricher.java        |  20 +-
 .../basic/AbstractTypeTransformingEnricher.java |   1 +
 .../brooklyn/enricher/basic/Aggregator.java     |  12 +-
 .../java/brooklyn/enricher/basic/Combiner.java  |  10 +-
 .../brooklyn/enricher/basic/Propagator.java     |   4 +-
 .../basic/SensorPropagatingEnricher.java        |   1 +
 .../brooklyn/enricher/basic/Transformer.java    |  11 +-
 .../entity/basic/ServiceStatusLogic.java        |  31 ++-
 .../policy/basic/AbstractEntityAdjunct.java     |  16 +-
 .../java/brooklyn/enricher/EnrichersTest.java   |   4 +-
 .../entity/basic/SoftwareProcessImpl.java       |  26 ++-
 .../basic/lifecycle/ScriptHelperTest.java       |  36 +++-
 .../entity/webapp/jboss/JBoss7ServerImpl.java   |  10 +-
 .../util/collections/CollectionFunctionals.java |  97 +++++++---
 .../java/brooklyn/util/guava/Functionals.java   |  53 ++++--
 .../java/brooklyn/util/guava/IfFunctions.java   | 158 +++++++++++++++
 .../java/brooklyn/util/guava/WhenFunctions.java | 190 -------------------
 .../collections/CollectionFunctionalsTest.java  |  51 +++++
 .../brooklyn/util/guava/FunctionalsTest.java    |  18 +-
 .../brooklyn/util/guava/IfFunctionsTest.java    | 101 ++++++++++
 .../brooklyn/util/guava/WhenFunctionsTest.java  |  91 ---------
 22 files changed, 536 insertions(+), 406 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/api/src/main/java/brooklyn/entity/basic/EntityLocal.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/basic/EntityLocal.java b/api/src/main/java/brooklyn/entity/basic/EntityLocal.java
index c8a18b5..b38a3d8 100644
--- a/api/src/main/java/brooklyn/entity/basic/EntityLocal.java
+++ b/api/src/main/java/brooklyn/entity/basic/EntityLocal.java
@@ -35,7 +35,6 @@ import brooklyn.management.SubscriptionManager;
 import brooklyn.management.Task;
 
 import com.google.common.annotations.Beta;
-import com.google.common.base.Optional;
 
 /** 
  * Extended Entity interface for use in places where the caller should have certain privileges,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
index 52a924a..d462a0c 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
@@ -24,6 +24,8 @@ import java.util.Map;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.rebind.BasicEnricherRebindSupport;
 import brooklyn.entity.rebind.RebindSupport;
@@ -33,6 +35,7 @@ import brooklyn.mementos.EnricherMemento;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherType;
 import brooklyn.policy.basic.AbstractEntityAdjunct;
+import brooklyn.util.flags.TypeCoercions;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.Maps;
@@ -84,20 +87,27 @@ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements
     }
 
     @Override
-    protected <T> void emit(Sensor<T> sensor, T val) {
+    protected <T> void emit(Sensor<T> sensor, Object val) {
         checkState(entity != null, "entity must first be set");
+        if (val == Entities.UNCHANGED) {
+            return;
+        }
+        if (val == Entities.REMOVE) {
+            ((EntityInternal)entity).removeAttribute((AttributeSensor<T>) sensor);
+            return;
+        }
         
+        T newVal = TypeCoercions.coerce(val, sensor.getTypeToken());
         if (sensor instanceof AttributeSensor) {
             if (Boolean.TRUE.equals(suppressDuplicates)) {
                 T oldValue = entity.getAttribute((AttributeSensor<T>)sensor);
-                if (Objects.equal(oldValue, val))
+                if (Objects.equal(oldValue, newVal))
                     return;
             }
-            entity.setAttribute((AttributeSensor<T>)sensor, val);
+            entity.setAttribute((AttributeSensor<T>)sensor, newVal);
         } else { 
-            entity.emit(sensor, val);
+            entity.emit(sensor, newVal);
         }
-
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java
index 05f96cd..27eac93 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java
@@ -51,6 +51,7 @@ public abstract class AbstractTypeTransformingEnricher<T,U> extends AbstractEnri
         this.target = target;
     }
     
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public void setEntity(EntityLocal entity) {
         super.setEntity(entity);
         if (producer==null) producer = entity;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/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 f5d503f..fed403e 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -35,7 +35,6 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
 import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.trait.Changeable;
 import brooklyn.event.AttributeSensor;
@@ -45,7 +44,6 @@ import brooklyn.event.SensorEventListener;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.flags.TypeCoercions;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -53,6 +51,7 @@ import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
 
+@SuppressWarnings("serial")
 public class Aggregator<T,U> extends AbstractEnricher implements SensorEventListener<T> {
 
     private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class);
@@ -98,7 +97,7 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
     public Aggregator() {
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked" })
     @Override
     public void setEntity(EntityLocal entity) {
         super.setEntity(entity);
@@ -237,12 +236,7 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
      */
     protected void onUpdated() {
         try {
-            Object v = compute();
-            if (v == Entities.UNCHANGED) {
-                // nothing
-            } else {
-                emit(targetSensor, TypeCoercions.coerce(v, targetSensor.getTypeToken()));
-            }
+            emit(targetSensor, compute());
         } catch (Throwable t) {
             LOG.warn("Error calculating and setting aggregate for enricher "+this, t);
             throw Exceptions.propagate(t);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/Combiner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Combiner.java b/core/src/main/java/brooklyn/enricher/basic/Combiner.java
index a49cf87..6876be2 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Combiner.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Combiner.java
@@ -33,7 +33,6 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.Sensor;
@@ -42,7 +41,6 @@ import brooklyn.event.SensorEventListener;
 import brooklyn.event.basic.BasicSensorEvent;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.flags.TypeCoercions;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -50,6 +48,7 @@ import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
 
+@SuppressWarnings("serial")
 public class Combiner<T,U> extends AbstractEnricher implements SensorEventListener<T> {
 
     private static final Logger LOG = LoggerFactory.getLogger(Combiner.class);
@@ -121,12 +120,7 @@ public class Combiner<T,U> extends AbstractEnricher implements SensorEventListen
      */
     protected void onUpdated() {
         try {
-            Object v = compute();
-            if (v == Entities.UNCHANGED) {
-                // nothing
-            } else {
-                emit(targetSensor, TypeCoercions.coerce(v, targetSensor.getTypeToken()));
-            }
+            emit(targetSensor, compute());
         } catch (Throwable t) {
             LOG.warn("Error calculating and setting combination for enricher "+this, t);
             throw Exceptions.propagate(t);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/Propagator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Propagator.java b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
index 7e06aa7..267ba88 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Propagator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
@@ -43,9 +43,9 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
+@SuppressWarnings("serial")
 public class Propagator extends AbstractEnricher implements SensorEventListener<Object> {
 
-    @SuppressWarnings("unused")
     private static final Logger LOG = LoggerFactory.getLogger(Propagator.class);
 
     @SetFromFlag("producer")
@@ -126,6 +126,7 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
         emitAllAttributes();
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public void onEvent(SensorEvent<Object> event) {
         // propagate upwards
@@ -147,6 +148,7 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
         emitAllAttributes(false);
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public void emitAllAttributes(boolean includeNullValues) {
         Iterable<? extends Sensor<?>> sensorsToPopulate = propagatingAll 
                 ? Iterables.filter(producer.getEntityType().getSensors(), sensorFilter)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java b/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java
index dfaa60f..8e863f7 100644
--- a/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java
+++ b/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java
@@ -158,6 +158,7 @@ public class SensorPropagatingEnricher extends AbstractEnricher implements Senso
         emitAllAttributes(false);
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public void emitAllAttributes(boolean includeNullValues) {
         for (Sensor s: sensors) {
             if (s instanceof AttributeSensor) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/enricher/basic/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
index 6877ec9..c517e88 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
@@ -26,14 +26,12 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.Sensor;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 import brooklyn.event.basic.BasicSensorEvent;
-import brooklyn.util.flags.TypeCoercions;
 
 import com.google.common.base.Function;
 import com.google.common.reflect.TypeToken;
@@ -104,14 +102,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
 
     @Override
     public void onEvent(SensorEvent<T> event) {
-        Object v = compute(event);
-        if (v == Entities.UNCHANGED) {
-            // nothing
-        } else {
-            U newValue = TypeCoercions.coerce(v, targetSensor.getTypeToken());
-//            oldValue = entity.
-            emit(targetSensor, newValue);
-        }
+        emit(targetSensor, compute(event));
     }
 
     protected Object compute(SensorEvent<T> event) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
index 056334c..8f4c6b4 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java
@@ -21,11 +21,15 @@ package brooklyn.entity.basic;
 import java.util.Map;
 
 import brooklyn.enricher.Enrichers;
+import brooklyn.enricher.basic.UpdatingMap;
 import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
 import brooklyn.policy.EnricherSpec;
 import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Functionals;
 
+import com.google.common.base.Function;
 import com.google.common.base.Functions;
 
 /** Logic, sensors and enrichers, and conveniences, for computing service status */ 
@@ -34,13 +38,28 @@ public class ServiceStatusLogic {
     public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP;
     public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS;
     
-    public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
-        return Enrichers.builder()
-            .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
-            .computing( Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)) )
-            .uniqueTag("service.isUp if no service.notUp.indicators")
-            .build();
+    private ServiceStatusLogic() {}
+    
+    public static class ServiceNotUpLogic {
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public static final EnricherSpec<?> newEnricherForServiceUpIfNoNotUpIndicators() {
+            return Enrichers.builder()
+                .transforming(SERVICE_NOT_UP_INDICATORS).publishing(Attributes.SERVICE_UP)
+                .computing( /* cast hacks to support removing */ (Function)
+                    Functionals.<Map<String,?>>
+                        ifNotEquals(null).<Object>apply(Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0)))
+                        .defaultValue(Entities.REMOVE) )
+                .uniqueTag("service.isUp if no service.notUp.indicators")
+                .build();
+        }
+        
+        /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the 
+         * {@link UpdatingMap} enricher for the given sensor reported this value (including {@link Entities#REMOVE}) */
+        public static void updateMapFromSensor(EntityLocal entity, Sensor<?> sensor, Object value) {
+            updateMapSensor(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName(), value);
+        }
     }
+    
 
     @SuppressWarnings("unchecked")
     public static <TKey,TVal> void updateMapSensor(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 126968e..68f8162 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -39,6 +39,7 @@ import brooklyn.config.ConfigMap;
 import brooklyn.enricher.basic.AbstractEnricher;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
+import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.trait.Configurable;
@@ -240,12 +241,21 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
     }
     
     /** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */
-    protected <T> void emit(Sensor<T> sensor, T val) {
+    protected <T> void emit(Sensor<T> sensor, Object val) {
         checkState(entity != null, "entity must first be set");
+        if (val == Entities.UNCHANGED) {
+            return;
+        }
+        if (val == Entities.REMOVE) {
+            ((EntityInternal)entity).removeAttribute((AttributeSensor<T>) sensor);
+            return;
+        }
+        
+        T newVal = TypeCoercions.coerce(val, sensor.getTypeToken());
         if (sensor instanceof AttributeSensor) {
-            entity.setAttribute((AttributeSensor<T>)sensor, val);
+            entity.setAttribute((AttributeSensor<T>)sensor, newVal);
         } else { 
-            entity.emit(sensor, val);
+            entity.emit(sensor, newVal);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index 4257c36..66d1bdd 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -396,7 +396,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.addEnricher(Enrichers.builder()
                 .updatingMap(MAP1)
                 .from(LONG1)
-                .computing(Functionals.when(-1L).value("-1 is not allowed"))
+                .computing(Functionals.ifEquals(-1L).value("-1 is not allowed"))
                 .build());
         
         doUpdatingMapChecks(MAP1);
@@ -408,7 +408,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.addEnricher(Enrichers.builder()
                 .updatingMap((AttributeSensor)MAP2)
                 .from(LONG1)
-                .computing(Functionals.when(-1L).value("-1 is not allowed"))
+                .computing(Functionals.ifEquals(-1L).value("-1 is not allowed"))
                 .build());
         
         doUpdatingMapChecks(MAP2);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index e376bee..25c23df 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.ConfigKey;
 import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ServiceStatusLogic.ServiceNotUpLogic;
 import brooklyn.entity.drivers.DriverDependentEntity;
 import brooklyn.entity.drivers.EntityDriverManager;
 import brooklyn.event.feed.function.FunctionFeed;
@@ -116,6 +117,18 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
         return Iterables.get(Iterables.filter(getLocations(), MachineLocation.class), 0, null);
     }
     
+    @Override
+    public void init() {
+        super.init();
+        
+        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+            .from(SERVICE_PROCESS_IS_RUNNING)
+            .computing(Functionals.ifNotEquals(true).value("The software process for this entity does not appear to be running"))
+            .build());
+        
+        addEnricher(ServiceNotUpLogic.newEnricherForServiceUpIfNoNotUpIndicators());
+    }
+    
   	/**
   	 * Called before driver.start; guarantees the driver will exist, and locations will have been set.
   	 */
@@ -161,14 +174,6 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
                             }
                         }))
                 .build();
-
-        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
-            .from(SERVICE_PROCESS_IS_RUNNING)
-            .computing(Functionals.when(false).value("Process not running (according to driver checkRunning)")
-                .when((Boolean)null).value("Process not running (no data for "+SERVICE_PROCESS_IS_RUNNING.getName()+")") )
-            .build());
-        
-        addEnricher(ServiceStatusLogic.newEnricherForServiceUpIfNoNotUpIndicators());
     }
 
     /**
@@ -180,7 +185,10 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
      */
     protected void disconnectServiceUpIsRunning() {
         if (serviceProcessIsRunning != null) serviceProcessIsRunning.stop();
-        ServiceStatusLogic.updateMapSensor(this, Attributes.SERVICE_NOT_UP_INDICATORS, SERVICE_PROCESS_IS_RUNNING.getName(), "Disabled checking whether service process is running");
+        // set null so the SERVICE_UP enricher runs (possibly removing it), then remove so everything is removed
+        // TODO race because the is-running check may be mid-task
+        setAttribute(SERVICE_PROCESS_IS_RUNNING, null);
+        removeAttribute(SERVICE_PROCESS_IS_RUNNING);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
index 9894a9e..6edd9a9 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.TestException;
 import org.testng.annotations.BeforeMethod;
@@ -31,8 +33,11 @@ import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.SoftwareProcessEntityTest;
+import brooklyn.entity.basic.SoftwareProcessEntityTest.MyService;
 import brooklyn.entity.basic.SoftwareProcessEntityTest.MyServiceImpl;
+import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Startable;
 import brooklyn.event.feed.function.FunctionFeed;
 import brooklyn.event.feed.function.FunctionPollConfig;
@@ -47,6 +52,8 @@ import com.google.common.collect.ImmutableList;
 
 public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
     
+    private static final Logger log = LoggerFactory.getLogger(ScriptHelperTest.class);
+    
     private SshMachineLocation machine;
     private FixedListMachineProvisioningLocation<SshMachineLocation> loc;
     boolean shouldFail = false;
@@ -65,34 +72,47 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
 
     @Test
     public void testCheckRunningForcesInessential() {
-        MyServiceInessentialDriverImpl entity = new MyServiceInessentialDriverImpl(app);
-        Entities.manage(entity);
+        MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class, MyServiceInessentialDriverImpl.class));
+        
+        // is set false on mgmt starting (probably shouldn't be though)
+        Assert.assertFalse(entity.getAttribute(Startable.SERVICE_UP));
         
         entity.start(ImmutableList.of(loc));
         SimulatedInessentialIsRunningDriver driver = (SimulatedInessentialIsRunningDriver) entity.getDriver();
         Assert.assertTrue(driver.isRunning());
+        // currently, is initially set true after successful start
+        Assert.assertTrue(entity.getAttribute(Startable.SERVICE_UP));
         
-        entity.connectServiceUpIsRunning();
+//        entity.connectServiceUpIsRunning();
         
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true);
+        log.info("XXX F");
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
         driver.setFailExecution(true);
+        log.info("XXX G");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false);
+        log.info("XXX H");
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, false);
         driver.setFailExecution(false);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true);
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
     }
     
-    private class MyServiceInessentialDriverImpl extends MyServiceImpl {
-        public MyServiceInessentialDriverImpl(Entity parent) {
-            super(parent);
-        }
+    public static class MyServiceInessentialDriverImpl extends MyServiceImpl {
         
         @Override public Class<?> getDriverInterface() {
             return SimulatedInessentialIsRunningDriver.class;
         }
 
         @Override
+        protected void connectSensors() {
+            super.connectSensors();
+            connectServiceUpIsRunning();
+        }
+        
+        @Override
         public void connectServiceUpIsRunning() {
-            super.connectServiceUpIsRunning();
+//            super.connectServiceUpIsRunning();
             // run more often
             FunctionFeed.builder()
                 .entity(this)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
index 05421d0..3c8e464 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
@@ -33,11 +33,9 @@ import brooklyn.event.feed.http.HttpFeed;
 import brooklyn.event.feed.http.HttpPollConfig;
 import brooklyn.event.feed.http.HttpValueFunctions;
 import brooklyn.location.access.BrooklynAccessUtils;
-import brooklyn.policy.Enricher;
 import brooklyn.util.guava.Functionals;
 
 import com.google.common.base.Functions;
-import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.net.HostAndPort;
 
@@ -51,16 +49,16 @@ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements J
         super();
     }
 
-    public JBoss7ServerImpl(Map flags){
+    public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags){
         this(flags, null);
     }
 
-    public JBoss7ServerImpl(Map flags, Entity parent) {
+    public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
         super(flags, parent);
     }
 
     @Override
-    public Class getDriverInterface() {
+    public Class<?> getDriverInterface() {
         return JBoss7Driver.class;
     }
 
@@ -120,7 +118,7 @@ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements J
         
         addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
             .from(MANAGEMENT_URL_UP)
-            .computing(Functionals.when(Predicates.not(Predicates.equalTo(true))).value("Management URL not reachable") )
+            .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
             .build());
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
index 07088b5..f7b1dd0 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
@@ -36,38 +36,81 @@ import com.google.common.collect.Sets;
  * @author alex */
 public class CollectionFunctionals {
 
+    private static final class EqualsSetPredicate implements Predicate<Iterable<?>> {
+        private final Iterable<?> target;
+
+        private EqualsSetPredicate(Iterable<?> target) {
+            this.target = target;
+        }
+
+        @Override
+        public boolean apply(@Nullable Iterable<?> input) {
+            if (input==null) return false;
+            return Sets.newHashSet(target).equals(Sets.newHashSet(input));
+        }
+    }
+
+    private static final class KeysOfMapFunction<K> implements Function<Map<K, ?>, Set<K>> {
+        @Override
+        public Set<K> apply(Map<K, ?> input) {
+            if (input==null) return null;
+            return input.keySet();
+        }
+
+        @Override public String toString() { return "keys"; }
+    }
+
+    private static final class SizeSupplier implements Supplier<Integer> {
+        private final Iterable<?> collection;
+
+        private SizeSupplier(Iterable<?> collection) {
+            this.collection = collection;
+        }
+
+        @Override
+        public Integer get() {
+            return Iterables.size(collection);
+        }
+
+        @Override public String toString() { return "sizeSupplier("+collection+")"; }
+    }
+
+    public static final class SizeFunction implements Function<Iterable<?>, Integer> {
+        private final Integer valueIfInputNull;
+
+        private SizeFunction(Integer valueIfInputNull) {
+            this.valueIfInputNull = valueIfInputNull;
+        }
+
+        @Override
+        public Integer apply(Iterable<?> input) {
+            if (input==null) return valueIfInputNull;
+            return Iterables.size(input);
+        }
+
+        @Override public String toString() { return "sizeFunction"; }
+    }
+
     public static Supplier<Integer> sizeSupplier(final Iterable<?> collection) {
-        return new Supplier<Integer>() {
-            @Override
-            public Integer get() {
-                return Iterables.size(collection);
-            }
-            @Override public String toString() { return "sizeSupplier("+collection+")"; }
-        };
+        return new SizeSupplier(collection);
     }
     
-    public static Function<Iterable<?>, Integer> sizeFunction() {
-        return new Function<Iterable<?>, Integer>() {
-            @Override
-            public Integer apply(Iterable<?> input) {
-                return Iterables.size(input);
-            }
-            @Override public String toString() { return "sizeFunction"; }
-        };
+    public static Function<Iterable<?>, Integer> sizeFunction() { return sizeFunction(null); }
+    
+    public static Function<Iterable<?>, Integer> sizeFunction(final Integer valueIfInputNull) {
+        return new SizeFunction(valueIfInputNull);
     }
 
     public static <K> Function<Map<K,?>,Set<K>> keys() {
-        return new Function<Map<K,?>, Set<K>>() {
-            @Override
-            public Set<K> apply(Map<K, ?> input) {
-                return input.keySet();
-            }
-            @Override public String toString() { return "keys"; }
-        };
+        return new KeysOfMapFunction<K>();
     }
 
     public static <K> Function<Map<K, ?>, Integer> mapSize() {
-        return Functions.compose(CollectionFunctionals.sizeFunction(), CollectionFunctionals.<K>keys());
+        return mapSize(null);
+    }
+    
+    public static <K> Function<Map<K, ?>, Integer> mapSize(Integer valueIfNull) {
+        return Functions.compose(CollectionFunctionals.sizeFunction(valueIfNull), CollectionFunctionals.<K>keys());
     }
 
     /** default guava Equals predicate will reflect order of target, and will fail when matching against a list;
@@ -76,13 +119,7 @@ public class CollectionFunctionals {
         return equalsSet(Arrays.asList(target));
     }
     public static Predicate<Iterable<?>> equalsSet(final Iterable<?> target) {
-        return new Predicate<Iterable<?>>() {
-            @Override
-            public boolean apply(@Nullable Iterable<?> input) {
-                if (input==null) return false;
-                return Sets.newHashSet(target).equals(Sets.newHashSet(input));
-            }
-        };
+        return new EqualsSetPredicate(target);
     }
 
     public static Predicate<Iterable<?>> sizeEquals(int targetSize) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
index caebc7b..a93c551 100644
--- a/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
+++ b/utils/common/src/main/java/brooklyn/util/guava/Functionals.java
@@ -18,13 +18,11 @@
  */
 package brooklyn.util.guava;
 
-import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilder;
-import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilderWhenFirst;
+import brooklyn.util.guava.IfFunctions.IfFunctionBuilderApplyingFirst;
 
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
 
 public class Functionals {
 
@@ -43,24 +41,43 @@ public class Functionals {
         return chain(f1, chain(f2, chain(f3, f4)));
     }
 
-    /** @see WhenFunctions */
-    public static <I> WhenFunctionBuilderWhenFirst<I> when(I test) {
-        return WhenFunctions.when(test);
-    }
-    
-    /** @see WhenFunctions */
-    public static <I> WhenFunctionBuilderWhenFirst<I> when(Predicate<I> test) {
-        return WhenFunctions.when(test);
+    /** @see IfFunctions */
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifEquals(I test) {
+        return IfFunctions.ifEquals(test);
     }
 
-    /** @see WhenFunctions */
-    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
-        return WhenFunctions.when(test, supplier);
+    /** @see IfFunctions */
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifNotEquals(I test) {
+        return IfFunctions.ifNotEquals(test);
     }
     
-    /** @see WhenFunctions */
-    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
-        return WhenFunctions.when(test, value);
+    /** @see IfFunctions */
+    public static <I> IfFunctionBuilderApplyingFirst<I> ifPredicate(Predicate<I> test) {
+        return IfFunctions.ifPredicate(test);
     }
-    
+
+    /** like guava equivalent but parametrises the input generic type, and allows tostring to be customised */
+    public static final class ConstantFunction<I, O> implements Function<I, O> {
+        private final O constant;
+        private Object toStringDescription;
+
+        public ConstantFunction(O constant) {
+            this(constant, null);
+        }
+        public ConstantFunction(O constant, Object toStringDescription) {
+            this.constant = constant;
+            this.toStringDescription = toStringDescription;
+        }
+
+        @Override
+        public O apply(I input) {
+            return constant;
+        }
+        
+        @Override
+        public String toString() {
+            return toStringDescription==null ? "constant("+constant+")" : toStringDescription.toString();
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java b/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
new file mode 100644
index 0000000..5384436
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java
@@ -0,0 +1,158 @@
+/*
+ * 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 brooklyn.util.guava;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+
+/** Utilities for building {@link Function} instances which return specific values
+ * (or {@link Supplier} or {@link Function} instances) when certain predicates are satisfied,
+ * tested in order and returning the first matching,
+ * with support for an "else" default value if none are satisfied (null by default). */
+public class IfFunctions {
+
+    public static <I,O> IfFunctionBuilder<I,O> newInstance(Class<I> testType, Class<O> returnType) {
+        return new IfFunctionBuilder<I,O>();
+    }
+    
+    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifPredicate(Predicate<? super I> test) {
+        return new IfFunctionBuilderApplyingFirst<I>(test);
+    }
+    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifEquals(I test) {
+        return ifPredicate(Predicates.equalTo(test));
+    }
+    public static <I,O> IfFunctionBuilderApplyingFirst<I> ifNotEquals(I test) {
+        return ifPredicate(Predicates.not(Predicates.equalTo(test)));
+    }
+    
+    @Beta
+    public static class IfFunction<I,O> implements Function<I,O> {
+        protected final Map<Predicate<? super I>,Function<? super I,? extends O>> tests = new LinkedHashMap<Predicate<? super I>,Function<? super I,? extends O>>();
+        protected Function<? super I,? extends O> defaultFunction = null;
+        
+        protected IfFunction(IfFunction<I,O> input) {
+            this.tests.putAll(input.tests);
+            this.defaultFunction = input.defaultFunction;
+        }
+
+        protected IfFunction() {
+        }
+        
+        @Override
+        public O apply(I input) {
+            for (Map.Entry<Predicate<? super I>,Function<? super I,? extends O>> test: tests.entrySet()) {
+                if (test.getKey().apply(input)) 
+                    return test.getValue().apply(input);
+            }
+            return defaultFunction==null ? null : defaultFunction.apply(input);
+        }
+        
+        @Override
+        public String toString() {
+            return "if["+tests+"]"+(defaultFunction!=null ? "-else["+defaultFunction+"]" : "");
+        }
+    }
+    
+    @Beta
+    public static class IfFunctionBuilder<I,O> extends IfFunction<I,O> {
+        protected IfFunctionBuilder() { super(); }
+        protected IfFunctionBuilder(IfFunction<I,O> input) { super(input); }
+        
+        public IfFunction<I,O> build() {
+            return new IfFunction<I,O>(this);
+        }
+        
+        public IfFunctionBuilderApplying<I,O> ifPredicate(Predicate<I> test) {
+            return new IfFunctionBuilderApplying<I,O>(this, (Predicate<I>)test);
+        }
+        public IfFunctionBuilderApplying<I,O> ifEquals(I test) {
+            return ifPredicate(Predicates.equalTo(test));
+        }
+        public IfFunctionBuilderApplying<I,O> ifNotEquals(I test) {
+            return ifPredicate(Predicates.not(Predicates.equalTo(test)));
+        }
+
+        public IfFunctionBuilder<I,O> defaultValue(O defaultValue) {
+            return defaultApply(new Functionals.ConstantFunction<I,O>(defaultValue, defaultValue));
+        }
+        @SuppressWarnings("unchecked")
+        public IfFunctionBuilder<I,O> defaultGet(Supplier<? extends O> defaultSupplier) {
+            return defaultApply((Function<I,O>)Functions.forSupplier(defaultSupplier));
+        }
+        public IfFunctionBuilder<I,O> defaultApply(Function<? super I,? extends O> defaultFunction) {
+            IfFunctionBuilder<I, O> result = new IfFunctionBuilder<I,O>(this);
+            result.defaultFunction = defaultFunction;
+            return result;
+        }
+    }
+
+    @Beta
+    public static class IfFunctionBuilderApplying<I,O> {
+        private IfFunction<I, O> input;
+        private Predicate<? super I> test;
+        
+        private IfFunctionBuilderApplying(IfFunction<I,O> input, Predicate<? super I> test) {
+            this.input = input;
+            this.test = test;
+        }
+        
+        public IfFunctionBuilder<I,O> value(O value) {
+            return apply(new Functionals.ConstantFunction<I,O>(value, value));
+        }
+        @SuppressWarnings("unchecked")
+        public IfFunctionBuilder<I,O> get(Supplier<? extends O> supplier) {
+            return apply((Function<I,O>)Functions.forSupplier(supplier));
+        }
+        public IfFunctionBuilder<I,O> apply(Function<? super I,? extends O> function) {
+            IfFunctionBuilder<I, O> result = new IfFunctionBuilder<I,O>(input);
+            result.tests.put(test, function);
+            return result;
+        }
+    }
+
+    @Beta
+    public static class IfFunctionBuilderApplyingFirst<I> {
+        private Predicate<? super I> test;
+        
+        private IfFunctionBuilderApplyingFirst(Predicate<? super I> test) {
+            this.test = test;
+        }
+        
+        public <O> IfFunctionBuilder<I,O> value(O value) {
+            return apply(new Functionals.ConstantFunction<I,O>(value, value));
+        }
+        @SuppressWarnings("unchecked")
+        public <O> IfFunctionBuilder<I,O> get(Supplier<? extends O> supplier) {
+            return apply((Function<I,O>)Functions.forSupplier(supplier));
+        }
+        public <O> IfFunctionBuilder<I,O> apply(Function<? super I,? extends O> function) {
+            IfFunctionBuilder<I, O> result = new IfFunctionBuilder<I,O>();
+            result.tests.put(test, function);
+            return result;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java b/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
deleted file mode 100644
index 50a1e35..0000000
--- a/utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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 brooklyn.util.guava;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-
-/** Utilities for building {@link Function} instances which return specific values
- * (or {@link Supplier} instances) when certain predicates are satisfied,
- * tested in order and returning the first matching,
- * with support for an "else" default value if none are satisfied (null by default). */
-public class WhenFunctions {
-
-    public static <I,O> WhenFunctionBuilder<I,O> newInstance(Class<I> testType, Class<O> returnType) {
-        return new WhenFunctionBuilder<I,O>();
-    }
-    
-    public static <I,O> WhenFunctionBuilderWhenFirst<I> when(Predicate<I> test) {
-        return new WhenFunctionBuilderWhenFirst<I>(test);
-    }
-    public static <I,O> WhenFunctionBuilderWhenFirst<I> when(I test) {
-        return new WhenFunctionBuilderWhenFirst<I>(test);
-    }
-    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
-        return new WhenFunctionBuilder<I,O>().when(test, supplier);
-    }
-    public static <I,O> WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
-        return new WhenFunctionBuilder<I,O>().when(test, value);
-    }
-    
-    public static class WhenFunction<I,O> implements Function<I,O> {
-        protected final Map<Predicate<I>,Supplier<O>> tests = new LinkedHashMap<Predicate<I>,Supplier<O>>();
-        protected Supplier<O> defaultValue = null;
-        
-        protected WhenFunction(WhenFunction<I,O> input) {
-            this.tests.putAll(input.tests);
-            this.defaultValue = input.defaultValue;
-        }
-
-        protected WhenFunction() {
-        }
-        
-        @Override
-        public O apply(I input) {
-            for (Map.Entry<Predicate<I>,Supplier<O>> test: tests.entrySet()) {
-                if (test.getKey().apply(input)) 
-                    return test.getValue().get();
-            }
-            return defaultValue==null ? null : defaultValue.get();
-        }
-        
-        @Override
-        public String toString() {
-            return "if["+tests+"]"+(defaultValue!=null ? "-else["+defaultValue+"]" : "");
-        }
-    }
-    
-    public static class WhenFunctionBuilder<I,O> extends WhenFunction<I,O> {
-        protected WhenFunctionBuilder() { super(); }
-        protected WhenFunctionBuilder(WhenFunction<I,O> input) { super(input); }
-        
-        public WhenFunction<I,O> build() {
-            return new WhenFunction<I,O>(this);
-        }
-        
-        public WhenFunctionBuilder<I,O> when(Predicate<I> test, Supplier<O> supplier) {
-            return when(test).value(supplier);
-        }
-
-        public WhenFunctionBuilder<I,O> when(Predicate<I> test, O value) {
-            return when(test).value(value);
-        }
-
-        public WhenFunctionBuilderWhen<I,O> when(Predicate<I> test) {
-            return whenUnchecked(test);
-        }
-        public WhenFunctionBuilderWhen<I,O> when(I test) {
-            return whenUnchecked(test);
-        }
-        @SuppressWarnings("unchecked")
-        protected WhenFunctionBuilderWhen<I,O> whenUnchecked(Object test) {
-            if (!(test instanceof Predicate)) {
-                test = Predicates.equalTo(test);
-            }
-            return new WhenFunctionBuilderWhen<I,O>(this, (Predicate<I>)test);
-        }
-
-        public WhenFunctionBuilder<I,O> defaultValue(O defaultValue) {
-            return defaultValueUnchecked(defaultValue);
-        }
-        public WhenFunctionBuilder<I,O> defaultValue(Supplier<O> defaultValue) {
-            return defaultValueUnchecked(defaultValue);
-        }
-        @SuppressWarnings("unchecked")
-        protected WhenFunctionBuilder<I,O> defaultValueUnchecked(Object defaultValue) {
-            if (!(defaultValue instanceof Supplier)) {
-                defaultValue = Suppliers.ofInstance(defaultValue);
-            }
-            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>(this);
-            result.defaultValue = (Supplier<O>)defaultValue;
-            return result;
-        }
-    }
-
-    public static class WhenFunctionBuilderWhen<I,O> {
-        private WhenFunction<I, O> input;
-        private Predicate<I> test;
-        
-        private WhenFunctionBuilderWhen(WhenFunction<I,O> input, Predicate<I> test) {
-            this.input = input;
-            this.test = test;
-        }
-        
-        public WhenFunctionBuilder<I,O> value(O value) {
-            return valueUnchecked(value);
-        }
-        public WhenFunctionBuilder<I,O> value(Supplier<O> value) {
-            return valueUnchecked(value);
-        }
-        @SuppressWarnings("unchecked")
-        protected WhenFunctionBuilder<I,O> valueUnchecked(Object value) {
-            if (!(value instanceof Supplier)) {
-                value = Suppliers.ofInstance(value);
-            }
-            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>(input);
-            result.tests.put(test, (Supplier<O>) value);
-            return result;
-        }
-    }
-
-    public static class WhenFunctionBuilderWhenFirst<I> {
-        private Predicate<I> test;
-        
-        private WhenFunctionBuilderWhenFirst(Predicate<I> test) {
-            whenUnchecked(test);
-        }
-        
-        public WhenFunctionBuilderWhenFirst(I test) {
-            whenUnchecked(test);
-        }
-
-        @SuppressWarnings("unchecked")
-        protected void whenUnchecked(Object test) {
-            if (!(test instanceof Predicate)) {
-                this.test = Predicates.equalTo((I)test);
-            } else {
-                this.test = (Predicate<I>) test;
-            }
-        }
-        
-        public <O> WhenFunctionBuilder<I,O> value(O value) {
-            return valueUnchecked(value);
-        }
-        public <O> WhenFunctionBuilder<I,O> value(Supplier<O> value) {
-            return valueUnchecked(value);
-        }
-        @SuppressWarnings("unchecked")
-        protected <O> WhenFunctionBuilder<I,O> valueUnchecked(Object value) {
-            if (!(value instanceof Supplier)) {
-                value = Suppliers.ofInstance(value);
-            }
-            WhenFunctionBuilder<I, O> result = new WhenFunctionBuilder<I,O>();
-            result.tests.put(test, (Supplier<O>) value);
-            return result;
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/test/java/brooklyn/util/collections/CollectionFunctionalsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/collections/CollectionFunctionalsTest.java b/utils/common/src/test/java/brooklyn/util/collections/CollectionFunctionalsTest.java
new file mode 100644
index 0000000..6611d9b
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/collections/CollectionFunctionalsTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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 brooklyn.util.collections;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CollectionFunctionalsTest {
+
+    @Test
+    public void testListSize() {
+        Assert.assertTrue(CollectionFunctionals.sizeEquals(2).apply(ImmutableList.of("x", "y")));
+        Assert.assertFalse(CollectionFunctionals.sizeEquals(2).apply(null));
+        Assert.assertTrue(CollectionFunctionals.sizeEquals(0).apply(ImmutableList.of()));
+        Assert.assertFalse(CollectionFunctionals.sizeEquals(0).apply(null));
+    }
+
+    @Test
+    public void testMapSize() {
+        Assert.assertTrue(CollectionFunctionals.<String>mapSizeEquals(2).apply(ImmutableMap.of("x", "1", "y", "2")));
+        Assert.assertFalse(CollectionFunctionals.<String>mapSizeEquals(2).apply(null));
+        Assert.assertTrue(CollectionFunctionals.mapSizeEquals(0).apply(ImmutableMap.of()));
+        Assert.assertFalse(CollectionFunctionals.mapSizeEquals(0).apply(null));
+    }
+
+    @Test
+    public void testMapSizeOfNull() {
+        Assert.assertEquals(CollectionFunctionals.mapSize().apply(null), null);
+        Assert.assertEquals(CollectionFunctionals.mapSize(-1).apply(null), (Integer)(-1));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java b/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
index 7e2ab35..3da3532 100644
--- a/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
+++ b/utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java
@@ -35,24 +35,24 @@ public class FunctionalsTest {
     }
 
     @Test
-    public void testWhen() {
-        WhenFunctionsTest.checkTF(Functionals.when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
+    public void testIf() {
+        IfFunctionsTest.checkTF(Functionals.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(), "?");
     }
 
     @Test
-    public void testWhenNoBuilder() {
-        WhenFunctionsTest.checkTF(Functionals.when(false).value("F").when(true).value("T").defaultValue("?"), "?");
+    public void testIfNoBuilder() {
+        IfFunctionsTest.checkTF(Functionals.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?"), "?");
     }
     
     @Test
-    public void testWhenPredicateAndSupplier() {
-        WhenFunctionsTest.checkTF(Functionals.when(Predicates.equalTo(false)).value(Suppliers.ofInstance("F"))
-            .when(true).value("T").defaultValue(Suppliers.ofInstance("?")).build(), "?");
+    public void testIfPredicateAndSupplier() {
+        IfFunctionsTest.checkTF(Functionals.ifPredicate(Predicates.equalTo(false)).get(Suppliers.ofInstance("F"))
+            .ifEquals(true).value("T").defaultGet(Suppliers.ofInstance("?")).build(), "?");
     }
 
     @Test
-    public void testWhenTwoArgs() {
-        WhenFunctionsTest.checkTF(Functionals.when(Predicates.equalTo(false), "F").when(Predicates.equalTo(true), "T").defaultValue("?").build(), "?");
+    public void testIfNotEqual() {
+        IfFunctionsTest.checkTF(Functionals.ifNotEquals(false).value("T").defaultValue("F").build(), "T");
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java b/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
new file mode 100644
index 0000000..74a5d5b
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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 brooklyn.util.guava;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.guava.IfFunctions.IfFunctionBuilder;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.base.Suppliers;
+
+public class IfFunctionsTest {
+
+    @Test
+    public void testCommonUsage() {
+        checkTF(IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(), "?");
+    }
+
+    @Test
+    public void testNoBuilder() {
+        checkTF(IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?"), "?");
+    }
+    
+    @Test
+    public void testPredicateAndSupplier() {
+        checkTF(IfFunctions.ifPredicate(Predicates.equalTo(false)).get(Suppliers.ofInstance("F"))
+            .ifEquals(true).value("T").defaultGet(Suppliers.ofInstance("?")).build(), "?");
+    }
+
+    @Test
+    public void testNoDefault() {
+        checkTF(IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").build(), null);
+    }
+
+    @Test
+    public void testNotEqual() {
+        checkTF(IfFunctions.ifNotEquals(false).value("T").defaultValue("F").build(), "T");
+    }
+
+    @Test
+    public void testFunction() {
+        checkTF(IfFunctions.ifNotEquals((Boolean)null).apply(new Function<Boolean, String>() {
+            @Override
+            public String apply(Boolean input) {
+                return input.toString().toUpperCase().substring(0, 1);
+            }
+        }).defaultValue("?"), "?");
+    }
+
+    @Test
+    public void testWithCast() {
+        Function<Boolean, String> f = IfFunctions.<Boolean,String>ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build();
+        checkTF(f, "?");
+    }
+
+    @Test
+    public void testWithoutCast() {
+        Function<Boolean, String> f = IfFunctions.newInstance(Boolean.class, String.class).ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build();
+        checkTF(f, "?");
+    }
+
+    @Test
+    public void testSupportsReplace() {
+        checkTF(IfFunctions.ifEquals(false).value("false").ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(), "?");
+    }
+
+    @Test
+    public void testIsImmutableAndSupportsReplace() {
+        IfFunctionBuilder<Boolean, String> f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?");
+        IfFunctionBuilder<Boolean, String> f2 = f.ifEquals(false).value("false").defaultValue("X");
+        IfFunctionBuilder<Boolean, String> f3 = f2.ifEquals(false).value("F");
+        checkTF(f, "?");
+        checkTF(f3, "X");
+        Assert.assertEquals(f2.apply(false), "false");
+    }
+
+    static void checkTF(Function<Boolean, String> f, Object defaultValue) {
+        Assert.assertEquals(f.apply(true), "T");
+        Assert.assertEquals(f.apply(false), "F");
+        Assert.assertEquals(f.apply(null), defaultValue);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bc1d19f7/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java b/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
deleted file mode 100644
index 2b01b14..0000000
--- a/utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 brooklyn.util.guava;
-
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import brooklyn.util.guava.WhenFunctions.WhenFunctionBuilder;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
-import com.google.common.base.Suppliers;
-
-public class WhenFunctionsTest {
-
-    @Test
-    public void testWhen() {
-        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
-    }
-
-    @Test
-    public void testWhenNoBuilder() {
-        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?"), "?");
-    }
-    
-    @Test
-    public void testWhenPredicateAndSupplier() {
-        checkTF(WhenFunctions.when(Predicates.equalTo(false)).value(Suppliers.ofInstance("F"))
-            .when(true).value("T").defaultValue(Suppliers.ofInstance("?")).build(), "?");
-    }
-
-    @Test
-    public void testWhenTwoArgs() {
-        checkTF(WhenFunctions.when(Predicates.equalTo(false), "F").when(Predicates.equalTo(true), "T").defaultValue("?").build(), "?");
-    }
-    
-    @Test
-    public void testWhenNoDefault() {
-        checkTF(WhenFunctions.when(false).value("F").when(true).value("T").build(), null);
-    }
-
-    @Test
-    public void testWhenWithCast() {
-        Function<Boolean, String> f = WhenFunctions.<Boolean,String>when(false).value("F").when(true).value("T").defaultValue("?").build();
-        checkTF(f, "?");
-    }
-
-    @Test
-    public void testWhenWithoutCast() {
-        Function<Boolean, String> f = WhenFunctions.newInstance(Boolean.class, String.class).when(false).value("F").when(true).value("T").defaultValue("?").build();
-        checkTF(f, "?");
-    }
-
-    @Test
-    public void testWhenSupportsReplace() {
-        checkTF(WhenFunctions.when(false).value("false").when(false).value("F").when(true).value("T").defaultValue("?").build(), "?");
-    }
-
-    @Test
-    public void testWhenIsImmutableAndSupportsReplace() {
-        WhenFunctionBuilder<Boolean, String> f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?");
-        WhenFunctionBuilder<Boolean, String> f2 = f.when(false).value("false").defaultValue("X");
-        WhenFunctionBuilder<Boolean, String> f3 = f2.when(false).value("F");
-        checkTF(f, "?");
-        checkTF(f3, "X");
-        Assert.assertEquals(f2.apply(false), "false");
-    }
-
-    static void checkTF(Function<Boolean, String> f, Object defaultValue) {
-        Assert.assertEquals(f.apply(true), "T");
-        Assert.assertEquals(f.apply(false), "F");
-        Assert.assertEquals(f.apply(null), defaultValue);
-    }
-    
-}


[13/26] git commit: add SERVICE_PROBLEMS and enrichers in ServiceStateLogic to compute state from that. add enrichers to populate PROBLEMS and NOT_UP_INDICATORS from children and members: this removes a lot of ad hoc service-up computations; some quorum

Posted by he...@apache.org.
add SERVICE_PROBLEMS and enrichers in ServiceStateLogic to compute state from that.
add enrichers to populate PROBLEMS and NOT_UP_INDICATORS from children and members: this removes a lot of ad hoc service-up computations; some quorum up logic e.g. for mongo, have changed, but in those cases the previous logic was quite hokey in any case. (to properly support QUORUM_SIZE we should have a key, or use the UP_QUORUM_CHECK config).
also tweak the recent workaround when AbstractEntity.add{Enricher,Policy} detects a duplicate, we now take the more recent one rather than the older one; this allows us to replace them in a principled fashion when using unique tags.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/97eed6bd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/97eed6bd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/97eed6bd

Branch: refs/heads/master
Commit: 97eed6bdc159ddcba231fc0183635317101ec7b9
Parents: bc1d19f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 25 07:23:01 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:17 2014 -0400

----------------------------------------------------------------------
 .../basic/AbstractBrooklynObjectSpec.java       |  14 +-
 api/src/main/java/brooklyn/entity/Entity.java   |   4 +-
 api/src/main/java/brooklyn/entity/Group.java    |   1 +
 .../brooklyn/entity/proxying/EntitySpec.java    |   7 +-
 .../java/brooklyn/location/LocationSpec.java    |   2 +-
 .../main/java/brooklyn/policy/EnricherSpec.java |  70 ++-
 .../main/java/brooklyn/policy/PolicySpec.java   |   2 +-
 .../main/java/brooklyn/enricher/Enrichers.java  |   3 +
 .../enricher/basic/AbstractAggregator.java      | 215 +++++++++
 .../basic/AbstractMultipleSensorAggregator.java | 144 ++++++
 .../brooklyn/enricher/basic/Aggregator.java     | 158 ++-----
 .../brooklyn/enricher/basic/Propagator.java     |  12 +-
 .../entity/basic/AbstractApplication.java       |  36 +-
 .../brooklyn/entity/basic/AbstractEntity.java   |  84 ++--
 .../brooklyn/entity/basic/AbstractGroup.java    |  10 +
 .../entity/basic/AbstractGroupImpl.java         |  10 +
 .../java/brooklyn/entity/basic/Attributes.java  |  19 +-
 .../java/brooklyn/entity/basic/ConfigKeys.java  |   4 +
 .../brooklyn/entity/basic/DynamicGroup.java     |   1 +
 .../brooklyn/entity/basic/DynamicGroupImpl.java |   2 +-
 .../brooklyn/entity/basic/EntityFunctions.java  |  55 +--
 .../java/brooklyn/entity/basic/Lifecycle.java   |  73 ++-
 .../java/brooklyn/entity/basic/QuorumCheck.java |  74 ++++
 .../entity/basic/ServiceStateLogic.java         | 440 +++++++++++++++++++
 .../entity/basic/ServiceStatusLogic.java        |  92 ----
 .../brooklyn/entity/group/DynamicCluster.java   |   2 +-
 .../entity/group/DynamicClusterImpl.java        |  82 ++--
 .../brooklyn/entity/group/DynamicFabric.java    |   2 +-
 .../entity/group/DynamicFabricImpl.java         |  13 +-
 .../brooklyn/event/basic/BasicConfigKey.java    |   6 +-
 .../event/basic/DependentConfiguration.java     |   4 +-
 .../policy/basic/AbstractEntityAdjunct.java     |   1 +
 .../enricher/basic/BasicEnricherTest.java       |  14 +-
 .../basic/DependentConfigurationTest.java       |   4 +-
 .../brooklyn/entity/basic/DynamicGroupTest.java |   2 -
 .../brooklyn/entity/basic/EntitySpecTest.java   |   5 +-
 .../entity/basic/PolicyRegistrationTest.java    |   6 +-
 .../entity/group/DynamicClusterTest.java        |   5 +-
 .../entity/rebind/RebindEnricherTest.java       |   4 +-
 .../BrooklynMementoPersisterTestFixture.java    |   2 +-
 .../brooklyn/event/feed/http/HttpFeedTest.java  |   5 +-
 .../EntityCleanupLongevityTestFixture.java      |   2 +-
 .../brooklyn/test/entity/TestEntityImpl.java    |   2 +-
 .../test/entity/TestEntityNoEnrichersImpl.java  |  32 ++
 .../brooklyn/demo/CumulusRDFApplication.java    |  12 +-
 .../demo/WebClusterDatabaseExample.java         |   2 +
 .../demo/WebClusterDatabaseExampleApp.java      |   2 +
 .../basic/AbstractSoftwareProcessDriver.java    |   6 +-
 .../brooklyn/entity/basic/SameServerEntity.java |   2 +-
 .../brooklyn/entity/basic/SoftwareProcess.java  |   2 +-
 ...wareProcessDriverLifecycleEffectorTasks.java |   3 +-
 .../entity/basic/SoftwareProcessImpl.java       |  36 +-
 .../brooklynnode/BrooklynEntityMirrorImpl.java  |  11 +-
 .../software/MachineLifecycleEffectorTasks.java |  30 +-
 .../basic/lifecycle/ScriptHelperTest.java       |   1 +
 .../entity/java/VanillaJavaAppTest.java         |   4 +-
 .../entity/messaging/jms/JMSBrokerImpl.java     |  13 +-
 .../entity/zookeeper/ZooKeeperEnsembleImpl.java |  22 +-
 .../cassandra/CassandraDatacenterImpl.java      |  21 +-
 .../nosql/cassandra/CassandraFabricImpl.java    |   6 +-
 .../nosql/couchbase/CouchbaseClusterImpl.java   |  40 +-
 .../nosql/couchdb/CouchDBClusterImpl.java       |  21 +-
 .../elasticsearch/ElasticSearchClusterImpl.java |  10 -
 .../nosql/mongodb/MongoDBReplicaSetImpl.java    |   3 +-
 .../MongoDBConfigServerClusterImpl.java         |  16 +-
 .../entity/nosql/riak/RiakClusterImpl.java      |  10 +-
 .../entity/proxy/AbstractControllerImpl.java    |   2 +-
 .../entity/proxy/LoadBalancerClusterImpl.java   |  31 --
 .../entity/proxy/nginx/NginxControllerImpl.java |   6 +-
 .../entity/proxy/nginx/NginxSshDriver.java      |   2 +-
 .../webapp/ControlledDynamicWebAppCluster.java  |   2 +-
 .../ControlledDynamicWebAppClusterImpl.java     |  71 +--
 .../entity/webapp/DynamicWebAppClusterImpl.java |  41 --
 .../entity/webapp/jetty/Jetty6ServerImpl.java   |   2 +-
 .../proxy/nginx/NginxRebindIntegrationTest.java |   3 +-
 .../ControlledDynamicWebAppClusterTest.java     |   2 +-
 .../rest/transform/ApplicationTransformer.java  |   2 +-
 .../java/brooklyn/util/guava/Functionals.java   |  31 ++
 78 files changed, 1476 insertions(+), 717 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
index dc6642f..0efb0a6 100644
--- a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
+++ b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
@@ -33,17 +33,17 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
 
     private static final long serialVersionUID = 3010955277740333030L;
     
-    private final Class<T> type;
+    private final Class<? extends T> type;
     private String displayName;
     private Set<Object> tags = MutableSet.of();
 
-    protected AbstractBrooklynObjectSpec(Class<T> type) {
+    protected AbstractBrooklynObjectSpec(Class<? extends T> type) {
         checkValidType(type);
         this.type = type;
     }
     
     @SuppressWarnings("unchecked")
-    protected final K self() {
+    protected K self() {
         return (K) this;
     }
 
@@ -52,7 +52,7 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
         return Objects.toStringHelper(this).add("type", getType()).toString();
     }
 
-    protected abstract void checkValidType(Class<T> type);
+    protected abstract void checkValidType(Class<? extends T> type);
     
     public K displayName(String val) {
         displayName = val;
@@ -71,14 +71,14 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
     }
 
     /**
-     * @return The type of the enricher
+     * @return The type of the object (or significant interface)
      */
-    public final Class<T> getType() {
+    public Class<? extends T> getType() {
         return type;
     }
     
     /**
-     * @return The display name of the enricher
+     * @return The display name of the object
      */
     public final String getDisplayName() {
         return displayName;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/entity/Entity.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java
index bcc8a58..88a10fa 100644
--- a/api/src/main/java/brooklyn/entity/Entity.java
+++ b/api/src/main/java/brooklyn/entity/Entity.java
@@ -214,7 +214,7 @@ public interface Entity extends BrooklynObject {
     /**
      * Adds the given policy to this entity. Also calls policy.setEntity if available.
      */
-    Policy addPolicy(Policy policy);
+    void addPolicy(Policy policy);
     
     /**
      * Adds the given policy to this entity. Also calls policy.setEntity if available.
@@ -230,7 +230,7 @@ public interface Entity extends BrooklynObject {
     /**
      * Adds the given enricher to this entity. Also calls enricher.setEntity if available.
      */
-    Enricher addEnricher(Enricher enricher);
+    void addEnricher(Enricher enricher);
     
     /**
      * Adds the given enricher to this entity. Also calls enricher.setEntity if available.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/entity/Group.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/Group.java b/api/src/main/java/brooklyn/entity/Group.java
index f99b385..f2cd0b8 100644
--- a/api/src/main/java/brooklyn/entity/Group.java
+++ b/api/src/main/java/brooklyn/entity/Group.java
@@ -30,6 +30,7 @@ import brooklyn.entity.proxying.EntitySpec;
  * or dynamic (i.e. contains all entities that match some filter).
  */
 public interface Group extends Entity {
+    
     /**
      * Return the entities that are members of this group.
      */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
index 7410266..4ed953a 100644
--- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
+++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
@@ -151,8 +151,13 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
         super(type);
     }
     
+    @SuppressWarnings("unchecked")
+    public Class<T> getType() {
+        return (Class<T>)super.getType();
+    }
+    
     @Override
-    protected void checkValidType(Class<T> type) {
+    protected void checkValidType(Class<? extends T> type) {
         // EntitySpec does nothing.  Other specs do check it's an implementation etc.
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/location/LocationSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/LocationSpec.java b/api/src/main/java/brooklyn/location/LocationSpec.java
index f34d1a2..6cd26ab 100644
--- a/api/src/main/java/brooklyn/location/LocationSpec.java
+++ b/api/src/main/java/brooklyn/location/LocationSpec.java
@@ -83,7 +83,7 @@ public class LocationSpec<T extends Location> extends AbstractBrooklynObjectSpec
         super(type);
     }
      
-    protected void checkValidType(java.lang.Class<T> type) {
+    protected void checkValidType(Class<? extends T> type) {
         checkIsImplementation(type, Location.class);
         checkIsNewStyleImplementation(type);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/policy/EnricherSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/policy/EnricherSpec.java b/api/src/main/java/brooklyn/policy/EnricherSpec.java
index f2cfdd4..e2f6c22 100644
--- a/api/src/main/java/brooklyn/policy/EnricherSpec.java
+++ b/api/src/main/java/brooklyn/policy/EnricherSpec.java
@@ -56,7 +56,7 @@ public class EnricherSpec<T extends Enricher> extends AbstractBrooklynObjectSpec
      * 
      * @param type A {@link Enricher} class
      */
-    public static <T extends Enricher> EnricherSpec<T> create(Class<T> type) {
+    public static <T extends Enricher> EnricherSpec<T> create(Class<? extends T> type) {
         return new EnricherSpec<T>(type);
     }
     
@@ -68,18 +68,18 @@ public class EnricherSpec<T extends Enricher> extends AbstractBrooklynObjectSpec
      * @param config The spec's configuration (see {@link EnricherSpec#configure(Map)}).
      * @param type   An {@link Enricher} class
      */
-    public static <T extends Enricher> EnricherSpec<T> create(Map<?,?> config, Class<T> type) {
+    public static <T extends Enricher> EnricherSpec<T> create(Map<?,?> config, Class<? extends T> type) {
         return EnricherSpec.create(type).configure(config);
     }
     
     private final Map<String, Object> flags = Maps.newLinkedHashMap();
     private final Map<ConfigKey<?>, Object> config = Maps.newLinkedHashMap();
 
-    protected EnricherSpec(Class<T> type) {
+    protected EnricherSpec(Class<? extends T> type) {
         super(type);
     }
     
-    protected void checkValidType(Class<T> type) {
+    protected void checkValidType(Class<? extends T> type) {
         checkIsImplementation(type, Enricher.class);
         checkIsNewStyleImplementation(type);
     }
@@ -149,4 +149,66 @@ public class EnricherSpec<T extends Enricher> extends AbstractBrooklynObjectSpec
         return Collections.unmodifiableMap(config);
     }
 
+    public abstract static class ExtensibleEnricherSpec<T extends Enricher,K extends ExtensibleEnricherSpec<T,K>> extends EnricherSpec<T> {
+        private static final long serialVersionUID = -3649347642882809739L;
+        
+        protected ExtensibleEnricherSpec(Class<? extends T> type) {
+            super(type);
+        }
+
+        @SuppressWarnings("unchecked")
+        protected K self() {
+            // we override the AbstractBrooklynObjectSpec method -- it's a different K here because
+            // EnricherSpec does not contain a parametrisable generic return type (Self)
+            return (K) this;
+        }
+        
+        @Override
+        public K uniqueTag(String uniqueTag) {
+            super.uniqueTag(uniqueTag);
+            return self();
+        }
+
+        @Override
+        public K configure(Map<?, ?> val) {
+            super.configure(val);
+            return self();
+        }
+
+        @Override
+        public K configure(CharSequence key, Object val) {
+            super.configure(key, val);
+            return self();
+        }
+
+        @Override
+        public <V> K configure(ConfigKey<V> key, V val) {
+            super.configure(key, val);
+            return self();
+        }
+
+        @Override
+        public <V> K configureIfNotNull(ConfigKey<V> key, V val) {
+            super.configureIfNotNull(key, val);
+            return self();
+        }
+
+        @Override
+        public <V> K configure(ConfigKey<V> key, Task<? extends V> val) {
+            super.configure(key, val);
+            return self();
+        }
+
+        @Override
+        public <V> K configure(HasConfigKey<V> key, V val) {
+            super.configure(key, val);
+            return self();
+        }
+
+        @Override
+        public <V> K configure(HasConfigKey<V> key, Task<? extends V> val) {
+            super.configure(key, val);
+            return self();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/api/src/main/java/brooklyn/policy/PolicySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/policy/PolicySpec.java b/api/src/main/java/brooklyn/policy/PolicySpec.java
index 13206cf..4c0665b 100644
--- a/api/src/main/java/brooklyn/policy/PolicySpec.java
+++ b/api/src/main/java/brooklyn/policy/PolicySpec.java
@@ -79,7 +79,7 @@ public class PolicySpec<T extends Policy> extends AbstractBrooklynObjectSpec<T,P
         super(type);
     }
     
-    protected void checkValidType(Class<T> type) {
+    protected void checkValidType(Class<? extends T> type) {
         checkIsImplementation(type, Policy.class);
         checkIsNewStyleImplementation(type);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/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 23b1b83..73b7a3b 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -125,6 +125,9 @@ public class Enrichers {
         public PropagatorBuilder propagatingAll() {
             return new PropagatorBuilder(true, null);
         }
+        public PropagatorBuilder propagatingAllButUsualAnd(Sensor<?>... vals) {
+            return new PropagatorBuilder(true, ImmutableSet.<Sensor<?>>builder().addAll(Propagator.SENSORS_NOT_USUALLY_PROPAGATED).add(vals).build());
+        }
         public PropagatorBuilder propagatingAllBut(Sensor<?>... vals) {
             return new PropagatorBuilder(true, ImmutableSet.copyOf(vals));
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
new file mode 100644
index 0000000..ba7762c
--- /dev/null
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
@@ -0,0 +1,215 @@
+/*
+ * 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 brooklyn.enricher.basic;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.trait.Changeable;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+/** Abstract superclass for enrichers which aggregate from children and/or members */
+@SuppressWarnings("serial")
+public abstract class AbstractAggregator<T,U> extends AbstractEnricher implements SensorEventListener<T> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractAggregator.class);
+
+    public static final ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer", "The entity whose children/members will be aggregated");
+
+    public static final ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
+
+    // FIXME this is not just for "members" i think -Alex
+    public static final ConfigKey<?> DEFAULT_MEMBER_VALUE = ConfigKeys.newConfigKey(Object.class, "enricher.defaultMemberValue");
+
+    public static final ConfigKey<Set<? extends Entity>> FROM_HARDCODED_PRODUCERS = ConfigKeys.newConfigKey(new TypeToken<Set<? extends Entity>>() {}, "enricher.aggregating.fromHardcodedProducers");
+
+    public static final ConfigKey<Boolean> FROM_MEMBERS = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromMembers");
+
+    public static final ConfigKey<Boolean> FROM_CHILDREN = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromChildren");
+
+    public static final ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() {}, "enricher.aggregating.entityFilter");
+
+    public static final ConfigKey<Predicate<?>> VALUE_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<?>>() {}, "enricher.aggregating.valueFilter");
+
+    protected Entity producer;
+    protected Sensor<U> targetSensor;
+    protected T defaultMemberValue;
+    protected Set<? extends Entity> fromHardcodedProducers;
+    protected Boolean fromMembers;
+    protected Boolean fromChildren;
+    protected Predicate<? super Entity> entityFilter;
+    protected Predicate<? super T> valueFilter;
+    
+    public AbstractAggregator() {}
+
+    @Override
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        setEntityLoadingConfig();
+
+        if (fromHardcodedProducers == null && producer == null) producer = entity;
+        checkState(fromHardcodedProducers != null ^ producer != null, "must specify one of %s (%s) or %s (%s)", 
+                PRODUCER.getName(), producer, FROM_HARDCODED_PRODUCERS.getName(), fromHardcodedProducers);
+        checkState(producer != null ? (Boolean.TRUE.equals(fromMembers) || Boolean.TRUE.equals(fromChildren)) : true, 
+                "when specifying producer, must specify at least one of fromMembers (%s) or fromChildren (%s)", fromMembers, fromChildren);
+
+        if (fromHardcodedProducers != null) {
+            for (Entity producer : Iterables.filter(fromHardcodedProducers, entityFilter)) {
+                addProducerHardcoded(producer);
+            }
+        }
+        
+        if (Boolean.TRUE.equals(fromMembers)) {
+            setEntityBeforeSubscribingProducerMemberEvents(entity);
+            setEntitySubscribeProducerMemberEvents();
+            setEntityAfterSubscribingProducerMemberEvents();
+        }
+        
+        if (Boolean.TRUE.equals(fromChildren)) {
+            setEntityBeforeSubscribingProducerChildrenEvents();
+            setEntitySubscribingProducerChildrenEvents();
+            setEntityAfterSubscribingProducerChildrenEvents();
+        }
+        
+        onUpdated();
+    }
+
+    @SuppressWarnings({ "unchecked" })
+    protected void setEntityLoadingConfig() {
+        this.producer = getConfig(PRODUCER);
+        this.fromHardcodedProducers= getConfig(FROM_HARDCODED_PRODUCERS);
+        this.defaultMemberValue = (T) getConfig(DEFAULT_MEMBER_VALUE);
+        this.fromMembers = Maybe.fromNullable(getConfig(FROM_MEMBERS)).or(fromMembers);
+        this.fromChildren = Maybe.fromNullable(getConfig(FROM_CHILDREN)).or(fromChildren);
+        this.entityFilter = (Predicate<? super Entity>) (getConfig(ENTITY_FILTER) == null ? Predicates.alwaysTrue() : getConfig(ENTITY_FILTER));
+        this.valueFilter = (Predicate<? super T>) (getConfig(VALUE_FILTER) == null ? Predicates.alwaysTrue() : getConfig(VALUE_FILTER));
+        
+        setEntityLoadingTargetConfig();
+    }
+    @SuppressWarnings({ "unchecked" })
+    protected void setEntityLoadingTargetConfig() {
+        this.targetSensor = (Sensor<U>) getRequiredConfig(TARGET_SENSOR);
+    }
+
+    protected void setEntityBeforeSubscribingProducerMemberEvents(EntityLocal entity) {
+        checkState(producer instanceof Group, "must be a group when fromMembers true: producer=%s; entity=%s; "
+                + "hardcodedProducers=%s", getConfig(PRODUCER), entity, fromHardcodedProducers);
+    }
+
+    protected void setEntitySubscribeProducerMemberEvents() {
+        subscribe(producer, Changeable.MEMBER_ADDED, new SensorEventListener<Entity>() {
+            @Override public void onEvent(SensorEvent<Entity> event) {
+                if (entityFilter.apply(event.getValue())) {
+                    addProducerMember(event.getValue());
+                    onUpdated();
+                }
+            }
+        });
+        subscribe(producer, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() {
+            @Override public void onEvent(SensorEvent<Entity> event) {
+                removeProducer(event.getValue());
+                onUpdated();
+            }
+        });
+    }
+
+    protected void setEntityAfterSubscribingProducerMemberEvents() {
+        if (producer instanceof Group) {
+            for (Entity member : Iterables.filter(((Group)producer).getMembers(), entityFilter)) {
+                addProducerMember(member);
+            }
+        }
+    }
+
+    protected void setEntityBeforeSubscribingProducerChildrenEvents() {
+    }
+
+    protected void setEntitySubscribingProducerChildrenEvents() {
+        subscribe(producer, AbstractEntity.CHILD_REMOVED, new SensorEventListener<Entity>() {
+            @Override public void onEvent(SensorEvent<Entity> event) {
+                removeProducer(event.getValue());
+                onUpdated();
+            }
+        });
+        subscribe(producer, AbstractEntity.CHILD_ADDED, new SensorEventListener<Entity>() {
+            @Override public void onEvent(SensorEvent<Entity> event) {
+                if (entityFilter.apply(event.getValue())) {
+                    addProducerChild(event.getValue());
+                    onUpdated();
+                }
+            }
+        });
+    }
+
+    protected void setEntityAfterSubscribingProducerChildrenEvents() {
+        for (Entity child : Iterables.filter(producer.getChildren(), entityFilter)) {
+            addProducerChild(child);
+        }
+    }
+
+    protected abstract void addProducerHardcoded(Entity producer);
+    protected abstract void addProducerMember(Entity producer);
+    protected abstract void addProducerChild(Entity producer);
+    
+    // TODO If producer removed but then get (queued) event from it after this method returns,  
+    protected void removeProducer(Entity producer) {
+        if (LOG.isDebugEnabled()) LOG.debug("{} stopped listening to {}", new Object[] {this, producer });
+        unsubscribe(producer);
+        onProducerRemoved(producer);
+    }
+
+    protected abstract void onProducerAdded(Entity producer);
+
+    protected abstract void onProducerRemoved(Entity producer);
+
+
+    /**
+     * Called whenever the values for the set of producers changes (e.g. on an event, or on a member added/removed).
+     */
+    protected void onUpdated() {
+        try {
+            emit(targetSensor, compute());
+        } catch (Throwable t) {
+            LOG.warn("Error calculating and setting aggregate for enricher "+this, t);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    protected abstract Object compute();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
new file mode 100644
index 0000000..85c36d8
--- /dev/null
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java
@@ -0,0 +1,144 @@
+/*
+ * 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 brooklyn.enricher.basic;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+/** Building on {@link AbstractAggregator} for a single source sensor (on multiple children and/or members) */
+public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggregator<Object,U> implements SensorEventListener<Object> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMultipleSensorAggregator.class);
+
+    
+    /** access via {@link #getValues(Sensor)} */
+    private final Map<String, Map<Entity,Object>> values = Collections.synchronizedMap(new LinkedHashMap<String, Map<Entity,Object>>());
+
+    public AbstractMultipleSensorAggregator() {}
+
+    protected abstract Collection<Sensor<?>> getSourceSensors();
+    
+    @Override
+    protected void setEntityLoadingConfig() {
+        super.setEntityLoadingConfig();
+        Preconditions.checkNotNull(getSourceSensors(), "sourceSensors must be set");
+    }
+    
+    protected void setEntityBeforeSubscribingProducerChildrenEvents() {
+        if (LOG.isDebugEnabled()) LOG.debug("{} subscribing to children of {}", new Object[] {this, producer });
+        for (Sensor<?> sourceSensor: getSourceSensors()) {
+            subscribeToChildren(producer, sourceSensor, this);
+        }
+    }
+
+    protected void addProducerHardcoded(Entity producer) {
+        for (Sensor<?> sourceSensor: getSourceSensors()) {
+            subscribe(producer, sourceSensor, this);
+        }
+        onProducerAdded(producer);
+    }
+
+    protected void addProducerChild(Entity producer) {
+        // not required due to subscribeToChildren call
+//        subscribe(producer, sourceSensor, this);
+        onProducerAdded(producer);
+    }
+
+    protected void addProducerMember(Entity producer) {
+        addProducerHardcoded(producer);
+    }
+
+    
+    protected void onProducerAdded(Entity producer) {
+        if (LOG.isDebugEnabled()) LOG.debug("{} listening to {}", new Object[] {this, producer});
+        synchronized (values) {
+            for (Sensor<?> sensor: getSourceSensors()) {
+                Map<Entity,Object> vs = values.get(sensor.getName());
+                if (vs==null) {
+                    vs = new LinkedHashMap<Entity,Object>();
+                    values.put(sensor.getName(), vs);
+                }
+                
+                Object vo = vs.get(producer);
+                if (vo==null) {
+                    Object initialVal;
+                    if (sensor instanceof AttributeSensor) {
+                        initialVal = producer.getAttribute((AttributeSensor<?>)sensor);
+                    } else {
+                        initialVal = null;
+                    }
+                    vs.put(producer, initialVal != null ? initialVal : defaultMemberValue);
+                    // NB: see notes on possible race, in Aggregator#onProducerAdded
+                }
+                
+            }
+        }
+    }
+    
+    protected void onProducerRemoved(Entity producer) {
+        synchronized (values) {
+            for (Sensor<?> sensor: getSourceSensors()) {
+                Map<Entity,Object> vs = values.get(sensor.getName());
+                if (vs!=null)
+                    vs.remove(producer);
+            }
+        }
+        onUpdated();
+    }
+
+    @Override
+    public void onEvent(SensorEvent<Object> event) {
+        Entity e = event.getSource();
+        synchronized (values) {
+            Map<Entity,Object> vs = values.get(event.getSensor().getName());
+            if (vs==null) {
+                LOG.warn("{} has no entry for sensor on "+event);
+            } else {
+                vs.put(e, event.getValue());
+            }
+        }
+        onUpdated();
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> Map<Entity,T> getValues(Sensor<T> sensor) {
+        synchronized (values) {
+            Map<Entity, T> sv = (Map<Entity, T>) values.get(sensor.getName());
+            if (sv==null) return ImmutableMap.of();
+            return MutableMap.copyOf(sv).asUnmodifiable();
+        }
+    }
+    
+    protected abstract Object compute();
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/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 fed403e..034a604 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -18,25 +18,18 @@
  */
 package brooklyn.enricher.basic;
 
-import static com.google.common.base.Preconditions.checkState;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
-import brooklyn.entity.Group;
-import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.trait.Changeable;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.Sensor;
 import brooklyn.event.SensorEvent;
@@ -46,46 +39,20 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
 
+/** Building on {@link AbstractAggregator} for a single source sensor (on multiple children and/or members) */
 @SuppressWarnings("serial")
-public class Aggregator<T,U> extends AbstractEnricher implements SensorEventListener<T> {
+public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEventListener<T> {
 
     private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class);
 
-    public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {}, "enricher.transformation");
-
-    public static final ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer");
-
     public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
+    public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {}, "enricher.transformation");
 
-    public static final ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
-
-    public static final ConfigKey<?> DEFAULT_MEMBER_VALUE = ConfigKeys.newConfigKey(Object.class, "enricher.defaultMemberValue");
-
-    public static final ConfigKey<Set<? extends Entity>> FROM_HARDCODED_PRODUCERS = ConfigKeys.newConfigKey(new TypeToken<Set<? extends Entity>>() {}, "enricher.aggregating.fromHardcodedProducers");
-
-    public static final ConfigKey<Boolean> FROM_MEMBERS = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromMembers");
-
-    public static final ConfigKey<Boolean> FROM_CHILDREN = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromChildren");
-
-    public static final ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() {}, "enricher.aggregating.entityFilter");
-
-    public static final ConfigKey<Predicate<?>> VALUE_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<?>>() {}, "enricher.aggregating.valueFilter");
-
-    protected Function<? super Collection<T>, ? extends U> transformation;
-    protected Entity producer;
     protected Sensor<T> sourceSensor;
-    protected Sensor<U> targetSensor;
-    protected T defaultMemberValue;
-    protected Set<? extends Entity> fromHardcodedProducers;
-    protected Boolean fromMembers;
-    protected Boolean fromChildren;
-    protected Predicate<? super Entity> entityFilter;
-    protected Predicate<? super T> valueFilter;
+    protected Function<? super Collection<T>, ? extends U> transformation;
     
     /**
      * Users of values should either on it synchronize when iterating over its entries or use
@@ -94,100 +61,40 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
     // We use a synchronizedMap over a ConcurrentHashMap for entities that store null values.
     protected final Map<Entity, T> values = Collections.synchronizedMap(new LinkedHashMap<Entity, T>());
 
-    public Aggregator() {
-    }
+    public Aggregator() {}
 
-    @SuppressWarnings({ "unchecked" })
-    @Override
-    public void setEntity(EntityLocal entity) {
-        super.setEntity(entity);
-        this.transformation = (Function<? super Collection<T>, ? extends U>) getRequiredConfig(TRANSFORMATION);
-        this.producer = getConfig(PRODUCER);
-        this.fromHardcodedProducers= getConfig(FROM_HARDCODED_PRODUCERS);
+    @SuppressWarnings("unchecked")
+    protected void setEntityLoadingConfig() {
+        super.setEntityLoadingConfig();
         this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);
-        this.targetSensor = (Sensor<U>) getRequiredConfig(TARGET_SENSOR);
-        this.defaultMemberValue = (T) getConfig(DEFAULT_MEMBER_VALUE);
-        this.fromMembers = getConfig(FROM_MEMBERS);
-        this.fromChildren = getConfig(FROM_CHILDREN);
-        this.entityFilter = (Predicate<? super Entity>) (getConfig(ENTITY_FILTER) == null ? Predicates.alwaysTrue() : getConfig(ENTITY_FILTER));
-        this.valueFilter = (Predicate<? super T>) (getConfig(VALUE_FILTER) == null ? Predicates.alwaysTrue() : getConfig(VALUE_FILTER));
-
-        if (fromHardcodedProducers == null && producer == null) producer = entity;
-        checkState(fromHardcodedProducers != null ^ producer != null, "must specify one of %s (%s) or %s (%s)", 
-                PRODUCER.getName(), producer, FROM_HARDCODED_PRODUCERS.getName(), fromHardcodedProducers);
-        checkState(producer != null ? (Boolean.TRUE.equals(fromMembers) ^ Boolean.TRUE.equals(fromChildren)) : true, 
-                "when specifying producer, must specify one of fromMembers (%s) or fromChildren (%s)", fromMembers, fromChildren);
-
-        if (fromHardcodedProducers != null) {
-            for (Entity producer : Iterables.filter(fromHardcodedProducers, entityFilter)) {
-                addProducer(producer);
-            }
-            onUpdated();
-        }
-        
-        if (Boolean.TRUE.equals(fromMembers)) {
-            checkState(producer instanceof Group, "must be a group when fromMembers true: producer=%s; entity=%s; "
-                    + "hardcodedProducers=%s", getConfig(PRODUCER), entity, fromHardcodedProducers);
-
-            subscribe(producer, Changeable.MEMBER_ADDED, new SensorEventListener<Entity>() {
-                @Override public void onEvent(SensorEvent<Entity> event) {
-                    if (entityFilter.apply(event.getValue())) addProducer(event.getValue());
-                }
-            });
-            subscribe(producer, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() {
-                @Override public void onEvent(SensorEvent<Entity> event) {
-                    removeProducer(event.getValue());
-                }
-            });
-            
-            if (producer instanceof Group) {
-                for (Entity member : Iterables.filter(((Group)producer).getMembers(), entityFilter)) {
-                    addProducer(member);
-                }
-            }
-            onUpdated();
-        }
+        this.transformation = (Function<? super Collection<T>, ? extends U>) getRequiredConfig(TRANSFORMATION);
+    }
         
-        if (Boolean.TRUE.equals(fromChildren)) {
-            if (LOG.isDebugEnabled()) LOG.debug("{} linked (children of {}, {}) to {}", new Object[] {this, producer, sourceSensor, targetSensor});
-            subscribeToChildren(producer, sourceSensor, this);
-
-            subscribe(producer, AbstractEntity.CHILD_REMOVED, new SensorEventListener<Entity>() {
-                @Override public void onEvent(SensorEvent<Entity> event) {
-                    onProducerRemoved(event.getValue());
-                }
-            });
-            subscribe(producer, AbstractEntity.CHILD_ADDED, new SensorEventListener<Entity>() {
-                @Override public void onEvent(SensorEvent<Entity> event) {
-                    if (entityFilter.apply(event.getValue())) onProducerAdded(event.getValue());
-                }
-            });
 
-            for (Entity child : Iterables.filter(producer.getChildren(), entityFilter)) {
-                onProducerAdded(child, false);
-            }
-            onUpdated();
-        }
+    protected void setEntityBeforeSubscribingProducerChildrenEvents() {
+        if (LOG.isDebugEnabled()) LOG.debug("{} subscribing to children of {}", new Object[] {this, producer });
+        subscribeToChildren(producer, sourceSensor, this);
     }
 
-    protected void addProducer(Entity producer) {
-        if (LOG.isDebugEnabled()) LOG.debug("{} linked ({}, {}) to {}", new Object[] {this, producer, sourceSensor, targetSensor});
+    protected void addProducerHardcoded(Entity producer) {
         subscribe(producer, sourceSensor, this);
         onProducerAdded(producer);
     }
-    
-    // TODO If producer removed but then get (queued) event from it after this method returns,  
-    protected T removeProducer(Entity producer) {
-        if (LOG.isDebugEnabled()) LOG.debug("{} unlinked ({}, {}) from {}", new Object[] {this, producer, sourceSensor, targetSensor});
-        unsubscribe(producer);
-        return onProducerRemoved(producer);
+
+    protected void addProducerChild(Entity producer) {
+        // not required due to subscribeToChildren call
+//        subscribe(producer, sourceSensor, this);
+        onProducerAdded(producer);
     }
 
-    protected void onProducerAdded(Entity producer) {
-        onProducerAdded(producer, true);
+    protected void addProducerMember(Entity producer) {
+        subscribe(producer, sourceSensor, this);
+        onProducerAdded(producer);
     }
+
     
-    protected void onProducerAdded(Entity producer, boolean update) {
+    protected void onProducerAdded(Entity producer) {
+        if (LOG.isDebugEnabled()) LOG.debug("{} listening to {}", new Object[] {this, producer});
         synchronized (values) {
             T vo = values.get(producer);
             if (vo==null) {
@@ -206,18 +113,13 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
                 if (LOG.isDebugEnabled()) LOG.debug("{} already had value ({}) for producer ({}); but that producer has just been added", new Object[] {this, vo, producer});
             }
         }
-        if (update) {
-            onUpdated();
-        }
     }
     
-    // TODO If producer removed but then get (queued) event from it after this method returns,  
-    protected T onProducerRemoved(Entity producer) {
-        T removed = values.remove(producer);
+    protected void onProducerRemoved(Entity producer) {
+        values.remove(producer);
         onUpdated();
-        return removed;
     }
-    
+
     @Override
     public void onEvent(SensorEvent<T> event) {
         Entity e = event.getSource();
@@ -231,9 +133,6 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
         onUpdated();
     }
 
-    /**
-     * Called whenever the values for the set of producers changes (e.g. on an event, or on a member added/removed).
-     */
     protected void onUpdated() {
         try {
             emit(targetSensor, compute());
@@ -257,4 +156,5 @@ public class Aggregator<T,U> extends AbstractEnricher implements SensorEventList
             return Collections.unmodifiableMap(MutableMap.copyOf(values));
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/enricher/basic/Propagator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Propagator.java b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
index 267ba88..7aca9ca 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Propagator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
@@ -18,16 +18,16 @@
  */
 package brooklyn.enricher.basic;
 
-import static com.google.common.base.Preconditions.checkState;
-
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.event.AttributeSensor;
@@ -36,9 +36,11 @@ import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 import brooklyn.util.flags.SetFromFlag;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
@@ -48,6 +50,10 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
 
     private static final Logger LOG = LoggerFactory.getLogger(Propagator.class);
 
+    public static final Set<Sensor<?>> SENSORS_NOT_USUALLY_PROPAGATED = ImmutableSet.<Sensor<?>>of(
+        Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS, 
+        Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, Attributes.SERVICE_PROBLEMS);
+
     @SetFromFlag("producer")
     public static ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer");
 
@@ -112,7 +118,7 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
             };
         }
             
-        checkState(propagatingAll ^ sensorMapping.size() > 0,
+        Preconditions.checkState(propagatingAll ^ sensorMapping.size() > 0,
                 "Exactly one must be set of propagatingAll (%s, excluding %s), sensorMapping (%s)", propagatingAll, getConfig(PROPAGATING_ALL_BUT), sensorMapping);
 
         if (propagatingAll) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
index b7edf59..da33208 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
@@ -27,6 +27,8 @@ import org.slf4j.LoggerFactory;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
 import brooklyn.entity.trait.StartableMethods;
 import brooklyn.location.Location;
 import brooklyn.management.ManagementContext;
@@ -75,11 +77,6 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
     }
 
     @Override
-    public void init() {
-        log.warn("Deprecated: AbstractApplication.init() will be declared abstract in a future release; please override (without calling super) for code instantiating child entities");
-    }
-
-    @Override
     public Application getApplication() {
         if (application!=null) {
             if (application.getId().equals(getId()))
@@ -115,6 +112,15 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         return this;
     }
     
+    /** as {@link AbstractEntity#initEnrichers()} but also adding default service not-up and problem indicators from children */
+    @Override
+    protected void initEnrichers() {
+        super.initEnrichers();
+        
+        // default app logic; easily overridable by adding a different enricher with the same tag
+        ServiceStateLogic.newEnricherFromChildren().checkChildrenAndMembers().addTo(this);
+    }
+    
     /**
      * Default start will start all Startable children (child.start(Collection<? extends Location>)),
      * calling preStart(locations) first and postStart(locations) afterwards.
@@ -123,23 +129,25 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
     public void start(Collection<? extends Location> locations) {
         this.addLocations(locations);
         Collection<? extends Location> locationsToUse = getLocations();
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING);
+        ServiceProblemsLogic.clearProblemsIndicator(this, START);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         recordApplicationEvent(Lifecycle.STARTING);
         try {
             preStart(locationsToUse);
             doStart(locationsToUse);
             postStart(locationsToUse);
         } catch (Exception e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            // TODO should probably remember these problems then clear?  if so, do it here or on all effectors?
+//            ServiceProblemsLogic.updateProblemsIndicator(this, START, e);
+            
             recordApplicationEvent(Lifecycle.ON_FIRE);
             // no need to log here; the effector invocation should do that
             throw Exceptions.propagate(e);
+        } finally {
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         }
-
-        setAttribute(SERVICE_UP, true);
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.RUNNING);
+        
         deployed = true;
-
         recordApplicationEvent(Lifecycle.RUNNING);
 
         logApplicationLifecycle("Started");
@@ -175,17 +183,17 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         logApplicationLifecycle("Stopping");
 
         setAttribute(SERVICE_UP, false);
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
         recordApplicationEvent(Lifecycle.STOPPING);
         try {
             doStop();
         } catch (Exception e) {
-            setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             recordApplicationEvent(Lifecycle.ON_FIRE);
             log.warn("Error stopping application " + this + " (rethrowing): "+e);
             throw Exceptions.propagate(e);
         }
-        setAttribute(Attributes.SERVICE_STATE, Lifecycle.STOPPED);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         recordApplicationEvent(Lifecycle.STOPPED);
 
         synchronized (this) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 97d7b4e..eb5f099 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -39,9 +39,9 @@ import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.EntityType;
 import brooklyn.entity.Group;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.rebind.BasicEntityRebindSupport;
-import brooklyn.entity.rebind.RebindManagerImpl;
 import brooklyn.entity.rebind.RebindSupport;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.Sensor;
@@ -1026,6 +1026,31 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
 //            .add("name", getDisplayName());
     }
     
+    // -------- INITIALIZATION --------------
+
+    /**
+     * Default entity initialization, just calls {@link #initEnrichers()}.
+     */
+    public void init() {
+        super.init();
+        initEnrichers();
+    }
+    
+    /**
+     * By default, adds enrichers to populate {@link Attributes#SERVICE_UP} and {@link Attributes#SERVICE_STATE}
+     * based on {@link Attributes#SERVICE_NOT_UP_INDICATORS}, 
+     * {@link Attributes#SERVICE_STATE_EXPECTED} and {@link Attributes#SERVICE_PROBLEMS}
+     * (doing nothing if these sensors are not used).
+     * <p>
+     * Subclasses may go further and populate the {@link Attributes#SERVICE_NOT_UP_INDICATORS} 
+     * and {@link Attributes#SERVICE_PROBLEMS} from children and members or other sources.
+     */
+    // these enrichers do nothing unless Attributes.SERVICE_NOT_UP_INDICATORS are used
+    // and/or SERVICE_STATE_EXPECTED 
+    protected void initEnrichers() {
+        addEnricher(ServiceNotUpLogic.newEnricherForServiceUpIfNoNotUpIndicators());
+        addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
+    }
     
     // -------- POLICIES --------------------
 
@@ -1035,35 +1060,32 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     }
 
     @Override
-    public Policy addPolicy(Policy policy) {
-        List<Policy> old = MutableList.<Policy>copyOf(policies);
-
+    public void addPolicy(Policy policy) {
+        Policy old = findApparentlyEqualAndWarnIfNotSameUniqueTag(policies, policy);
+        if (old!=null) {
+            LOG.debug("Removing "+old+" when adding "+policy+" to "+this);
+            removePolicy(old);
+        }
+        
         policies.add((AbstractPolicy)policy);
         ((AbstractPolicy)policy).setEntity(this);
         
         getManagementSupport().getEntityChangeListener().onPolicyAdded(policy);
         emit(AbstractEntity.POLICY_ADDED, new PolicyDescriptor(policy));
-        
-        Policy actual = findApparentlyEqualsAndWarn(old, policy);
-        if (actual!=null) {
-            removePolicy(policy);
-            return actual;
-        }
-        return policy;
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     public <T extends Policy> T addPolicy(PolicySpec<T> spec) {
         T policy = getManagementContext().getEntityManager().createPolicy(spec);
-        return (T) addPolicy(policy);
+        addPolicy(policy);
+        return policy;
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     public <T extends Enricher> T addEnricher(EnricherSpec<T> spec) {
         T enricher = getManagementContext().getEntityManager().createEnricher(spec);
-        return (T) addEnricher(enricher);
+        addEnricher(enricher);
+        return enricher;
     }
 
     @Override
@@ -1094,37 +1116,31 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     }
 
     @Override
-    public Enricher addEnricher(Enricher enricher) {
-        List<Enricher> old = MutableList.<Enricher>copyOf(enrichers);
+    public void addEnricher(Enricher enricher) {
+        Enricher old = findApparentlyEqualAndWarnIfNotSameUniqueTag(enrichers, enricher);
+        if (old!=null) {
+            LOG.debug("Removing "+old+" when adding "+enricher+" to "+this);
+            removeEnricher(old);
+        }
         
         enrichers.add((AbstractEnricher) enricher);
         ((AbstractEnricher)enricher).setEntity(this);
         
         getManagementSupport().getEntityChangeListener().onEnricherAdded(enricher);
         // TODO Could add equivalent of AbstractEntity.POLICY_ADDED for enrichers; no use-case for that yet
-        
-        Enricher actual = findApparentlyEqualsAndWarn(old, enricher);
-        if (actual!=null) {
-            removeEnricher(enricher);
-            return actual;
-        }
-        return enricher;
     }
     
-    private <T extends EntityAdjunct> T findApparentlyEqualsAndWarn(Collection<? extends T> items, T newItem) {
-        T oldItem = findApparentlyEquals(items, newItem);
+    private <T extends EntityAdjunct> T findApparentlyEqualAndWarnIfNotSameUniqueTag(Collection<? extends T> items, T newItem) {
+        T oldItem = findApparentlyEqual(items, newItem);
         
         if (oldItem!=null) {
             String newItemTag = newItem.getUniqueTag();
             if (newItemTag!=null) {
-                // old item has same tag; don't add
-                LOG.warn("Adding to "+this+", "+newItem+" has identical uniqueTag as existing "+oldItem+"; will remove after adding. "
-                    + "Underlying addition should be modified so it is not added twice.");
                 return oldItem;
             }
             if (isRebinding()) {
-                LOG.warn("Adding to "+this+", "+newItem+" appears identical to existing "+oldItem+"; will remove after adding. "
-                    + "Underlying addition should be modified so it is not added twice during rebind.");
+                LOG.warn("Adding to "+this+", "+newItem+" appears identical to existing "+oldItem+"; will replace. "
+                    + "Underlying addition should be modified so it is not added twice during rebind or unique tag should be used to indicate it is identical.");
                 return oldItem;
             } else {
                 LOG.warn("Adding to "+this+", "+newItem+" appears identical to existing "+oldItem+"; may get removed on rebind. "
@@ -1135,8 +1151,8 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
             return null;
         }
     }
-    private <T extends EntityAdjunct> T findApparentlyEquals(Collection<? extends T> itemsCopy, T newItem) {
-        // FIXME workaround for issue where enrichers can get added multiple times on rebind,
+    private <T extends EntityAdjunct> T findApparentlyEqual(Collection<? extends T> itemsCopy, T newItem) {
+        // TODO workaround for issue where enrichers can get added multiple times on rebind,
         // if it's added in onBecomingManager or connectSensors; the right fix will be more disciplined about how/where these are added
         // (easier done when sensor feeds are persisted)
         Class<?> beforeEntityAdjunct = newItem.getClass();
@@ -1157,6 +1173,8 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
                         "transformation",
                         // from averager
                         "values", "timestamps", "lastAverage")) {
+                    
+                    
                     return oldItem;
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
index e82825d..67e4cf5 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
@@ -23,6 +23,8 @@ import java.util.Collection;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
+import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
 import brooklyn.entity.trait.Changeable;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
@@ -43,6 +45,7 @@ import com.google.common.reflect.TypeToken;
  */
 public interface AbstractGroup extends Entity, Group, Changeable {
 
+    @SuppressWarnings("serial")
     AttributeSensor<Collection<Entity>> GROUP_MEMBERS = Sensors.newSensor(
             new TypeToken<Collection<Entity>>() { }, "group.members", "Members of the group");
 
@@ -52,6 +55,13 @@ public interface AbstractGroup extends Entity, Group, Changeable {
     ConfigKey<String> MEMBER_DELEGATE_NAME_FORMAT = ConfigKeys.newStringConfigKey(
             "group.members.delegate.nameFormat", "Delegate members name format string (Use %s for the original entity display name)", "%s");
 
+    public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, 
+        "Up check, applied by default to members, requiring at least one present and up",
+        QuorumChecks.atLeastOne());
+    public static final ConfigKey<QuorumCheck> RUNNING_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK,
+        "Problems check from children actual states (lifecycle), applied by default to members and children, not checking upness, but requiring by default that none are on-fire",
+        QuorumChecks.all());
+
     void setMembers(Collection<Entity> m);
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
index 765d166..e3520f6 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
@@ -89,6 +89,16 @@ public abstract class AbstractGroupImpl extends AbstractEntity implements Abstra
         setAttribute(GROUP_MEMBERS, ImmutableList.<Entity>of());
     }
 
+    @Override
+    protected void initEnrichers() {
+        super.initEnrichers();
+        
+        // problem if any children or members are on fire
+        ServiceStateLogic.newEnricherFromChildrenState().checkChildrenAndMembers().requireRunningChildren(getConfig(RUNNING_QUORUM_CHECK)).addTo(this);
+        // defaults to requiring at least one member or child who is up
+        ServiceStateLogic.newEnricherFromChildrenUp().checkChildrenAndMembers().requireUpChildren(getConfig(UP_QUORUM_CHECK)).addTo(this);
+    }
+
     /**
      * Adds the given entity as a member of this group <em>and</em> this group as one of the groups of the child
      */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/Attributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Attributes.java b/core/src/main/java/brooklyn/entity/basic/Attributes.java
index 4453cad..eaa6143 100644
--- a/core/src/main/java/brooklyn/entity/basic/Attributes.java
+++ b/core/src/main/java/brooklyn/entity/basic/Attributes.java
@@ -45,6 +45,7 @@ public interface Attributes {
     BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
             String.class, "download.url", "URL pattern for downloading the installer (will substitute things like ${version} automatically)");
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     BasicAttributeSensorAndConfigKey<Map<String,String>> DOWNLOAD_ADDON_URLS = new BasicAttributeSensorAndConfigKey(
             Map.class, "download.addon.urls", "URL patterns for downloading named add-ons (will substitute things like ${version} automatically)");
 
@@ -53,9 +54,11 @@ public interface Attributes {
      * Port number attributes.
      */
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     AttributeSensor<List<Integer>> PORT_NUMBERS = new BasicAttributeSensor(
             List.class, "port.list", "List of port numbers");
     
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     AttributeSensor<List<Sensor<Integer>>> PORT_SENSORS = new BasicAttributeSensor(
             List.class, "port.list.sensors", "List of port number attributes");
 
@@ -98,8 +101,20 @@ public interface Attributes {
         "service.notUp.indicators", 
         "A map of namespaced indicators that the service is not up");
     
-    AttributeSensor<Lifecycle> SERVICE_STATE = Sensors.newSensor(Lifecycle.class,
-            "service.state", "Expected lifecycle state of the service");
+    @SuppressWarnings("serial")
+    AttributeSensor<Map<String,Object>> SERVICE_PROBLEMS = Sensors.newSensor(
+        new TypeToken<Map<String,Object>>() {},
+        "service.problems", 
+        "A map of namespaced indicators of problems with a service");
+
+    AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Sensors.newSensor(Lifecycle.class,
+            "service.state", "Actual lifecycle state of the service");
+    AttributeSensor<Lifecycle.Transition> SERVICE_STATE_EXPECTED = Sensors.newSensor(Lifecycle.Transition.class,
+            "service.state.expected", "Last controlled change to service state, indicating what the expected state should be");
+    
+    /** @deprecated since 0.7.0 use {@link #SERVICE_STATE_ACTUAL} or {@link #SERVICE_STATE_EXPECTED} as appropriate. */
+    @Deprecated
+    AttributeSensor<Lifecycle> SERVICE_STATE = SERVICE_STATE_ACTUAL;
 
     /*
      * Other metadata (optional)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java b/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
index b7f6362..b79f4b7 100644
--- a/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
+++ b/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
@@ -132,6 +132,10 @@ public class ConfigKeys {
         return new BasicConfigKeyOverwriting<T>(parent, defaultValue);
     }
 
+    public static <T> ConfigKey<T> newConfigKeyWithDefault(ConfigKey<T> parent, String newDescription, T defaultValue) {
+        return new BasicConfigKeyOverwriting<T>(parent, newDescription, defaultValue);
+    }
+
     public static <T> ConfigKey<T> newConfigKeyRenamed(String newName, ConfigKey<T> key) {
         return new BasicConfigKey<T>(key.getTypeToken(), newName, key.getDescription(), key.getDefaultValue());
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java b/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java
index 537189d..bd2f87e 100644
--- a/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java
+++ b/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java
@@ -35,6 +35,7 @@ import com.google.common.reflect.TypeToken;
 @ImplementedBy(DynamicGroupImpl.class)
 public interface DynamicGroup extends AbstractGroup {
 
+    @SuppressWarnings("serial")
     @SetFromFlag("entityFilter")
     ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() { },
             "dynamicgroup.entityfilter", "Filter for entities which will automatically be in the group");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java
index c69c210..2d5a76f 100644
--- a/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java
@@ -60,7 +60,7 @@ public class DynamicGroupImpl extends AbstractGroupImpl implements DynamicGroup
         super.init();
         setAttribute(RUNNING, true);
     }
-
+    
     @Override
     public void setEntityFilter(Predicate<? super Entity> filter) {
         // TODO Sould this be "evenIfOwned"?

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java b/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java
index af9454c..7ad41f6 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java
@@ -30,49 +30,55 @@ import brooklyn.entity.trait.Identifiable;
 import brooklyn.event.AttributeSensor;
 import brooklyn.management.ManagementContext;
 import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Functionals;
 
 import com.google.common.base.Function;
 import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 
 public class EntityFunctions {
 
     public static <T> Function<Entity, T> attribute(final AttributeSensor<T> attribute) {
-        return new Function<Entity, T>() {
+        class GetEntityAttributeFunction implements Function<Entity, T> {
             @Override public T apply(Entity input) {
                 return (input == null) ? null : input.getAttribute(attribute);
             }
         };
+        return new GetEntityAttributeFunction();
     }
     
     public static <T> Function<Entity, T> config(final ConfigKey<T> key) {
-        return new Function<Entity, T>() {
+        class GetEntityConfigFunction implements Function<Entity, T> {
             @Override public T apply(Entity input) {
                 return (input == null) ? null : input.getConfig(key);
             }
         };
+        return new GetEntityConfigFunction();
     }
     
     public static Function<Entity, String> displayName() {
-        return new Function<Entity, String>() {
+        class GetEntityDisplayName implements Function<Entity, String> {
             @Override public String apply(Entity input) {
                 return (input == null) ? null : input.getDisplayName();
             }
         };
+        return new GetEntityDisplayName();
     }
     
     public static Function<Identifiable, String> id() {
-        return new Function<Identifiable, String>() {
+        class GetIdFunction implements Function<Identifiable, String> {
             @Override public String apply(Identifiable input) {
                 return (input == null) ? null : input.getId();
             }
         };
+        return new GetIdFunction();
     }
 
     /** returns a function which sets the given sensors on the entity passed in,
      * with {@link Entities#UNCHANGED} and {@link Entities#REMOVE} doing those actions. */
     public static Function<Entity,Void> settingSensorsConstant(final Map<AttributeSensor<?>,Object> values) {
         checkNotNull(values, "values");
-        return new Function<Entity,Void>() {
+        class SettingSensorsConstantFunction implements Function<Entity, Void> {
             @SuppressWarnings({ "unchecked", "rawtypes" })
             @Override public Void apply(Entity input) {
                 for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) {
@@ -89,42 +95,37 @@ public class EntityFunctions {
                 }
                 return null;
             }
-        };
+        }
+        return new SettingSensorsConstantFunction();
     }
 
     /** as {@link #settingSensorsConstant(Map)} but as a {@link Runnable} */
-    public static Runnable settingSensorsConstantRunnable(final Entity entity, final Map<AttributeSensor<?>,Object> values) {
+    public static Runnable settingSensorsConstant(final Entity entity, final Map<AttributeSensor<?>,Object> values) {
         checkNotNull(entity, "entity");
         checkNotNull(values, "values");
-        return new Runnable() {
-            @Override
-            public void run() {
-                settingSensorsConstant(values).apply(entity);
-            }
-        };
+        return Functionals.runnable(Suppliers.compose(settingSensorsConstant(values), Suppliers.ofInstance(entity)));
     }
 
-
-    /** as {@link #settingSensorsConstant(Map)} but creating a {@link Function} which ignores its input,
-     * suitable for use with sensor feeds where the input is ignored */
-    public static <T> Function<T,Void> settingSensorsConstantFunction(final Entity entity, final Map<AttributeSensor<?>,Object> values) {
-        checkNotNull(entity, "entity");
-        checkNotNull(values, "values");
-        return new Function<T,Void>() {
-            @Override
-            public Void apply(T input) {
-                return settingSensorsConstant(values).apply(entity);
+    public static <K,V> Function<Entity, Void> updatingSensorMapEntry(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+        class UpdatingSensorMapEntryFunction implements Function<Entity, Void> {
+            @Override public Void apply(Entity input) {
+                ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get());
+                return null;
             }
-        };
+        }
+        return new UpdatingSensorMapEntryFunction();
+    }
+    public static <K,V> Runnable updatingSensorMapEntry(final Entity entity, final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+        return Functionals.runnable(Suppliers.compose(updatingSensorMapEntry(mapSensor, key, valueSupplier), Suppliers.ofInstance(entity)));
     }
 
     public static Supplier<Collection<Application>> applications(final ManagementContext mgmt) {
-        return new Supplier<Collection<Application>>() {
+        class AppsSupplier implements Supplier<Collection<Application>> {
             @Override
             public Collection<Application> get() {
                 return mgmt.getApplications();
             }
-        };
+        }
+        return new AppsSupplier();
     }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
index f0f0a9e..5e7d0fe 100644
--- a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
+++ b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java
@@ -18,26 +18,15 @@
  */
 package brooklyn.entity.basic;
 
+import java.io.Serializable;
+import java.util.Date;
+
 import com.google.common.base.CaseFormat;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 
 /**
  * An enumeration representing the status of an {@link brooklyn.entity.Entity}.
- *
- * @startuml img/entity-lifecycle.png
- * title Entity Lifecycle
- * 
- * (*) ->  "CREATED"
- *     if "Exception" then
- *     ->  "ON_FIRE"
- *     else
- *     --> "STARTING"
- *     --> "RUNNING"
- *     ->  "STOPPING"
- *     --> "STOPPED"
- *     --> "RUNNING"
- *     --> "DESTROYED"
- *     -left-> (*)
- * @enduml
  */
 public enum Lifecycle {
     /**
@@ -51,12 +40,21 @@ public enum Lifecycle {
 
     /**
      * The entity is starting.
-     *
-     * This stage is entered when the {@link brooklyn.entity.trait.Startable#START} {@link brooklyn.entity.Effector} is called. 
-     * The entity will have its location set and and setup helper object created.
+     * <p>
+     * This stage is typically entered when the {@link brooklyn.entity.trait.Startable#START} {@link brooklyn.entity.Effector} 
+     * is called, to undertake the startup operations from the management plane.
+     * When this completes the entity will normally transition to 
+     * {@link Lifecycle#RUNNING}. 
      */
+//    * {@link Lifecycle#STARTED} or 
     STARTING,
 
+//    /**
+//     * The entity has been started and no further start-up steps are needed from the management plane,
+//     * but the entity has not yet been confirmed as running.
+//     */
+//    STARTED,
+//
     /**
      * The entity service is expected to be running. In healthy operation, {@link Attributes#SERVICE_UP} will be true,
      * or will shortly be true if all service start actions have been completed and we are merely waiting for it to be running. 
@@ -121,4 +119,41 @@ public enum Lifecycle {
           return ON_FIRE;
        }
     }
+    
+    public static class Transition implements Serializable {
+        private static final long serialVersionUID = 603419184398753502L;
+        
+        final Lifecycle state;
+        final long timestampUtc;
+        
+        public Transition(Lifecycle state, Date timestamp) {
+            this.state = Preconditions.checkNotNull(state, "state");
+            this.timestampUtc = Preconditions.checkNotNull(timestamp, "timestamp").getTime();
+        }
+        
+        public Lifecycle getState() {
+            return state;
+        }
+        public Date getTimestamp() {
+            return new Date(timestampUtc);
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(state, timestampUtc);
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Transition)) return false;
+            if (!state.equals(((Transition)obj).getState())) return false;
+            if (timestampUtc != ((Transition)obj).timestampUtc) return false;
+            return true;
+        }
+        
+        @Override
+        public String toString() {
+            return state+" @ "+new Date(timestampUtc);
+        }
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/97eed6bd/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
new file mode 100644
index 0000000..bac4515
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
@@ -0,0 +1,74 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import java.io.Serializable;
+
+public interface QuorumCheck {
+
+    public boolean isQuorate(int sizeHealthy, int totalSize);
+
+    public static class QuorumChecks {
+        public static QuorumCheck all() {
+            return new NumericQuorumCheck(0, 1.0, false);
+        }
+        public static QuorumCheck allAndAtLeastOne() {
+            return new NumericQuorumCheck(1, 1.0, false);
+        }
+        public static QuorumCheck atLeastOne() {
+            return new NumericQuorumCheck(1, 0.0, false);
+        }
+        /** require at least one to be up if the total size is non-zero;
+         * ie okay if empty, or if non-empty and something is healthy, but not okay if not-empty and nothing is healthy */
+        public static QuorumCheck atLeastOneUnlessEmpty() {
+            return new NumericQuorumCheck(1, 0.0, true);
+        }
+        public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
+        }
+    }
+    
+    public static class NumericQuorumCheck implements QuorumCheck, Serializable {
+        private static final long serialVersionUID = -5090669237460159621L;
+        
+        protected final int minRequiredSize;
+        protected final double minRequiredRatio;
+        protected final boolean allowEmpty;
+
+        public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            this.minRequiredSize = minRequiredSize;
+            this.minRequiredRatio = minRequiredRatio;
+            this.allowEmpty = allowEmpty;
+        }
+        
+        @Override
+        public boolean isQuorate(int sizeHealthy, int totalSize) {
+            if (allowEmpty && totalSize==0) return true;
+            if (sizeHealthy < minRequiredSize) return false;
+            if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false;
+            return true;
+        }
+        
+        @Override
+        public String toString() {
+            return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%]";
+        }
+    }
+    
+}


[14/26] git commit: fix failing test on service going on_fire not aborting dependent configuration

Posted by he...@apache.org.
fix failing test on service going on_fire not aborting dependent configuration


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/bfb8737e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/bfb8737e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/bfb8737e

Branch: refs/heads/master
Commit: bfb8737e647357053f65d5bd305a4f6783ad2866
Parents: 5ec0a00
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 25 16:35:55 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:18 2014 -0400

----------------------------------------------------------------------
 .../java/brooklyn/event/basic/DependentConfiguration.java    | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bfb8737e/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
index 9d8bf4b..dd73cc8 100644
--- a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
+++ b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
@@ -194,7 +194,15 @@ public class DependentConfiguration {
                             semaphore.release();
                         }
                     }}));
+                Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
+                if (abortCondition.predicate.apply(abortValue)) {
+                    abortion.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                }
+            }
+            if (abortion.size() > 0) {
+                throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortion);
             }
+            
             value = source.getAttribute(sensor);
             while (!ready.apply(value)) {
                 String prevBlockingDetails = current.setBlockingDetails("Waiting for ready from "+source+" "+sensor+" (subscription)");


[19/26] git commit: use initApp for apps to ensure enrichers get added, fixing tests and other misc tidies

Posted by he...@apache.org.
use initApp for apps to ensure enrichers get added, fixing tests and other misc tidies


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5ec0a00b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5ec0a00b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5ec0a00b

Branch: refs/heads/master
Commit: 5ec0a00b1c9deb4ff3b7ccc4dc7e7e6755fded26
Parents: b2daedf
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 25 16:29:52 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Aug 27 02:17:18 2014 -0400

----------------------------------------------------------------------
 .../entity/basic/AbstractApplication.java       |  7 ++++
 .../entity/basic/BasicApplicationImpl.java      |  7 ----
 .../catalog/internal/MyCatalogItems.java        |  4 --
 .../basic/DependentConfigurationTest.java       | 35 +++++++++++++---
 .../entity/rebind/RebindTestFixtureWithApp.java |  3 +-
 .../test/entity/TestApplicationImpl.java        |  5 ---
 .../entity/TestApplicationNoEnrichersImpl.java  | 44 ++++++++++++++++++++
 .../brooklyn/demo/GlobalWebFabricExample.java   |  2 +-
 .../demo/StandaloneQpidBrokerExample.java       |  2 +-
 .../brooklyn/demo/CumulusRDFApplication.java    |  4 +-
 .../demo/HighAvailabilityCassandraCluster.java  |  2 +-
 .../java/brooklyn/demo/ResilientMongoDbApp.java |  2 +-
 .../java/brooklyn/demo/RiakClusterExample.java  |  2 +-
 .../brooklyn/demo/SimpleCassandraCluster.java   |  2 +-
 .../main/java/brooklyn/demo/StormSampleApp.java |  2 +-
 .../brooklyn/demo/WideAreaCassandraCluster.java |  2 +-
 .../brooklyn/demo/NodeJsTodoApplication.java    |  2 +-
 .../brooklyn/demo/SingleWebServerExample.java   |  2 +-
 .../demo/WebClusterDatabaseExample.java         |  4 +-
 .../demo/WebClusterDatabaseExampleApp.java      |  4 +-
 .../demo/WebClusterDatabaseExampleGroovy.groovy |  2 +-
 .../java/brooklyn/demo/WebClusterExample.java   |  2 +-
 .../entity/basic/lifecycle/MyEntityImpl.java    |  3 +-
 .../basic/lifecycle/NaiveScriptRunnerTest.java  |  3 ++
 .../basic/lifecycle/ScriptHelperTest.java       | 13 +++---
 .../brooklyn/entity/pool/ServerPoolTest.java    | 13 +++---
 .../qa/longevity/webcluster/WebClusterApp.java  |  2 +-
 .../rest/testing/mocks/RestMockApp.java         |  8 ----
 .../util/BrooklynRestResourceUtilsTest.java     |  4 --
 29 files changed, 113 insertions(+), 74 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
index da33208..35e0083 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
@@ -57,6 +57,13 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
     public AbstractApplication() {
     }
 
+    public void init() { 
+        initApp();
+        super.init();
+    }
+    
+    protected void initApp() {}
+    
     /**
      * 
      * @deprecated since 0.6; use EntitySpec so no-arg constructor

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java b/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java
index 1dfc7bc..6915556 100644
--- a/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java
@@ -19,11 +19,4 @@
 package brooklyn.entity.basic;
 
 public class BasicApplicationImpl extends AbstractApplication implements BasicApplication {
-    public BasicApplicationImpl() {
-    }
-    
-    @Override
-    public void init() {
-        // no-op
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java b/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java
index 66fe0e6..3506d49 100644
--- a/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java
+++ b/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java
@@ -26,10 +26,6 @@ public class MyCatalogItems {
 
     @Catalog(description="Some silly app test")
     public static class MySillyAppTemplate extends AbstractApplication {
-        @Override
-        public void init() {
-            // no-op
-        }
     }
     
     @Catalog(description="Some silly app builder test")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
index f6096a4..433d677 100644
--- a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java
@@ -29,6 +29,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -38,8 +40,12 @@ import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.basic.DependentConfiguration;
 import brooklyn.management.Task;
 import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.task.BasicTask;
 import brooklyn.util.text.StringPredicates;
 import brooklyn.util.time.Duration;
@@ -58,6 +64,8 @@ import com.google.common.util.concurrent.Callables;
  */
 public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
 
+    private static final Logger log = LoggerFactory.getLogger(DependentConfigurationTest.class);
+    
     public static final int SHORT_WAIT_MS = 100;
     public static final int TIMEOUT_MS = 30*1000;
     
@@ -187,23 +195,38 @@ public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
     }
 
     @Test
-    public void testAttributeWhenReadyAbortsWhenOnfireByDefault() throws Exception {
+    public void testAttributeWhenReadyAbortsWhenOnFireByDefault() {
+        log.info("starting test "+JavaClassNames.niceClassAndMethod());
         final Task<String> t = submit(DependentConfiguration.builder()
                 .attributeWhenReady(entity, TestEntity.NAME)
                 .build());
 
         ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
         try {
             assertDoneEventually(t);
-            fail();
-        } catch (Exception e) {
-            if (!e.toString().contains("Aborted waiting for ready")) throw e;
+            fail("Should have failed already!");
+        } catch (Throwable e) {
+            if (e.toString().contains("Aborted waiting for ready")) 
+                return;
+            
+            log.warn("Did not abort as expected: "+e, e);
+            Entities.dumpInfo(entity);
+            
+            throw Exceptions.propagate(e);
         }
     }
 
+    @Test(invocationCount=100, groups = "Integration")
+    public void testAttributeWhenReadyAbortsWhenOnfireByDefaultManyTimes() {
+        testAttributeWhenReadyAbortsWhenOnFireByDefault();
+    }
+    
     @Test
-    public void testAttributeWhenReadyAbortsWhenAlreadyOnfireByDefault() throws Exception {
+    public void testAttributeWhenReadyAbortsWhenAlreadyOnFireByDefault() throws Exception {
         ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
         
         final Task<String> t = submit(DependentConfiguration.builder()
                 .attributeWhenReady(entity, TestEntity.NAME)
@@ -281,7 +304,7 @@ public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
     
     private <T> T assertDoneEventually(final Task<T> t) throws Exception {
         final AtomicReference<ExecutionException> exception = new AtomicReference<ExecutionException>();
-        T result = Asserts.succeedsEventually(new Callable<T>() {
+        T result = Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Callable<T>() {
             @Override public T call() throws InterruptedException, TimeoutException {
                 try {
                     return t.get(Duration.ONE_SECOND);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java
index 1d54199..81ca411 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java
@@ -21,11 +21,12 @@ package brooklyn.entity.rebind;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestApplicationNoEnrichersImpl;
 
 public class RebindTestFixtureWithApp extends RebindTestFixture<TestApplication> {
 
     protected TestApplication createApp() {
-        return ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class), origManagementContext);
+        return ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class), origManagementContext);
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
index ae5c963..8c7d844 100644
--- a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
@@ -54,11 +54,6 @@ public class TestApplicationImpl extends AbstractApplication implements TestAppl
     }
 
     @Override
-    public void init() {
-        // no-op
-    }
-    
-    @Override
     public <T extends Entity> T createAndManageChild(EntitySpec<T> spec) {
         if (!getManagementSupport().isDeployed()) throw new IllegalStateException("Entity "+this+" not managed");
         T child = addChild(spec);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java b/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
new file mode 100644
index 0000000..1435443
--- /dev/null
+++ b/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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 brooklyn.test.entity;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEventListener;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.SubscriptionHandle;
+import brooklyn.util.logging.LoggingSetup;
+
+/**
+ * Mock application for testing.
+ */
+public class TestApplicationNoEnrichersImpl extends TestApplicationImpl {
+    
+    protected void initEnrichers() { /* none */ }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java
----------------------------------------------------------------------
diff --git a/examples/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java b/examples/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java
index 004f118..cf4fa24 100644
--- a/examples/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java
+++ b/examples/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java
@@ -82,7 +82,7 @@ public class GlobalWebFabricExample extends AbstractApplication {
         new PortAttributeSensorAndConfigKey(AbstractController.PROXY_HTTP_PORT, PortRanges.fromInteger(80));
     
     @Override
-    public void init() {
+    public void initApp() {
         StringConfigMap config = getManagementContext().getConfig();
         
         GeoscalingDnsService geoDns = addChild(EntitySpec.create(GeoscalingDnsService.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneQpidBrokerExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneQpidBrokerExample.java b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneQpidBrokerExample.java
index e042e0d..57400e3 100644
--- a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneQpidBrokerExample.java
+++ b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneQpidBrokerExample.java
@@ -43,7 +43,7 @@ public class StandaloneQpidBrokerExample extends AbstractApplication {
     public static final String DEFAULT_LOCATION = "localhost";
     
     @Override
-    public void init() {
+    public void initApp() {
         // Configure the Qpid broker entity
     	QpidBroker broker = addChild(EntitySpec.create(QpidBroker.class)
     	        .configure("amqpPort", 5672)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
index eaabaff..c560ca5 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
@@ -114,9 +114,7 @@ public class CumulusRDFApplication extends AbstractApplication {
      * </ul>
      */
     @Override
-    public void init() {
-        super.init();
-        
+    public void initApp() {
         // Cassandra cluster
         EntitySpec<CassandraDatacenter> clusterSpec = EntitySpec.create(CassandraDatacenter.class)
                 .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
index c21c4cf..a105783 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/HighAvailabilityCassandraCluster.java
@@ -55,7 +55,7 @@ public class HighAvailabilityCassandraCluster extends AbstractApplication {
     
     
     @Override
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(CassandraDatacenter.class)
                 .configure(CassandraDatacenter.CLUSTER_NAME, "Brooklyn")
                 .configure(CassandraDatacenter.INITIAL_SIZE, getConfig(CASSANDRA_CLUSTER_SIZE))

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
index 290f25e..4acbbea 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/ResilientMongoDbApp.java
@@ -55,7 +55,7 @@ public class ResilientMongoDbApp extends AbstractApplication implements Startabl
     public static final String DEFAULT_LOCATION = "named:gce-europe-west1";
 
     @Override
-    public void init() {
+    public void initApp() {
         MongoDBReplicaSet rs = addChild(
                 EntitySpec.create(MongoDBReplicaSet.class)
                         .configure(MongoDBReplicaSet.INITIAL_SIZE, 3));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
index 0134e27..58b6f60 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/RiakClusterExample.java
@@ -64,7 +64,7 @@ public class RiakClusterExample extends AbstractApplication {
         Entities.dumpInfo(launcher.getApplications());
     }
 
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(RiakCluster.class)
                 .configure(RiakCluster.INITIAL_SIZE, getConfig(RIAK_RING_SIZE))
                 .configure(RiakCluster.MEMBER_SPEC, EntitySpec.create(RiakNode.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/SimpleCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/SimpleCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/SimpleCassandraCluster.java
index 577a9dc..94f792c 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/SimpleCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/SimpleCassandraCluster.java
@@ -35,7 +35,7 @@ public class SimpleCassandraCluster extends AbstractApplication {
     private static final String DEFAULT_LOCATION = "localhost";
 
     @Override
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(CassandraDatacenter.class)
                 .configure(CassandraDatacenter.INITIAL_SIZE, 1)
                 .configure(CassandraDatacenter.CLUSTER_NAME, "Brooklyn"));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/StormSampleApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/StormSampleApp.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/StormSampleApp.java
index cb70578..732ccd5 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/StormSampleApp.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/StormSampleApp.java
@@ -47,7 +47,7 @@ public class StormSampleApp extends AbstractApplication implements StartableAppl
     public static final String DEFAULT_LOCATION = "named:gce-europe-west1";
 
     @Override
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(StormDeployment.class)
             .configure(StormDeployment.SUPERVISORS_COUNT, 2)
             .configure(StormDeployment.ZOOKEEPERS_COUNT, 1));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
index 8e30fc4..325502f 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/WideAreaCassandraCluster.java
@@ -54,7 +54,7 @@ public class WideAreaCassandraCluster extends AbstractApplication {
     
 	
     @Override
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(CassandraFabric.class)
                 .configure(CassandraDatacenter.CLUSTER_NAME, "Brooklyn")
                 .configure(CassandraDatacenter.INITIAL_SIZE, getConfig(CASSANDRA_CLUSTER_SIZE)) // per location

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java
index 54eb008..2eef128 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java
@@ -42,7 +42,7 @@ import com.google.common.collect.ImmutableMap;
 public class NodeJsTodoApplication extends AbstractApplication implements StartableApplication {
 
     @Override
-    public void init() {
+    public void initApp() {
         RedisStore redis = addChild(EntitySpec.create(RedisStore.class));
 
         addChild(EntitySpec.create(NodeJsWebAppService.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java
index 9cf027f..d974e35 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java
@@ -44,7 +44,7 @@ public class SingleWebServerExample extends AbstractApplication {
     private static final String WAR_PATH = "classpath://hello-world-webapp.war";
 
     @Override
-    public void init() {
+    public void initApp() {
         addChild(EntitySpec.create(JBoss7Server.class)
                 .configure(JavaWebAppService.ROOT_WAR, WAR_PATH)
                 .configure(Attributes.HTTP_PORT, PortRanges.fromString("8080+")));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
index bfd3a1c..fd7c9ae 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java
@@ -69,9 +69,7 @@ public class WebClusterDatabaseExample extends AbstractApplication {
             "appservers.count", "Number of app servers deployed");
 
     @Override
-    public void init() {
-        super.init();
-        
+    public void initApp() {
         MySqlNode mysql = addChild(EntitySpec.create(MySqlNode.class)
                 .configure("creationScriptUrl", DB_SETUP_SQL_URL));
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
index b1d3915..706445f 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
@@ -117,9 +117,7 @@ public class WebClusterDatabaseExampleApp extends AbstractApplication implements
     public static final AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
 
     @Override
-    public void init() {
-        super.init();
-        
+    public void initApp() {
         MySqlNode mysql = addChild(
                 EntitySpec.create(MySqlNode.class)
                         .configure(MySqlNode.CREATION_SCRIPT_URL, Entities.getRequiredUrlConfig(this, DB_SETUP_SQL_URL)));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
index fc58a02..c3f6cc7 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
@@ -57,7 +57,7 @@ public class WebClusterDatabaseExampleGroovy extends AbstractApplication {
     public static final String DB_PASSWORD = "br00k11n";
     
     @Override
-    public void init() {
+    public void initApp() {
         MySqlNode mysql = addChild(MySqlNode,
                 creationScriptUrl: DB_SETUP_SQL_URL);
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java
index 28dab20..6a5d064 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java
@@ -59,7 +59,7 @@ public class WebClusterExample extends AbstractApplication {
     private ControlledDynamicWebAppCluster web;
     
     @Override
-    public void init() {
+    public void initApp() {
         nginxController = addChild(EntitySpec.create(NginxController.class)
                 //.configure("domain", "webclusterexample.brooklyn.local")
                 .configure("port", "8000+"));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
index fcafc19..aa57e19 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
@@ -26,7 +26,6 @@ import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.SoftwareProcessDriver;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
@@ -36,7 +35,7 @@ import brooklyn.util.text.Identifiers;
 
 public class MyEntityImpl extends SoftwareProcessImpl implements MyEntity {
     @Override
-    public Class getDriverInterface() {
+    public Class<?> getDriverInterface() {
         return MyEntityDriver.class;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/NaiveScriptRunnerTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/NaiveScriptRunnerTest.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/NaiveScriptRunnerTest.java
index eda5cbd..15f4fd4 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/NaiveScriptRunnerTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/NaiveScriptRunnerTest.java
@@ -54,6 +54,7 @@ public class NaiveScriptRunnerTest {
     @BeforeMethod
     private void setup() { commands.clear(); }
     
+    @SuppressWarnings("rawtypes")
     private NaiveScriptRunner newMockRunner(final int result) {
         return new NaiveScriptRunner() {
             @Override
@@ -68,6 +69,7 @@ public class NaiveScriptRunnerTest {
         };
     }
 
+    @SuppressWarnings("rawtypes")
     public static NaiveScriptRunner newLocalhostRunner() {
         return new NaiveScriptRunner() {
             LocalhostMachineProvisioningLocation location = new LocalhostMachineProvisioningLocation();
@@ -75,6 +77,7 @@ public class NaiveScriptRunnerTest {
             public int execute(List<String> script, String summaryForLogging) {
                 return execute(new MutableMap(), script, summaryForLogging);
             }
+            @SuppressWarnings("unchecked")
             @Override
             public int execute(Map flags, List<String> script, String summaryForLogging) {
                 try {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
index daa6109..bdb49b7 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/ScriptHelperTest.java
@@ -30,8 +30,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.SoftwareProcessEntityTest;
@@ -83,16 +81,16 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
         // currently, is initially set true after successful start
         Assert.assertTrue(entity.getAttribute(Startable.SERVICE_UP));
         
-//        entity.connectServiceUpIsRunning();
-        
         EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true);
-        log.info("XXX F");
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+        
+        log.debug("up, now cause failure");
+        
         driver.setFailExecution(true);
-        log.info("XXX G");
         EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false);
-        log.info("XXX H");
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, false);
+        
+        log.debug("caught failure, now clear");
         driver.setFailExecution(false);
         EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true);
         EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
@@ -117,7 +115,6 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
             FunctionFeed.builder()
                 .entity(this)
                 .period(Duration.millis(10))
-                .onlyIfServiceUp()
                 .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING)
                     .onException(Functions.constant(Boolean.FALSE))
                     .callable(new Callable<Boolean>() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/software/base/src/test/java/brooklyn/entity/pool/ServerPoolTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/pool/ServerPoolTest.java b/software/base/src/test/java/brooklyn/entity/pool/ServerPoolTest.java
index f565c54..584fbfe 100644
--- a/software/base/src/test/java/brooklyn/entity/pool/ServerPoolTest.java
+++ b/software/base/src/test/java/brooklyn/entity/pool/ServerPoolTest.java
@@ -29,18 +29,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.Test;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Lifecycle;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
-import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestApplication;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
 public class ServerPoolTest extends AbstractServerPoolTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(ServerPoolTest.class);
@@ -59,8 +59,7 @@ public class ServerPoolTest extends AbstractServerPoolTest {
     public void testFailureWhenNotEnoughServersAvailable() {
         TestApplication app = createAppWithChildren(getInitialPoolSize() + 1);
         assertNoMachinesAvailableForApp(app);
-        // Not asserting attr = true because the sensor will probably be null
-        assertFalse(Boolean.TRUE.equals(app.getAttribute(Attributes.SERVICE_UP)));
+        EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/usage/qa/src/main/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java b/usage/qa/src/main/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
index 143dcbc..a223861 100644
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
+++ b/usage/qa/src/main/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
@@ -47,7 +47,7 @@ public class WebClusterApp extends AbstractApplication {
     private static final long loadCyclePeriodMs = 2 * 60 * 1000L;
 
     @Override
-    public void init() {
+    public void initApp() {
         final AttributeSensor<Double> sinusoidalLoad =
                 Sensors.newDoubleSensor("brooklyn.qa.sinusoidalLoad", "Sinusoidal server load");
         AttributeSensor<Double> averageLoad =

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockApp.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockApp.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockApp.java
index 97915c9..186ca76 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockApp.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/RestMockApp.java
@@ -21,12 +21,4 @@ package brooklyn.rest.testing.mocks;
 import brooklyn.entity.basic.AbstractApplication;
 
 public class RestMockApp extends AbstractApplication {
-    
-    public RestMockApp() {
-    }
-
-    @Override
-    public void init() {
-        // no-op
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ec0a00b/usage/rest-server/src/test/java/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java b/usage/rest-server/src/test/java/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
index 58419bb..4c7d16b 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
@@ -110,10 +110,6 @@ public class BrooklynRestResourceUtilsTest {
             description="Application which does nothing, included only as part of the test cases.",
             iconUrl="")
     public static class SampleNoOpApplication extends AbstractApplication implements MyInterface {
-        @Override
-        public void init() {
-            // no-op
-        }
     }
     
     public static class MyPolicy extends AbstractPolicy {