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 <[email protected]> Authored: Tue Aug 26 22:10:47 2014 -0500 Committer: Alex Heneveld <[email protected]> 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);
