From 238fab7f0f520a462175ca88b2a807c081e32145 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 11:16:23 -0400 Subject: [PATCH 01/22] prototype of service_up being derived from service_not_up_indicators --- .../brooklyn/entity/basic/Attributes.java | 6 ++++ .../entity/basic/SoftwareProcess.java | 3 ++ .../entity/basic/SoftwareProcessImpl.java | 34 ++++++++++++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/brooklyn/entity/basic/Attributes.java b/core/src/main/java/brooklyn/entity/basic/Attributes.java index 3752752544..4453cad970 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.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 SERVICE_UP = Sensors.newBooleanSensor("service.isUp", "Whether the service is active and availability (confirmed and monitored)"); + @SuppressWarnings("serial") + AttributeSensor> SERVICE_NOT_UP_INDICATORS = Sensors.newSensor( + new TypeToken>() {}, + "service.notUp.indicators", + "A map of namespaced indicators that the service is not up"); AttributeSensor SERVICE_STATE = Sensors.newSensor(Lifecycle.class, "service.state", "Expected lifecycle state of the service"); 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 368619ddbb..eb906607a9 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 @@ private ChildStartableMode(boolean isDisabled, boolean isBackground, boolean isL public static final AttributeSensor PROVISIONING_LOCATION = new BasicAttributeSensor( MachineProvisioningLocation.class, "softwareservice.provisioningLocation", "Location used to provision a machine where this is running"); + public static final AttributeSensor SERVICE_PROCESS_IS_RUNNING = Sensors.newBooleanSensor("service.process.isRunning", + "Whether the process for the service is confirmed as running"); + public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; public static final AttributeSensor PID_FILE = Sensors.newStringSensor("softwareprocess.pid.file", "PID file"); 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 741cb4dda3..fb872b0a54 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.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.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.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 @@ protected void connectSensors() { * @see #disconnectServiceUpIsRunning() */ protected void connectServiceUpIsRunning() { - serviceUp = FunctionFeed.builder() + serviceProcessIsRunning = FunctionFeed.builder() .entity(this) .period(5000) - .poll(new FunctionPollConfig(SERVICE_UP) + .poll(new FunctionPollConfig(SERVICE_PROCESS_IS_RUNNING) .onException(Functions.constant(Boolean.FALSE)) .callable(new Callable() { public Boolean call() { @@ -159,6 +162,29 @@ public Boolean call() { } })) .build(); + + // FIXME quick-and-dirty change + Function> f = new Function>() { + @Override + public Map apply(Boolean input) { + Map 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.mapSizeEquals(0)) ).build()); } /** @@ -169,7 +195,7 @@ public Boolean call() { * @see #connectServiceUpIsRunning() */ protected void disconnectServiceUpIsRunning() { - if (serviceUp != null) serviceUp.stop(); + if (serviceProcessIsRunning != null) serviceProcessIsRunning.stop(); } /** From 45e3035de648e32487535bf7a8050d5b3926b057 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 15:38:40 -0400 Subject: [PATCH 02/22] add WhenFunctions which allows `Functionals.when(false).value("F")` approach to building up functions --- .../java/brooklyn/util/guava/Functionals.java | 25 +++ .../brooklyn/util/guava/WhenFunctions.java | 190 ++++++++++++++++++ .../brooklyn/util/guava/FunctionalsTest.java | 58 ++++++ .../KeyTransformingLoadingCacheTest.java | 1 - .../util/guava/WhenFunctionsTest.java | 91 +++++++++ 5 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java create mode 100644 utils/common/src/test/java/brooklyn/util/guava/FunctionalsTest.java create mode 100644 utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.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 6eb65e5c71..caebc7bb8b 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 static Function chain(final Function f1, return chain(f1, chain(f2, chain(f3, f4))); } + /** @see WhenFunctions */ + public static WhenFunctionBuilderWhenFirst when(I test) { + return WhenFunctions.when(test); + } + + /** @see WhenFunctions */ + public static WhenFunctionBuilderWhenFirst when(Predicate test) { + return WhenFunctions.when(test); + } + + /** @see WhenFunctions */ + public static WhenFunctionBuilder when(Predicate test, Supplier supplier) { + return WhenFunctions.when(test, supplier); + } + + /** @see WhenFunctions */ + public static WhenFunctionBuilder when(Predicate test, O value) { + return WhenFunctions.when(test, value); + } + } 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 0000000000..50a1e35d8f --- /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 WhenFunctionBuilder newInstance(Class testType, Class returnType) { + return new WhenFunctionBuilder(); + } + + public static WhenFunctionBuilderWhenFirst when(Predicate test) { + return new WhenFunctionBuilderWhenFirst(test); + } + public static WhenFunctionBuilderWhenFirst when(I test) { + return new WhenFunctionBuilderWhenFirst(test); + } + public static WhenFunctionBuilder when(Predicate test, Supplier supplier) { + return new WhenFunctionBuilder().when(test, supplier); + } + public static WhenFunctionBuilder when(Predicate test, O value) { + return new WhenFunctionBuilder().when(test, value); + } + + public static class WhenFunction implements Function { + protected final Map,Supplier> tests = new LinkedHashMap,Supplier>(); + protected Supplier defaultValue = null; + + protected WhenFunction(WhenFunction input) { + this.tests.putAll(input.tests); + this.defaultValue = input.defaultValue; + } + + protected WhenFunction() { + } + + @Override + public O apply(I input) { + for (Map.Entry,Supplier> 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 extends WhenFunction { + protected WhenFunctionBuilder() { super(); } + protected WhenFunctionBuilder(WhenFunction input) { super(input); } + + public WhenFunction build() { + return new WhenFunction(this); + } + + public WhenFunctionBuilder when(Predicate test, Supplier supplier) { + return when(test).value(supplier); + } + + public WhenFunctionBuilder when(Predicate test, O value) { + return when(test).value(value); + } + + public WhenFunctionBuilderWhen when(Predicate test) { + return whenUnchecked(test); + } + public WhenFunctionBuilderWhen when(I test) { + return whenUnchecked(test); + } + @SuppressWarnings("unchecked") + protected WhenFunctionBuilderWhen whenUnchecked(Object test) { + if (!(test instanceof Predicate)) { + test = Predicates.equalTo(test); + } + return new WhenFunctionBuilderWhen(this, (Predicate)test); + } + + public WhenFunctionBuilder defaultValue(O defaultValue) { + return defaultValueUnchecked(defaultValue); + } + public WhenFunctionBuilder defaultValue(Supplier defaultValue) { + return defaultValueUnchecked(defaultValue); + } + @SuppressWarnings("unchecked") + protected WhenFunctionBuilder defaultValueUnchecked(Object defaultValue) { + if (!(defaultValue instanceof Supplier)) { + defaultValue = Suppliers.ofInstance(defaultValue); + } + WhenFunctionBuilder result = new WhenFunctionBuilder(this); + result.defaultValue = (Supplier)defaultValue; + return result; + } + } + + public static class WhenFunctionBuilderWhen { + private WhenFunction input; + private Predicate test; + + private WhenFunctionBuilderWhen(WhenFunction input, Predicate test) { + this.input = input; + this.test = test; + } + + public WhenFunctionBuilder value(O value) { + return valueUnchecked(value); + } + public WhenFunctionBuilder value(Supplier value) { + return valueUnchecked(value); + } + @SuppressWarnings("unchecked") + protected WhenFunctionBuilder valueUnchecked(Object value) { + if (!(value instanceof Supplier)) { + value = Suppliers.ofInstance(value); + } + WhenFunctionBuilder result = new WhenFunctionBuilder(input); + result.tests.put(test, (Supplier) value); + return result; + } + } + + public static class WhenFunctionBuilderWhenFirst { + private Predicate test; + + private WhenFunctionBuilderWhenFirst(Predicate 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) test; + } + } + + public WhenFunctionBuilder value(O value) { + return valueUnchecked(value); + } + public WhenFunctionBuilder value(Supplier value) { + return valueUnchecked(value); + } + @SuppressWarnings("unchecked") + protected WhenFunctionBuilder valueUnchecked(Object value) { + if (!(value instanceof Supplier)) { + value = Suppliers.ofInstance(value); + } + WhenFunctionBuilder result = new WhenFunctionBuilder(); + result.tests.put(test, (Supplier) value); + return result; + } + } + +} 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 0000000000..7e2ab354c7 --- /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(), "?"); + } + +} 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 1f85b2383c..ed3c3adfbf 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.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; public class KeyTransformingLoadingCacheTest { 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 0000000000..2b01b14b70 --- /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 f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?").build(); + checkTF(f, "?"); + } + + @Test + public void testWhenWithoutCast() { + Function 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 f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?"); + WhenFunctionBuilder f2 = f.when(false).value("false").defaultValue("X"); + WhenFunctionBuilder f3 = f2.when(false).value("F"); + checkTF(f, "?"); + checkTF(f3, "X"); + Assert.assertEquals(f2.apply(false), "false"); + } + + static void checkTF(Function f, Object defaultValue) { + Assert.assertEquals(f.apply(true), "T"); + Assert.assertEquals(f.apply(false), "F"); + Assert.assertEquals(f.apply(null), defaultValue); + } + +} From a8bff36ec5cbaaa4c4f10d0adfd464ba8d75b8a7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 17:05:41 -0400 Subject: [PATCH 03/22] 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 --- .../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(-) create mode 100644 core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java index dab423ce4a..6ddc780008 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.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 PropagatorBuilder propagatingAllBut(Iterable> vals public AggregatorBuilder aggregating(AttributeSensor val) { return new ConcreteAggregatorBuilder(val); } + /** creates an {@link UpdatingMap} enricher: + * {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required + **/ + public UpdatingMapBuilder updatingMap(AttributeSensor> target) { + return new UpdatingMapBuilder(target); + } } @@ -117,6 +124,8 @@ public abstract static class AggregatorBuilder 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 val) { this.publishing = (AttributeSensor) checkNotNull(val); @@ -443,6 +452,61 @@ public String toString() { } } + public abstract static class AbstractUpdatingMapBuilder> extends Builder { + protected AttributeSensor> targetSensor; + protected AttributeSensor fromSensor; + protected TKey key; + protected Function computing; + protected Boolean removingIfResultIsNull; + + public AbstractUpdatingMapBuilder(AttributeSensor> target) { + this.targetSensor = target; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public UpdatingMapBuilder from(AttributeSensor fromSensor) { + this.fromSensor = checkNotNull(fromSensor); + return (UpdatingMapBuilder) this; + } + public B computing(Function 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 null 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 { } @@ -482,6 +546,12 @@ public ConcreteTransformerBuilder(AttributeSensor val) { } } + public static class UpdatingMapBuilder extends AbstractUpdatingMapBuilder> { + public UpdatingMapBuilder(AttributeSensor> val) { + super(val); + } + } + protected static T average(Collection vals, Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken type) { Double doubleValueToReportIfNoSensors = (valueToReportIfNoSensors == null) ? null : valueToReportIfNoSensors.doubleValue(); int count = count(vals, defaultValueForUnreportedSensors!=null); @@ -529,4 +599,6 @@ private static Map newIdentityMap(Set keys) { } return result; } + + } 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 0000000000..f85852c5e5 --- /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}. + *

+ * 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 null 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 source sensor type + * @param key type in target sensor map + * @param value type in target sensor map + */ +@SuppressWarnings("serial") +public class UpdatingMap extends AbstractEnricher implements SensorEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(UpdatingMap.class); + + @SetFromFlag("fromSensor") + public static final ConfigKey> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.sourceSensor"); + @SetFromFlag("targetSensor") + public static final ConfigKey> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "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> COMPUTING = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.updatingMap.computing"); + @SetFromFlag("removingIfResultIsNull") + public static final ConfigKey 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 sourceSensor; + protected AttributeSensor> targetSensor; + protected TKey key; + protected Function computing; + protected Boolean removingIfResultIsNull; + + public UpdatingMap() { + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + this.sourceSensor = (AttributeSensor) getRequiredConfig(SOURCE_SENSOR); + this.targetSensor = (AttributeSensor>) 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 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 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); + } + } + +} diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index 8dc9615975..8bfd2bb227 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.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.Iterables; import com.google.common.reflect.TypeToken; +@SuppressWarnings("serial") public class EnrichersTest extends BrooklynAppUnitTestSupport { public static final AttributeSensor NUM1 = Sensors.newIntegerSensor("test.num1"); @@ -54,6 +57,9 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport { public static final AttributeSensor STR2 = Sensors.newStringSensor("test.str2"); public static final AttributeSensor> SET1 = Sensors.newSensor(new TypeToken>() {}, "test.set1", "set1 descr"); public static final AttributeSensor LONG1 = Sensors.newLongSensor("test.long1"); + public static final AttributeSensor> MAP1 = Sensors.newSensor(new TypeToken>() {}, "test.map1", "map1 descr"); + @SuppressWarnings("rawtypes") + public static final AttributeSensor MAP2 = Sensors.newSensor(Map.class, "test.map2"); private TestEntity entity; private TestEntity entity2; @@ -68,6 +74,7 @@ public void setUp() throws Exception { group = app.createAndManageChild(EntitySpec.create(BasicGroup.class)); } + @SuppressWarnings("unchecked") @Test public void testAdding() { entity.addEnricher(Enrichers.builder() @@ -81,6 +88,7 @@ public void testAdding() { EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 5); } + @SuppressWarnings("unchecked") @Test public void testCombiningWithCustomFunction() { entity.addEnricher(Enrichers.builder() @@ -94,6 +102,7 @@ public void testCombiningWithCustomFunction() { 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 void testTransformingCastsResult() { 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 void testAggregatingCastsResult() { .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 void testAggregatingRespectsUnchanged() { 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.of()); + + entity.setAttribute(LONG1, -1L); + EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.of( + LONG1.getName(), "-1 is not allowed")); + + entity.setAttribute(LONG1, 1L); + EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.of()); + } + } 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 fb872b0a54..56bb319eef 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.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 Boolean call() { })) .build(); - // FIXME quick-and-dirty change - Function> f = new Function>() { - @Override - public Map apply(Boolean input) { - Map 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.mapSizeEquals(0)) ).build()); From 141751b9213a28943b90e767349d11973a278083 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 22:04:45 -0400 Subject: [PATCH 04/22] minor test utils fixes, including `assertThat` --- .../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(-) 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 e163fad648..78fefebbff 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"); diff --git a/utils/common/src/main/java/brooklyn/test/Asserts.java b/utils/common/src/main/java/brooklyn/test/Asserts.java index 68a414f5ce..d52c34775d 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 static void assertReturnsEventually(final Runnable r, Duration timeout) t throw new ExecutionException(throwable.get()); } } - + + public static void assertThat(T object, Predicate 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) 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 1e7ffb5d63..07088b5835 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 static Supplier sizeSupplier(final Iterable collection) { public Integer get() { return Iterables.size(collection); } + @Override public String toString() { return "sizeSupplier("+collection+")"; } }; } @@ -51,6 +52,7 @@ public static Function, Integer> sizeFunction() { public Integer apply(Iterable input) { return Iterables.size(input); } + @Override public String toString() { return "sizeFunction"; } }; } @@ -60,6 +62,7 @@ public static Function,Set> keys() { public Set apply(Map input) { return input.keySet(); } + @Override public String toString() { return "keys"; } }; } From 3c714ed12259c3909f0244f83f504e6a62be7bdb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 22:09:15 -0400 Subject: [PATCH 05/22] refactor Enrichers.builder() types to give correct generics for published sensor --- .../java/brooklyn/enricher/Enrichers.java | 132 +++++++++--------- .../java/brooklyn/enricher/EnrichersTest.java | 55 +++++++- 2 files changed, 115 insertions(+), 72 deletions(-) diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java index 6ddc780008..0d3a0d550d 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> { @@ -67,36 +67,46 @@ protected B self() { } } - public abstract static class InitialBuilder> extends Builder { - public PropagatorBuilder propagating(Map, ? extends Sensor> vals) { - return new ConcretePropagatorBuilder(vals); + protected abstract static class AbstractInitialBuilder> extends Builder { + public PropagatorBuilder propagating(Map, ? extends Sensor> vals) { + return new PropagatorBuilder(vals); } - public PropagatorBuilder propagating(Iterable> vals) { - return new ConcretePropagatorBuilder(vals); + public PropagatorBuilder propagating(Iterable> 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> vals) { - return new ConcretePropagatorBuilder(true, vals); + public PropagatorBuilder propagatingAllBut(Iterable> vals) { + return new PropagatorBuilder(true, vals); } - public TransformerBuilder transforming(AttributeSensor val) { - return new ConcreteTransformerBuilder(val); - } - public CombinerBuilder combining(AttributeSensor... vals) { - return new ConcreteCombinerBuilder(vals); - } - public CombinerBuilder combining(Collection> vals) { - return new ConcreteCombinerBuilder(vals); - } - public AggregatorBuilder aggregating(AttributeSensor val) { - return new ConcreteAggregatorBuilder(val); + + /** builds an enricher which transforms a given sensor: + *

  • applying a (required) function ({@link TransformerBuilder#computing(Function)}, or {@link TransformerBuilder#computingAverage()}/{@link TransformerBuilder#computingSum()}, mandatory); + *
  • and publishing it on the entity where the enricher is attached; + *
  • optionally taking the sensor from a different source entity ({@link TransformerBuilder#from(Entity)}); + *
  • and optionally publishing it as a different sensor ({@link TransformerBuilder#publishing(AttributeSensor)}); + *

    (You must supply at least one of the optional values, of course, otherwise the enricher may loop endlessly!) */ + public TransformerBuilder transforming(AttributeSensor val) { + return new TransformerBuilder(val); + } + /** as {@link #transforming(AttributeSensor)} but accepting multiple sensors, with the function acting on the set of values */ + public CombinerBuilder combining(Collection> vals) { + return new CombinerBuilder(vals); + } + /** as {@link #combining(Collection)} */ + public CombinerBuilder combining(AttributeSensor... vals) { + return new CombinerBuilder(vals); + } + /** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */ + public AggregatorBuilder aggregating(AttributeSensor val) { + return new AggregatorBuilder(val); } /** creates an {@link UpdatingMap} enricher: * {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required @@ -107,7 +117,7 @@ public UpdatingMapBuilder updatingMap(AttributeSens } - public abstract static class AggregatorBuilder> extends Builder { + protected abstract static class AbstractAggregatorBuilder> extends Builder { protected final AttributeSensor aggregating; protected AttributeSensor publishing; protected Entity fromEntity; @@ -121,15 +131,13 @@ public abstract static class AggregatorBuilder aggregating) { + public AbstractAggregatorBuilder(AttributeSensor 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 val) { + public AggregatorBuilder publishing(AttributeSensor 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 String toString() { } } - public abstract static class CombinerBuilder> extends Builder { + protected abstract static class AbstractCombinerBuilder> extends Builder { protected final List> combining; protected AttributeSensor publishing; protected Entity fromEntity; @@ -248,17 +256,17 @@ public abstract static class CombinerBuilder... vals) { + public AbstractCombinerBuilder(AttributeSensor... vals) { this(ImmutableList.copyOf(vals)); } - public CombinerBuilder(Collection> vals) { + public AbstractCombinerBuilder(Collection> vals) { checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty"); this.combining = ImmutableList.>copyOf(vals); } @SuppressWarnings({ "unchecked", "rawtypes" }) - public B publishing(AttributeSensor val) { + public CombinerBuilder publishing(AttributeSensor 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 String toString() { } } - /** builds an enricher which transforms a given sensor: - *

  • applying a function ({@link #computing(Function)}, or {@link #computingAverage()}/{@link #computingSum()}, mandatory); - *
  • and publishing it on the entity where the enricher is attached; - *
  • optionally taking the sensor from a different source entity ({@link #from(Entity)}); - *
  • and optionally publishing it as a different sensor ({@link #publishing(AttributeSensor)}); - *

    (You should supply at least one of the optional values, of course, otherwise the enricher may loop endlessly!) */ - public abstract static class TransformerBuilder> extends Builder { + protected abstract static class AbstractTransformerBuilder> extends Builder { protected final AttributeSensor transforming; protected AttributeSensor publishing; protected Entity fromEntity; protected Function computing; protected Function, ?> computingFromEvent; - public TransformerBuilder(AttributeSensor val) { + public AbstractTransformerBuilder(AttributeSensor val) { this.transforming = checkNotNull(val); } @SuppressWarnings({ "unchecked", "rawtypes" }) - public B publishing(AttributeSensor val) { + public TransformerBuilder publishing(AttributeSensor 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 String toString() { } } - public abstract static class PropagatorBuilder> extends Builder { + protected abstract static class AbstractPropagatorBuilder> extends Builder { protected final Map, ? extends Sensor> propagating; protected final Boolean propagatingAll; protected final Iterable> propagatingAllBut; protected Entity fromEntity; - public PropagatorBuilder(Map, ? extends Sensor> vals) { + public AbstractPropagatorBuilder(Map, ? 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> vals) { + public AbstractPropagatorBuilder(Iterable> vals) { this(newIdentityMap(ImmutableSet.copyOf(vals))); } - public PropagatorBuilder(Sensor... vals) { + public AbstractPropagatorBuilder(Sensor... vals) { this(newIdentityMap(ImmutableSet.copyOf(vals))); } - public PropagatorBuilder(boolean propagatingAll, Iterable> butVals) { + public AbstractPropagatorBuilder(boolean propagatingAll, Iterable> 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 String toString() { } } - private static class ConcreteInitialBuilder extends InitialBuilder { + public static class InitialBuilder extends AbstractInitialBuilder { } - private static class ConcreteAggregatorBuilder extends AggregatorBuilder> { - public ConcreteAggregatorBuilder(AttributeSensor aggregating) { + public static class AggregatorBuilder extends AbstractAggregatorBuilder> { + public AggregatorBuilder(AttributeSensor aggregating) { super(aggregating); } } - private static class ConcretePropagatorBuilder extends PropagatorBuilder { - public ConcretePropagatorBuilder(Map, ? extends Sensor> vals) { + public static class PropagatorBuilder extends AbstractPropagatorBuilder { + public PropagatorBuilder(Map, ? extends Sensor> vals) { super(vals); } - public ConcretePropagatorBuilder(Iterable> vals) { + public PropagatorBuilder(Iterable> vals) { super(vals); } - public ConcretePropagatorBuilder(Sensor... vals) { + public PropagatorBuilder(Sensor... vals) { super(vals); } - public ConcretePropagatorBuilder(boolean propagatingAll, Iterable> butVals) { + public PropagatorBuilder(boolean propagatingAll, Iterable> butVals) { super(propagatingAll, butVals); } } - private static class ConcreteCombinerBuilder extends CombinerBuilder> { - public ConcreteCombinerBuilder(AttributeSensor... vals) { + public static class CombinerBuilder extends AbstractCombinerBuilder> { + public CombinerBuilder(AttributeSensor... vals) { super(vals); } - public ConcreteCombinerBuilder(Collection> vals) { + public CombinerBuilder(Collection> vals) { super(vals); } } - private static class ConcreteTransformerBuilder extends TransformerBuilder> { - public ConcreteTransformerBuilder(AttributeSensor val) { + public static class TransformerBuilder extends AbstractTransformerBuilder> { + public TransformerBuilder(AttributeSensor val) { super(val); } } diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index 8bfd2bb227..ab3f1991ff 100644 --- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java +++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java @@ -28,12 +28,15 @@ 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 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 void testCombiningWithCustomFunction() { public void testCombiningRespectsUnchanged() { entity.addEnricher(Enrichers.builder() .combining(NUM1, NUM2) - .publishing(NUM3) + .publishing(NUM3) .computing(new Function, Object>() { @Override public Object apply(Iterable input) { if (input != null && Iterables.contains(input, 123)) { @@ -156,7 +160,7 @@ public void testTransformingCastsResult() { 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 void testTransformingFromEvent() { } @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) + .publishing(STR2) .computing(new Function() { @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.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 void testAggregatingCastsResult() { .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 void testAggregatingRespectsUnchanged() { group.addMember(entity); group.addEnricher(Enrichers.builder() .aggregating(NUM1) - .publishing(LONG1) + .publishing(LONG1) .fromMembers() .computing(new Function, Object>() { @Override public Object apply(Iterable input) { From 41deca4d9428867d9c1d54ec3e5334fe183cfea2 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 22:38:11 -0400 Subject: [PATCH 06/22] 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 --- .../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(-) diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java index 0d3a0d550d..23b1b837aa 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.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.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 @@ protected B self() { } } + public abstract static class AbstractEnricherBuilder> extends Builder { + final Class enricherType; + Boolean suppressDuplicates; + String uniqueTag; + Set tags = MutableSet.of(); + + public AbstractEnricherBuilder(Class 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 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> extends Builder { public PropagatorBuilder propagating(Map, ? extends Sensor> vals) { return new PropagatorBuilder(vals); @@ -117,7 +162,7 @@ public UpdatingMapBuilder updatingMap(AttributeSens } - protected abstract static class AbstractAggregatorBuilder> extends Builder { + protected abstract static class AbstractAggregatorBuilder> extends AbstractEnricherBuilder { protected final AttributeSensor aggregating; protected AttributeSensor publishing; protected Entity fromEntity; @@ -132,6 +177,7 @@ protected abstract static class AbstractAggregatorBuilder aggregating) { + super(Aggregator.class); this.aggregating = aggregating; } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -195,6 +241,11 @@ public B excludingBlank() { this.excludingBlank = true; return self(); } + @Override + protected String getDefaultUniqueTag() { + if (publishing==null) return null; + return "aggregator:"+publishing.getName(); + } public EnricherSpec build() { Predicate valueFilter; if (Boolean.TRUE.equals(excludingBlank)) { @@ -208,9 +259,7 @@ public EnricherSpec build() { 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 String toString() { } } - protected abstract static class AbstractCombinerBuilder> extends Builder { + protected abstract static class AbstractCombinerBuilder> extends AbstractEnricherBuilder { protected final List> combining; protected AttributeSensor publishing; protected Entity fromEntity; @@ -260,6 +309,7 @@ public AbstractCombinerBuilder(AttributeSensor... vals) { this(ImmutableList.copyOf(vals)); } public AbstractCombinerBuilder(Collection> vals) { + super(Combiner.class); checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty"); this.combining = ImmutableList.>copyOf(vals); } @@ -306,10 +356,13 @@ public B excludingBlank() { 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 String toString() { } } - protected abstract static class AbstractTransformerBuilder> extends Builder { + protected abstract static class AbstractTransformerBuilder> extends AbstractEnricherBuilder { protected final AttributeSensor transforming; protected AttributeSensor publishing; protected Entity fromEntity; @@ -341,6 +394,7 @@ protected abstract static class AbstractTransformerBuilder, ?> computingFromEvent; public AbstractTransformerBuilder(AttributeSensor val) { + super(Transformer.class); this.transforming = checkNotNull(val); } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -360,10 +414,13 @@ public B computingFromEvent(Function, ? extends T> val) { 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 String toString() { } } - protected abstract static class AbstractPropagatorBuilder> extends Builder { + protected abstract static class AbstractPropagatorBuilder> extends AbstractEnricherBuilder { protected final Map, ? extends Sensor> propagating; protected final Boolean propagatingAll; protected final Iterable> propagatingAllBut; protected Entity fromEntity; public AbstractPropagatorBuilder(Map, ? 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 AbstractPropagatorBuilder(Iterable> vals) { public AbstractPropagatorBuilder(Sensor... vals) { this(newIdentityMap(ImmutableSet.copyOf(vals))); } - public AbstractPropagatorBuilder(boolean propagatingAll, Iterable> butVals) { + AbstractPropagatorBuilder(boolean propagatingAll, Iterable> 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 B from(Entity val) { this.fromEntity = checkNotNull(val); return self(); } - public EnricherSpec build() { + @Override + protected String getDefaultUniqueTag() { List summary = MutableList.of(); if (propagating!=null) { for (Map.Entry, ? extends Sensor> entry: propagating.entrySet()) { @@ -432,9 +492,10 @@ public EnricherSpec build() { 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 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 String toString() { } } - public abstract static class AbstractUpdatingMapBuilder> extends Builder { + public abstract static class AbstractUpdatingMapBuilder> extends AbstractEnricherBuilder { protected AttributeSensor> targetSensor; protected AttributeSensor fromSensor; protected TKey key; @@ -462,6 +523,7 @@ public abstract static class AbstractUpdatingMapBuilder> target) { + super(UpdatingMap.class); this.targetSensor = target; } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -484,10 +546,13 @@ public B removingIfResultIsNull(boolean val) { 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 PropagatorBuilder(Iterable> vals) { public PropagatorBuilder(Sensor... vals) { super(vals); } - public PropagatorBuilder(boolean propagatingAll, Iterable> butVals) { + PropagatorBuilder(boolean propagatingAll, Iterable> butVals) { super(propagatingAll, butVals); } } diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java index 9cba5b9095..52a924a3a3 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 @@ */ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements Enricher { + public static final ConfigKey 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()); @@ -60,9 +72,32 @@ public EnricherType getEnricherType() { return enricherType.getSnapshot(); } + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + this.suppressDuplicates = getConfig(SUPPRESS_DUPLICATES); + } + @Override protected void onChanged() { requestPersist(); } + + @Override + protected void emit(Sensor 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)sensor); + if (Objects.equal(oldValue, val)) + return; + } + entity.setAttribute((AttributeSensor)sensor, val); + } else { + entity.emit(sensor, val); + } + + } } diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java index 768e7c5e2e..6877ec9f5a 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 extends AbstractEnricher implements SensorEventLis public static ConfigKey> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.sourceSensor"); public static ConfigKey> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.targetSensor"); - + protected Function, ? extends U> transformation; protected Entity producer; protected Sensor sourceSensor; @@ -65,6 +65,7 @@ public Transformer() { @Override public void setEntity(EntityLocal entity) { super.setEntity(entity); + final Function transformationFromValue = (Function) getConfig(TRANSFORMATION_FROM_VALUE); final Function, ? extends U> transformationFromEvent = (Function, ? 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 void onEvent(SensorEvent event) { 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); } } diff --git a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java index f85852c5e5..60bbe4b1b0 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.flags.SetFromFlag; import com.google.common.base.Function; +import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; /** @@ -47,7 +48,8 @@ * with default behaviour being to remove an entry if null 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 extends AbstractEnricher implements Sensor protected Boolean removingIfResultIsNull; public UpdatingMap() { + this(Maps.newLinkedHashMap()); + } + + public UpdatingMap(Map flags) { + super(flags); + if (suppressDuplicates==null) { + // this defaults to suppressing duplicates + suppressDuplicates = true; + } } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java index 4797b592a4..126968eb46 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.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 void setEntity(EntityLocal entity) { this.entity = entity; } + /** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */ protected void emit(Sensor sensor, T val) { checkState(entity != null, "entity must first be set"); if (sensor instanceof AttributeSensor) { diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index ab3f1991ff..4257c36893 100644 --- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java +++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java @@ -214,28 +214,27 @@ public void testTransformingRespectsUnchangedButWillRepublish() { 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.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.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() { From 843f1fcaa28331e5b3e9b3325899366d0588601f Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 23:47:59 -0400 Subject: [PATCH 07/22] simple cleaned up illustration of service_up from not_up computation --- .../entity/basic/ServiceStatusLogic.java | 73 +++++++++++++++++++ .../entity/basic/SoftwareProcessImpl.java | 13 ++-- .../entity/webapp/jboss/JBoss7ServerImpl.java | 16 ++-- 3 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 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 0000000000..056334ccd9 --- /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 SERVICE_UP = Attributes.SERVICE_UP; + public static final AttributeSensor> 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.mapSizeEquals(0)) ) + .uniqueTag("service.isUp if no service.notUp.indicators") + .build(); + } + + @SuppressWarnings("unchecked") + public static void updateMapSensor(EntityLocal entity, AttributeSensor> sensor, + TKey key, Object v) { + Map 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); + } + +} 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 56bb319eef..e376bee3cc 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.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 @@ protected void connectSensors() { protected void connectServiceUpIsRunning() { serviceProcessIsRunning = FunctionFeed.builder() .entity(this) - .period(5000) + .period(Duration.FIVE_SECONDS) .poll(new FunctionPollConfig(SERVICE_PROCESS_IS_RUNNING) .onException(Functions.constant(Boolean.FALSE)) .callable(new Callable() { @@ -165,12 +164,11 @@ public Boolean call() { 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.mapSizeEquals(0)) ).build()); + + addEnricher(ServiceStatusLogic.newEnricherForServiceUpIfNoNotUpIndicators()); } /** @@ -182,6 +180,7 @@ public Boolean call() { */ 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"); } /** 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 2cb1303881..05421d0d54 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 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.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 @@ protected void connectSensors() { } 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 From 94184b2f65bfe8a00fd6fe170f22c276ad9b29db Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 6 Aug 2014 23:48:26 -0400 Subject: [PATCH 08/22] a bunch more service-up checks (WIP) --- .../basic/lifecycle/ScriptHelperTest.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) 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 ef61fb83a0..9894a9eaf3 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.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 MyServiceInessentialDriverImpl(Entity parent) { @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(SERVICE_UP) - .onException(Functions.constant(Boolean.FALSE)) - .callable(new Callable() { - public Boolean call() { - return getDriver().isRunning(); - } - })) - .build(); + .entity(this) + .period(Duration.millis(10)) + .poll(new FunctionPollConfig(SERVICE_PROCESS_IS_RUNNING) + .onException(Functions.constant(Boolean.FALSE)) + .callable(new Callable() { + public Boolean call() { + return getDriver().isRunning(); + } + })) + .build(); } } From c1ebb8f44cb3baa24f05021b262b9bf25c864f39 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 7 Aug 2014 23:37:51 -0400 Subject: [PATCH 09/22] fix enricher type test with suppress duplicates --- core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java b/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java index 4b9744ba72..ec8525a84e 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 void tearDown() throws Exception { @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); From bc1d19f755bc32a5ed5ec84ba50198fa39ba47bb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 12 Aug 2014 10:50:46 -0400 Subject: [PATCH 10/22] rename WhenFunctions class to IfFunctions, and tidies/cleanups there and in enrichers --- .../brooklyn/entity/basic/EntityLocal.java | 1 - .../enricher/basic/AbstractEnricher.java | 20 +- .../AbstractTypeTransformingEnricher.java | 1 + .../brooklyn/enricher/basic/Aggregator.java | 12 +- .../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 +- .../collections/CollectionFunctionals.java | 97 ++++++--- .../java/brooklyn/util/guava/Functionals.java | 53 +++-- .../java/brooklyn/util/guava/IfFunctions.java | 158 +++++++++++++++ .../brooklyn/util/guava/WhenFunctions.java | 190 ------------------ .../CollectionFunctionalsTest.java | 51 +++++ .../brooklyn/util/guava/FunctionalsTest.java | 18 +- .../brooklyn/util/guava/IfFunctionsTest.java | 101 ++++++++++ .../util/guava/WhenFunctionsTest.java | 91 --------- 22 files changed, 536 insertions(+), 406 deletions(-) create mode 100644 utils/common/src/main/java/brooklyn/util/guava/IfFunctions.java delete mode 100644 utils/common/src/main/java/brooklyn/util/guava/WhenFunctions.java create mode 100644 utils/common/src/test/java/brooklyn/util/collections/CollectionFunctionalsTest.java create mode 100644 utils/common/src/test/java/brooklyn/util/guava/IfFunctionsTest.java delete mode 100644 utils/common/src/test/java/brooklyn/util/guava/WhenFunctionsTest.java diff --git a/api/src/main/java/brooklyn/entity/basic/EntityLocal.java b/api/src/main/java/brooklyn/entity/basic/EntityLocal.java index c8a18b5586..b38a3d8d75 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.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, diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java index 52a924a3a3..d462a0c1dc 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 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.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 @@ protected void onChanged() { } @Override - protected void emit(Sensor sensor, T val) { + protected void emit(Sensor sensor, Object val) { checkState(entity != null, "entity must first be set"); + if (val == Entities.UNCHANGED) { + return; + } + if (val == Entities.REMOVE) { + ((EntityInternal)entity).removeAttribute((AttributeSensor) sensor); + return; + } + T newVal = TypeCoercions.coerce(val, sensor.getTypeToken()); if (sensor instanceof AttributeSensor) { if (Boolean.TRUE.equals(suppressDuplicates)) { T oldValue = entity.getAttribute((AttributeSensor)sensor); - if (Objects.equal(oldValue, val)) + if (Objects.equal(oldValue, newVal)) return; } - entity.setAttribute((AttributeSensor)sensor, val); + entity.setAttribute((AttributeSensor)sensor, newVal); } else { - entity.emit(sensor, val); + entity.emit(sensor, newVal); } - } } diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractTypeTransformingEnricher.java index 05f96cd698..27eac93724 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 AbstractTypeTransformingEnricher(Entity producer, Sensor source, Senso this.target = target; } + @SuppressWarnings({ "unchecked", "rawtypes" }) public void setEntity(EntityLocal entity) { super.setEntity(entity); if (producer==null) producer = entity; diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java index f5d503f1a3..fed403e7f1 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.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.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.collect.Iterables; import com.google.common.reflect.TypeToken; +@SuppressWarnings("serial") public class Aggregator extends AbstractEnricher implements SensorEventListener { private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class); @@ -98,7 +97,7 @@ public class Aggregator 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 void onEvent(SensorEvent event) { */ 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); diff --git a/core/src/main/java/brooklyn/enricher/basic/Combiner.java b/core/src/main/java/brooklyn/enricher/basic/Combiner.java index a49cf87514..6876be23c8 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 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.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.collect.Iterables; import com.google.common.reflect.TypeToken; +@SuppressWarnings("serial") public class Combiner extends AbstractEnricher implements SensorEventListener { private static final Logger LOG = LoggerFactory.getLogger(Combiner.class); @@ -121,12 +120,7 @@ public void onEvent(SensorEvent event) { */ 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); diff --git a/core/src/main/java/brooklyn/enricher/basic/Propagator.java b/core/src/main/java/brooklyn/enricher/basic/Propagator.java index 7e06aa728e..267ba88abe 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.Maps; import com.google.common.reflect.TypeToken; +@SuppressWarnings("serial") public class Propagator extends AbstractEnricher implements SensorEventListener { - @SuppressWarnings("unused") private static final Logger LOG = LoggerFactory.getLogger(Propagator.class); @SetFromFlag("producer") @@ -126,6 +126,7 @@ public void setEntity(EntityLocal entity) { emitAllAttributes(); } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void onEvent(SensorEvent event) { // propagate upwards @@ -147,6 +148,7 @@ public void emitAllAttributes() { emitAllAttributes(false); } + @SuppressWarnings({ "rawtypes", "unchecked" }) public void emitAllAttributes(boolean includeNullValues) { Iterable> sensorsToPopulate = propagatingAll ? Iterables.filter(producer.getEntityType().getSensors(), sensorFilter) diff --git a/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java b/core/src/main/java/brooklyn/enricher/basic/SensorPropagatingEnricher.java index dfaa60fb48..8e863f772c 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 void emitAllAttributes() { emitAllAttributes(false); } + @SuppressWarnings({ "rawtypes", "unchecked" }) public void emitAllAttributes(boolean includeNullValues) { for (Sensor s: sensors) { if (s instanceof AttributeSensor) { diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java index 6877ec9f5a..c517e881e7 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 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 void setEntity(EntityLocal entity) { @Override public void onEvent(SensorEvent 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 event) { diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java index 056334ccd9..8f4c6b40fb 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 @@ 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 SERVICE_UP = Attributes.SERVICE_UP; public static final AttributeSensor> 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.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.> + ifNotEquals(null).apply(Functions.forPredicate(CollectionFunctionals.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 void updateMapSensor(EntityLocal entity, AttributeSensor> sensor, diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java index 126968eb46..68f81625e4 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.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 void setEntity(EntityLocal entity) { } /** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */ - protected void emit(Sensor sensor, T val) { + protected void emit(Sensor sensor, Object val) { checkState(entity != null, "entity must first be set"); + if (val == Entities.UNCHANGED) { + return; + } + if (val == Entities.REMOVE) { + ((EntityInternal)entity).removeAttribute((AttributeSensor) sensor); + return; + } + + T newVal = TypeCoercions.coerce(val, sensor.getTypeToken()); if (sensor instanceof AttributeSensor) { - entity.setAttribute((AttributeSensor)sensor, val); + entity.setAttribute((AttributeSensor)sensor, newVal); } else { - entity.emit(sensor, val); + entity.emit(sensor, newVal); } } diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index 4257c36893..66d1bdd455 100644 --- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java +++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java @@ -396,7 +396,7 @@ public void testUpdatingMap1() { 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 void testUpdatingMap2() { 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); 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 e376bee3cc..25c23dfefa 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 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 @@ protected MachineLocation getMachineOrNull() { 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 Boolean call() { } })) .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 Boolean call() { */ 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); } /** 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 9894a9eaf3..6edd9a9f4e 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.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.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 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport { + private static final Logger log = LoggerFactory.getLogger(ScriptHelperTest.class); + private SshMachineLocation machine; private FixedListMachineProvisioningLocation loc; boolean shouldFail = false; @@ -65,34 +72,47 @@ public void setUp() throws Exception { @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) 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 05421d0d54..3c8e464d30 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.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 JBoss7ServerImpl(){ 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 @@ protected void connectServiceUp() { 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()); } 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 07088b5835..f7b1dd0178 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 @@ * @author alex */ public class CollectionFunctionals { + private static final class EqualsSetPredicate implements Predicate> { + 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 implements Function, Set> { + @Override + public Set apply(Map input) { + if (input==null) return null; + return input.keySet(); + } + + @Override public String toString() { return "keys"; } + } + + private static final class SizeSupplier implements Supplier { + 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, 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 sizeSupplier(final Iterable collection) { - return new Supplier() { - @Override - public Integer get() { - return Iterables.size(collection); - } - @Override public String toString() { return "sizeSupplier("+collection+")"; } - }; + return new SizeSupplier(collection); } - public static Function, Integer> sizeFunction() { - return new Function, Integer>() { - @Override - public Integer apply(Iterable input) { - return Iterables.size(input); - } - @Override public String toString() { return "sizeFunction"; } - }; + public static Function, Integer> sizeFunction() { return sizeFunction(null); } + + public static Function, Integer> sizeFunction(final Integer valueIfInputNull) { + return new SizeFunction(valueIfInputNull); } public static Function,Set> keys() { - return new Function, Set>() { - @Override - public Set apply(Map input) { - return input.keySet(); - } - @Override public String toString() { return "keys"; } - }; + return new KeysOfMapFunction(); } public static Function, Integer> mapSize() { - return Functions.compose(CollectionFunctionals.sizeFunction(), CollectionFunctionals.keys()); + return mapSize(null); + } + + public static Function, Integer> mapSize(Integer valueIfNull) { + return Functions.compose(CollectionFunctionals.sizeFunction(valueIfNull), CollectionFunctionals.keys()); } /** default guava Equals predicate will reflect order of target, and will fail when matching against a list; @@ -76,13 +119,7 @@ public static Predicate> equalsSetOf(Object... target) { return equalsSet(Arrays.asList(target)); } public static Predicate> equalsSet(final Iterable target) { - return new Predicate>() { - @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> sizeEquals(int targetSize) { 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 caebc7bb8b..a93c551601 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 static Function chain(final Function f1, return chain(f1, chain(f2, chain(f3, f4))); } - /** @see WhenFunctions */ - public static WhenFunctionBuilderWhenFirst when(I test) { - return WhenFunctions.when(test); - } - - /** @see WhenFunctions */ - public static WhenFunctionBuilderWhenFirst when(Predicate test) { - return WhenFunctions.when(test); + /** @see IfFunctions */ + public static IfFunctionBuilderApplyingFirst ifEquals(I test) { + return IfFunctions.ifEquals(test); } - /** @see WhenFunctions */ - public static WhenFunctionBuilder when(Predicate test, Supplier supplier) { - return WhenFunctions.when(test, supplier); + /** @see IfFunctions */ + public static IfFunctionBuilderApplyingFirst ifNotEquals(I test) { + return IfFunctions.ifNotEquals(test); } - /** @see WhenFunctions */ - public static WhenFunctionBuilder when(Predicate test, O value) { - return WhenFunctions.when(test, value); + /** @see IfFunctions */ + public static IfFunctionBuilderApplyingFirst ifPredicate(Predicate 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 implements Function { + 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(); + } + } + } 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 0000000000..5384436b3a --- /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 IfFunctionBuilder newInstance(Class testType, Class returnType) { + return new IfFunctionBuilder(); + } + + public static IfFunctionBuilderApplyingFirst ifPredicate(Predicate test) { + return new IfFunctionBuilderApplyingFirst(test); + } + public static IfFunctionBuilderApplyingFirst ifEquals(I test) { + return ifPredicate(Predicates.equalTo(test)); + } + public static IfFunctionBuilderApplyingFirst ifNotEquals(I test) { + return ifPredicate(Predicates.not(Predicates.equalTo(test))); + } + + @Beta + public static class IfFunction implements Function { + protected final Map,Function> tests = new LinkedHashMap,Function>(); + protected Function defaultFunction = null; + + protected IfFunction(IfFunction input) { + this.tests.putAll(input.tests); + this.defaultFunction = input.defaultFunction; + } + + protected IfFunction() { + } + + @Override + public O apply(I input) { + for (Map.Entry,Function> 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 extends IfFunction { + protected IfFunctionBuilder() { super(); } + protected IfFunctionBuilder(IfFunction input) { super(input); } + + public IfFunction build() { + return new IfFunction(this); + } + + public IfFunctionBuilderApplying ifPredicate(Predicate test) { + return new IfFunctionBuilderApplying(this, (Predicate)test); + } + public IfFunctionBuilderApplying ifEquals(I test) { + return ifPredicate(Predicates.equalTo(test)); + } + public IfFunctionBuilderApplying ifNotEquals(I test) { + return ifPredicate(Predicates.not(Predicates.equalTo(test))); + } + + public IfFunctionBuilder defaultValue(O defaultValue) { + return defaultApply(new Functionals.ConstantFunction(defaultValue, defaultValue)); + } + @SuppressWarnings("unchecked") + public IfFunctionBuilder defaultGet(Supplier defaultSupplier) { + return defaultApply((Function)Functions.forSupplier(defaultSupplier)); + } + public IfFunctionBuilder defaultApply(Function defaultFunction) { + IfFunctionBuilder result = new IfFunctionBuilder(this); + result.defaultFunction = defaultFunction; + return result; + } + } + + @Beta + public static class IfFunctionBuilderApplying { + private IfFunction input; + private Predicate test; + + private IfFunctionBuilderApplying(IfFunction input, Predicate test) { + this.input = input; + this.test = test; + } + + public IfFunctionBuilder value(O value) { + return apply(new Functionals.ConstantFunction(value, value)); + } + @SuppressWarnings("unchecked") + public IfFunctionBuilder get(Supplier supplier) { + return apply((Function)Functions.forSupplier(supplier)); + } + public IfFunctionBuilder apply(Function function) { + IfFunctionBuilder result = new IfFunctionBuilder(input); + result.tests.put(test, function); + return result; + } + } + + @Beta + public static class IfFunctionBuilderApplyingFirst { + private Predicate test; + + private IfFunctionBuilderApplyingFirst(Predicate test) { + this.test = test; + } + + public IfFunctionBuilder value(O value) { + return apply(new Functionals.ConstantFunction(value, value)); + } + @SuppressWarnings("unchecked") + public IfFunctionBuilder get(Supplier supplier) { + return apply((Function)Functions.forSupplier(supplier)); + } + public IfFunctionBuilder apply(Function function) { + IfFunctionBuilder result = new IfFunctionBuilder(); + result.tests.put(test, function); + return result; + } + } + +} 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 50a1e35d8f..0000000000 --- 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 WhenFunctionBuilder newInstance(Class testType, Class returnType) { - return new WhenFunctionBuilder(); - } - - public static WhenFunctionBuilderWhenFirst when(Predicate test) { - return new WhenFunctionBuilderWhenFirst(test); - } - public static WhenFunctionBuilderWhenFirst when(I test) { - return new WhenFunctionBuilderWhenFirst(test); - } - public static WhenFunctionBuilder when(Predicate test, Supplier supplier) { - return new WhenFunctionBuilder().when(test, supplier); - } - public static WhenFunctionBuilder when(Predicate test, O value) { - return new WhenFunctionBuilder().when(test, value); - } - - public static class WhenFunction implements Function { - protected final Map,Supplier> tests = new LinkedHashMap,Supplier>(); - protected Supplier defaultValue = null; - - protected WhenFunction(WhenFunction input) { - this.tests.putAll(input.tests); - this.defaultValue = input.defaultValue; - } - - protected WhenFunction() { - } - - @Override - public O apply(I input) { - for (Map.Entry,Supplier> 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 extends WhenFunction { - protected WhenFunctionBuilder() { super(); } - protected WhenFunctionBuilder(WhenFunction input) { super(input); } - - public WhenFunction build() { - return new WhenFunction(this); - } - - public WhenFunctionBuilder when(Predicate test, Supplier supplier) { - return when(test).value(supplier); - } - - public WhenFunctionBuilder when(Predicate test, O value) { - return when(test).value(value); - } - - public WhenFunctionBuilderWhen when(Predicate test) { - return whenUnchecked(test); - } - public WhenFunctionBuilderWhen when(I test) { - return whenUnchecked(test); - } - @SuppressWarnings("unchecked") - protected WhenFunctionBuilderWhen whenUnchecked(Object test) { - if (!(test instanceof Predicate)) { - test = Predicates.equalTo(test); - } - return new WhenFunctionBuilderWhen(this, (Predicate)test); - } - - public WhenFunctionBuilder defaultValue(O defaultValue) { - return defaultValueUnchecked(defaultValue); - } - public WhenFunctionBuilder defaultValue(Supplier defaultValue) { - return defaultValueUnchecked(defaultValue); - } - @SuppressWarnings("unchecked") - protected WhenFunctionBuilder defaultValueUnchecked(Object defaultValue) { - if (!(defaultValue instanceof Supplier)) { - defaultValue = Suppliers.ofInstance(defaultValue); - } - WhenFunctionBuilder result = new WhenFunctionBuilder(this); - result.defaultValue = (Supplier)defaultValue; - return result; - } - } - - public static class WhenFunctionBuilderWhen { - private WhenFunction input; - private Predicate test; - - private WhenFunctionBuilderWhen(WhenFunction input, Predicate test) { - this.input = input; - this.test = test; - } - - public WhenFunctionBuilder value(O value) { - return valueUnchecked(value); - } - public WhenFunctionBuilder value(Supplier value) { - return valueUnchecked(value); - } - @SuppressWarnings("unchecked") - protected WhenFunctionBuilder valueUnchecked(Object value) { - if (!(value instanceof Supplier)) { - value = Suppliers.ofInstance(value); - } - WhenFunctionBuilder result = new WhenFunctionBuilder(input); - result.tests.put(test, (Supplier) value); - return result; - } - } - - public static class WhenFunctionBuilderWhenFirst { - private Predicate test; - - private WhenFunctionBuilderWhenFirst(Predicate 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) test; - } - } - - public WhenFunctionBuilder value(O value) { - return valueUnchecked(value); - } - public WhenFunctionBuilder value(Supplier value) { - return valueUnchecked(value); - } - @SuppressWarnings("unchecked") - protected WhenFunctionBuilder valueUnchecked(Object value) { - if (!(value instanceof Supplier)) { - value = Suppliers.ofInstance(value); - } - WhenFunctionBuilder result = new WhenFunctionBuilder(); - result.tests.put(test, (Supplier) value); - return result; - } - } - -} 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 0000000000..6611d9b3d8 --- /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.mapSizeEquals(2).apply(ImmutableMap.of("x", "1", "y", "2"))); + Assert.assertFalse(CollectionFunctionals.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)); + } + +} 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 7e2ab354c7..3da3532fc4 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 void testChain() { } @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"); } } 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 0000000000..74a5d5bc0b --- /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() { + @Override + public String apply(Boolean input) { + return input.toString().toUpperCase().substring(0, 1); + } + }).defaultValue("?"), "?"); + } + + @Test + public void testWithCast() { + Function f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(); + checkTF(f, "?"); + } + + @Test + public void testWithoutCast() { + Function 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 f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?"); + IfFunctionBuilder f2 = f.ifEquals(false).value("false").defaultValue("X"); + IfFunctionBuilder f3 = f2.ifEquals(false).value("F"); + checkTF(f, "?"); + checkTF(f3, "X"); + Assert.assertEquals(f2.apply(false), "false"); + } + + static void checkTF(Function f, Object defaultValue) { + Assert.assertEquals(f.apply(true), "T"); + Assert.assertEquals(f.apply(false), "F"); + Assert.assertEquals(f.apply(null), defaultValue); + } + +} 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 2b01b14b70..0000000000 --- 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 f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?").build(); - checkTF(f, "?"); - } - - @Test - public void testWhenWithoutCast() { - Function 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 f = WhenFunctions.when(false).value("F").when(true).value("T").defaultValue("?"); - WhenFunctionBuilder f2 = f.when(false).value("false").defaultValue("X"); - WhenFunctionBuilder f3 = f2.when(false).value("F"); - checkTF(f, "?"); - checkTF(f3, "X"); - Assert.assertEquals(f2.apply(false), "false"); - } - - static void checkTF(Function f, Object defaultValue) { - Assert.assertEquals(f.apply(true), "T"); - Assert.assertEquals(f.apply(false), "F"); - Assert.assertEquals(f.apply(null), defaultValue); - } - -} From 97eed6bdc159ddcba231fc0183635317101ec7b9 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Aug 2014 07:23:01 +0100 Subject: [PATCH 11/22] 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. --- .../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 +- .../java/brooklyn/policy/EnricherSpec.java | 70 ++- .../main/java/brooklyn/policy/PolicySpec.java | 2 +- .../java/brooklyn/enricher/Enrichers.java | 3 + .../enricher/basic/AbstractAggregator.java | 215 +++++++++ .../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 + .../brooklyn/entity/basic/Attributes.java | 19 +- .../brooklyn/entity/basic/ConfigKeys.java | 4 + .../brooklyn/entity/basic/DynamicGroup.java | 1 + .../entity/basic/DynamicGroupImpl.java | 2 +- .../entity/basic/EntityFunctions.java | 55 +-- .../java/brooklyn/entity/basic/Lifecycle.java | 73 ++- .../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 +- .../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 +- .../event/feed/http/HttpFeedTest.java | 5 +- .../EntityCleanupLongevityTestFixture.java | 2 +- .../brooklyn/test/entity/TestEntityImpl.java | 2 +- .../entity/TestEntityNoEnrichersImpl.java | 32 ++ .../brooklyn/demo/CumulusRDFApplication.java | 12 +- .../demo/WebClusterDatabaseExample.java | 2 + .../demo/WebClusterDatabaseExampleApp.java | 2 + .../basic/AbstractSoftwareProcessDriver.java | 6 +- .../entity/basic/SameServerEntity.java | 2 +- .../entity/basic/SoftwareProcess.java | 2 +- ...reProcessDriverLifecycleEffectorTasks.java | 3 +- .../entity/basic/SoftwareProcessImpl.java | 36 +- .../BrooklynEntityMirrorImpl.java | 11 +- .../MachineLifecycleEffectorTasks.java | 30 +- .../basic/lifecycle/ScriptHelperTest.java | 1 + .../entity/java/VanillaJavaAppTest.java | 4 +- .../entity/messaging/jms/JMSBrokerImpl.java | 13 +- .../zookeeper/ZooKeeperEnsembleImpl.java | 22 +- .../cassandra/CassandraDatacenterImpl.java | 21 +- .../nosql/cassandra/CassandraFabricImpl.java | 6 +- .../nosql/couchbase/CouchbaseClusterImpl.java | 40 +- .../nosql/couchdb/CouchDBClusterImpl.java | 21 +- .../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 -- .../proxy/nginx/NginxControllerImpl.java | 6 +- .../entity/proxy/nginx/NginxSshDriver.java | 2 +- .../ControlledDynamicWebAppCluster.java | 2 +- .../ControlledDynamicWebAppClusterImpl.java | 71 +-- .../webapp/DynamicWebAppClusterImpl.java | 41 -- .../entity/webapp/jetty/Jetty6ServerImpl.java | 2 +- .../nginx/NginxRebindIntegrationTest.java | 3 +- .../ControlledDynamicWebAppClusterTest.java | 2 +- .../transform/ApplicationTransformer.java | 2 +- .../java/brooklyn/util/guava/Functionals.java | 31 ++ 78 files changed, 1476 insertions(+), 717 deletions(-) create mode 100644 core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java create mode 100644 core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java create mode 100644 core/src/main/java/brooklyn/entity/basic/QuorumCheck.java create mode 100644 core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java delete mode 100644 core/src/main/java/brooklyn/entity/basic/ServiceStatusLogic.java create mode 100644 core/src/test/java/brooklyn/test/entity/TestEntityNoEnrichersImpl.java diff --git a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java index dc6642f929..0efb0a6480 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 type; + private final Class type; private String displayName; private Set tags = MutableSet.of(); - protected AbstractBrooklynObjectSpec(Class type) { + protected AbstractBrooklynObjectSpec(Class type) { checkValidType(type); this.type = type; } @SuppressWarnings("unchecked") - protected final K self() { + protected K self() { return (K) this; } @@ -52,7 +52,7 @@ public String toString() { return Objects.toStringHelper(this).add("type", getType()).toString(); } - protected abstract void checkValidType(Class type); + protected abstract void checkValidType(Class type); public K displayName(String val) { displayName = val; @@ -71,14 +71,14 @@ public K tags(Iterable tagsToAdd) { } /** - * @return The type of the enricher + * @return The type of the object (or significant interface) */ - public final Class getType() { + public Class getType() { return type; } /** - * @return The display name of the enricher + * @return The display name of the object */ public final String getDisplayName() { return displayName; diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java index bcc8a58998..88a10fa253 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. diff --git a/api/src/main/java/brooklyn/entity/Group.java b/api/src/main/java/brooklyn/entity/Group.java index f99b3854b6..f2cd0b8d55 100644 --- a/api/src/main/java/brooklyn/entity/Group.java +++ b/api/src/main/java/brooklyn/entity/Group.java @@ -30,6 +30,7 @@ * 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. */ diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java index 74102665c3..4ed953a070 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 EntitySpec(Class type) { super(type); } + @SuppressWarnings("unchecked") + public Class getType() { + return (Class)super.getType(); + } + @Override - protected void checkValidType(Class type) { + protected void checkValidType(Class type) { // EntitySpec does nothing. Other specs do check it's an implementation etc. } diff --git a/api/src/main/java/brooklyn/location/LocationSpec.java b/api/src/main/java/brooklyn/location/LocationSpec.java index f34d1a28a0..6cd26ab191 100644 --- a/api/src/main/java/brooklyn/location/LocationSpec.java +++ b/api/src/main/java/brooklyn/location/LocationSpec.java @@ -83,7 +83,7 @@ protected LocationSpec(Class type) { super(type); } - protected void checkValidType(java.lang.Class type) { + protected void checkValidType(Class type) { checkIsImplementation(type, Location.class); checkIsNewStyleImplementation(type); } diff --git a/api/src/main/java/brooklyn/policy/EnricherSpec.java b/api/src/main/java/brooklyn/policy/EnricherSpec.java index f2cfdd44d9..e2f6c2270f 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 extends AbstractBrooklynObjectSpec * * @param type A {@link Enricher} class */ - public static EnricherSpec create(Class type) { + public static EnricherSpec create(Class type) { return new EnricherSpec(type); } @@ -68,18 +68,18 @@ public static EnricherSpec create(Class type) { * @param config The spec's configuration (see {@link EnricherSpec#configure(Map)}). * @param type An {@link Enricher} class */ - public static EnricherSpec create(Map config, Class type) { + public static EnricherSpec create(Map config, Class type) { return EnricherSpec.create(type).configure(config); } private final Map flags = Maps.newLinkedHashMap(); private final Map, Object> config = Maps.newLinkedHashMap(); - protected EnricherSpec(Class type) { + protected EnricherSpec(Class type) { super(type); } - protected void checkValidType(Class type) { + protected void checkValidType(Class type) { checkIsImplementation(type, Enricher.class); checkIsNewStyleImplementation(type); } @@ -149,4 +149,66 @@ public Map, Object> getConfig() { return Collections.unmodifiableMap(config); } + public abstract static class ExtensibleEnricherSpec> extends EnricherSpec { + private static final long serialVersionUID = -3649347642882809739L; + + protected ExtensibleEnricherSpec(Class 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 K configure(ConfigKey key, V val) { + super.configure(key, val); + return self(); + } + + @Override + public K configureIfNotNull(ConfigKey key, V val) { + super.configureIfNotNull(key, val); + return self(); + } + + @Override + public K configure(ConfigKey key, Task val) { + super.configure(key, val); + return self(); + } + + @Override + public K configure(HasConfigKey key, V val) { + super.configure(key, val); + return self(); + } + + @Override + public K configure(HasConfigKey key, Task val) { + super.configure(key, val); + return self(); + } + } } diff --git a/api/src/main/java/brooklyn/policy/PolicySpec.java b/api/src/main/java/brooklyn/policy/PolicySpec.java index 13206cf049..4c0665bcea 100644 --- a/api/src/main/java/brooklyn/policy/PolicySpec.java +++ b/api/src/main/java/brooklyn/policy/PolicySpec.java @@ -79,7 +79,7 @@ protected PolicySpec(Class type) { super(type); } - protected void checkValidType(Class type) { + protected void checkValidType(Class type) { checkIsImplementation(type, Policy.class); checkIsNewStyleImplementation(type); } diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java index 23b1b837aa..73b7a3b2f4 100644 --- a/core/src/main/java/brooklyn/enricher/Enrichers.java +++ b/core/src/main/java/brooklyn/enricher/Enrichers.java @@ -125,6 +125,9 @@ public PropagatorBuilder propagating(Sensor... vals) { public PropagatorBuilder propagatingAll() { return new PropagatorBuilder(true, null); } + public PropagatorBuilder propagatingAllButUsualAnd(Sensor... vals) { + return new PropagatorBuilder(true, ImmutableSet.>builder().addAll(Propagator.SENSORS_NOT_USUALLY_PROPAGATED).add(vals).build()); + } public PropagatorBuilder propagatingAllBut(Sensor... vals) { return new PropagatorBuilder(true, ImmutableSet.copyOf(vals)); } 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 0000000000..ba7762ce04 --- /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 extends AbstractEnricher implements SensorEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractAggregator.class); + + public static final ConfigKey PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer", "The entity whose children/members will be aggregated"); + + public static final ConfigKey> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "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> FROM_HARDCODED_PRODUCERS = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.fromHardcodedProducers"); + + public static final ConfigKey FROM_MEMBERS = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromMembers"); + + public static final ConfigKey FROM_CHILDREN = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromChildren"); + + public static final ConfigKey> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.entityFilter"); + + public static final ConfigKey> VALUE_FILTER = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.valueFilter"); + + protected Entity producer; + protected Sensor targetSensor; + protected T defaultMemberValue; + protected Set fromHardcodedProducers; + protected Boolean fromMembers; + protected Boolean fromChildren; + protected Predicate entityFilter; + protected Predicate 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) (getConfig(ENTITY_FILTER) == null ? Predicates.alwaysTrue() : getConfig(ENTITY_FILTER)); + this.valueFilter = (Predicate) (getConfig(VALUE_FILTER) == null ? Predicates.alwaysTrue() : getConfig(VALUE_FILTER)); + + setEntityLoadingTargetConfig(); + } + @SuppressWarnings({ "unchecked" }) + protected void setEntityLoadingTargetConfig() { + this.targetSensor = (Sensor) 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() { + @Override public void onEvent(SensorEvent event) { + if (entityFilter.apply(event.getValue())) { + addProducerMember(event.getValue()); + onUpdated(); + } + } + }); + subscribe(producer, Changeable.MEMBER_REMOVED, new SensorEventListener() { + @Override public void onEvent(SensorEvent 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() { + @Override public void onEvent(SensorEvent event) { + removeProducer(event.getValue()); + onUpdated(); + } + }); + subscribe(producer, AbstractEntity.CHILD_ADDED, new SensorEventListener() { + @Override public void onEvent(SensorEvent 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(); + +} 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 0000000000..85c36d852e --- /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 extends AbstractAggregator implements SensorEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractMultipleSensorAggregator.class); + + + /** access via {@link #getValues(Sensor)} */ + private final Map> values = Collections.synchronizedMap(new LinkedHashMap>()); + + public AbstractMultipleSensorAggregator() {} + + protected abstract Collection> 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 vs = values.get(sensor.getName()); + if (vs==null) { + vs = new LinkedHashMap(); + 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 vs = values.get(sensor.getName()); + if (vs!=null) + vs.remove(producer); + } + } + onUpdated(); + } + + @Override + public void onEvent(SensorEvent event) { + Entity e = event.getSource(); + synchronized (values) { + Map 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 Map getValues(Sensor sensor) { + synchronized (values) { + Map sv = (Map) values.get(sensor.getName()); + if (sv==null) return ImmutableMap.of(); + return MutableMap.copyOf(sv).asUnmodifiable(); + } + } + + protected abstract Object compute(); +} diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java index fed403e7f1..034a6047f3 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.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 extends AbstractEnricher implements SensorEventListener { +public class Aggregator extends AbstractAggregator implements SensorEventListener { private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class); - public static final ConfigKey, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken, ?>>() {}, "enricher.transformation"); - - public static final ConfigKey PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer"); - public static final ConfigKey> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.sourceSensor"); + public static final ConfigKey, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken, ?>>() {}, "enricher.transformation"); - public static final ConfigKey> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.targetSensor"); - - public static final ConfigKey DEFAULT_MEMBER_VALUE = ConfigKeys.newConfigKey(Object.class, "enricher.defaultMemberValue"); - - public static final ConfigKey> FROM_HARDCODED_PRODUCERS = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.fromHardcodedProducers"); - - public static final ConfigKey FROM_MEMBERS = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromMembers"); - - public static final ConfigKey FROM_CHILDREN = ConfigKeys.newBooleanConfigKey("enricher.aggregating.fromChildren"); - - public static final ConfigKey> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.entityFilter"); - - public static final ConfigKey> VALUE_FILTER = ConfigKeys.newConfigKey(new TypeToken>() {}, "enricher.aggregating.valueFilter"); - - protected Function, ? extends U> transformation; - protected Entity producer; protected Sensor sourceSensor; - protected Sensor targetSensor; - protected T defaultMemberValue; - protected Set fromHardcodedProducers; - protected Boolean fromMembers; - protected Boolean fromChildren; - protected Predicate entityFilter; - protected Predicate valueFilter; + protected Function, ? 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 extends AbstractEnricher implements SensorEventList // We use a synchronizedMap over a ConcurrentHashMap for entities that store null values. protected final Map values = Collections.synchronizedMap(new LinkedHashMap()); - public Aggregator() { - } + public Aggregator() {} - @SuppressWarnings({ "unchecked" }) - @Override - public void setEntity(EntityLocal entity) { - super.setEntity(entity); - this.transformation = (Function, ? extends U>) getRequiredConfig(TRANSFORMATION); - this.producer = getConfig(PRODUCER); - this.fromHardcodedProducers= getConfig(FROM_HARDCODED_PRODUCERS); + @SuppressWarnings("unchecked") + protected void setEntityLoadingConfig() { + super.setEntityLoadingConfig(); this.sourceSensor = (Sensor) getRequiredConfig(SOURCE_SENSOR); - this.targetSensor = (Sensor) getRequiredConfig(TARGET_SENSOR); - this.defaultMemberValue = (T) getConfig(DEFAULT_MEMBER_VALUE); - this.fromMembers = getConfig(FROM_MEMBERS); - this.fromChildren = getConfig(FROM_CHILDREN); - this.entityFilter = (Predicate) (getConfig(ENTITY_FILTER) == null ? Predicates.alwaysTrue() : getConfig(ENTITY_FILTER)); - this.valueFilter = (Predicate) (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() { - @Override public void onEvent(SensorEvent event) { - if (entityFilter.apply(event.getValue())) addProducer(event.getValue()); - } - }); - subscribe(producer, Changeable.MEMBER_REMOVED, new SensorEventListener() { - @Override public void onEvent(SensorEvent event) { - removeProducer(event.getValue()); - } - }); - - if (producer instanceof Group) { - for (Entity member : Iterables.filter(((Group)producer).getMembers(), entityFilter)) { - addProducer(member); - } - } - onUpdated(); - } + this.transformation = (Function, ? 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() { - @Override public void onEvent(SensorEvent event) { - onProducerRemoved(event.getValue()); - } - }); - subscribe(producer, AbstractEntity.CHILD_ADDED, new SensorEventListener() { - @Override public void onEvent(SensorEvent 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 @@ protected void onProducerAdded(Entity producer, boolean update) { 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 event) { Entity e = event.getSource(); @@ -231,9 +133,6 @@ public void onEvent(SensorEvent event) { 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 @@ protected Map copyOfValues() { return Collections.unmodifiableMap(MutableMap.copyOf(values)); } } + } diff --git a/core/src/main/java/brooklyn/enricher/basic/Propagator.java b/core/src/main/java/brooklyn/enricher/basic/Propagator.java index 267ba88abe..7aca9ca8e8 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.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> SENSORS_NOT_USUALLY_PROPAGATED = ImmutableSet.>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 PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer"); @@ -112,7 +118,7 @@ public void setEntity(EntityLocal entity) { }; } - 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) { diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java index b7edf59075..da33208367 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 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; @@ -74,11 +76,6 @@ public AbstractApplication(Map properties, Entity parent) { super(properties, parent); } - @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) { @@ -115,6 +112,15 @@ public AbstractApplication setParent(Entity parent) { 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)), * calling preStart(locations) first and postStart(locations) afterwards. @@ -123,23 +129,25 @@ public AbstractApplication setParent(Entity parent) { public void start(Collection locations) { this.addLocations(locations); Collection 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 void stop() { 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) { diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index 97d7b4e1ab..eb5f09975b 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.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 @@ protected ToStringHelper toStringHelper() { // .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). + *

    + * 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 Collection getPolicies() { } @Override - public Policy addPolicy(Policy policy) { - List old = MutableList.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 addPolicy(PolicySpec spec) { T policy = getManagementContext().getEntityManager().createPolicy(spec); - return (T) addPolicy(policy); + addPolicy(policy); + return policy; } - @SuppressWarnings("unchecked") @Override public T addEnricher(EnricherSpec spec) { T enricher = getManagementContext().getEntityManager().createEnricher(spec); - return (T) addEnricher(enricher); + addEnricher(enricher); + return enricher; } @Override @@ -1094,37 +1116,31 @@ public Collection getEnrichers() { } @Override - public Enricher addEnricher(Enricher enricher) { - List old = MutableList.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 findApparentlyEqualsAndWarn(Collection items, T newItem) { - T oldItem = findApparentlyEquals(items, newItem); + private T findApparentlyEqualAndWarnIfNotSameUniqueTag(Collection 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 @@ private T findApparentlyEqualsAndWarn(Collection T findApparentlyEquals(Collection itemsCopy, T newItem) { - // FIXME workaround for issue where enrichers can get added multiple times on rebind, + private T findApparentlyEqual(Collection 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 @@ private T findApparentlyEquals(Collection "transformation", // from averager "values", "timestamps", "lastAverage")) { + + return oldItem; } } diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java index e82825d1f4..67e4cf5112 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 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 @@ */ public interface AbstractGroup extends Entity, Group, Changeable { + @SuppressWarnings("serial") AttributeSensor> GROUP_MEMBERS = Sensors.newSensor( new TypeToken>() { }, "group.members", "Members of the group"); @@ -52,6 +55,13 @@ public interface AbstractGroup extends Entity, Group, Changeable { ConfigKey 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 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 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 m); /** diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java index 765d1661b4..e3520f6300 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 void init() { setAttribute(GROUP_MEMBERS, ImmutableList.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 and this group as one of the groups of the child */ diff --git a/core/src/main/java/brooklyn/entity/basic/Attributes.java b/core/src/main/java/brooklyn/entity/basic/Attributes.java index 4453cad970..eaa614394e 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 DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey( String.class, "download.url", "URL pattern for downloading the installer (will substitute things like ${version} automatically)"); + @SuppressWarnings({ "unchecked", "rawtypes" }) BasicAttributeSensorAndConfigKey> 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> PORT_NUMBERS = new BasicAttributeSensor( List.class, "port.list", "List of port numbers"); + @SuppressWarnings({ "unchecked", "rawtypes" }) AttributeSensor>> 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 SERVICE_STATE = Sensors.newSensor(Lifecycle.class, - "service.state", "Expected lifecycle state of the service"); + @SuppressWarnings("serial") + AttributeSensor> SERVICE_PROBLEMS = Sensors.newSensor( + new TypeToken>() {}, + "service.problems", + "A map of namespaced indicators of problems with a service"); + + AttributeSensor SERVICE_STATE_ACTUAL = Sensors.newSensor(Lifecycle.class, + "service.state", "Actual lifecycle state of the service"); + AttributeSensor 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 SERVICE_STATE = SERVICE_STATE_ACTUAL; /* * Other metadata (optional) diff --git a/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java b/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java index b7f636250c..b79f4b79dc 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 static ConfigKey newConfigKeyWithDefault(ConfigKey parent, T de return new BasicConfigKeyOverwriting(parent, defaultValue); } + public static ConfigKey newConfigKeyWithDefault(ConfigKey parent, String newDescription, T defaultValue) { + return new BasicConfigKeyOverwriting(parent, newDescription, defaultValue); + } + public static ConfigKey newConfigKeyRenamed(String newName, ConfigKey key) { return new BasicConfigKey(key.getTypeToken(), newName, key.getDescription(), key.getDefaultValue()); } diff --git a/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java b/core/src/main/java/brooklyn/entity/basic/DynamicGroup.java index 537189d1a8..bd2f87e98f 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 @@ @ImplementedBy(DynamicGroupImpl.class) public interface DynamicGroup extends AbstractGroup { + @SuppressWarnings("serial") @SetFromFlag("entityFilter") ConfigKey> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken>() { }, "dynamicgroup.entityfilter", "Filter for entities which will automatically be in the group"); diff --git a/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/DynamicGroupImpl.java index c69c210787..2d5a76fab2 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 void init() { super.init(); setAttribute(RUNNING, true); } - + @Override public void setEntityFilter(Predicate filter) { // TODO Sould this be "evenIfOwned"? diff --git a/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java b/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java index af9454cc70..7ad41f62ac 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.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 Function attribute(final AttributeSensor attribute) { - return new Function() { + class GetEntityAttributeFunction implements Function { @Override public T apply(Entity input) { return (input == null) ? null : input.getAttribute(attribute); } }; + return new GetEntityAttributeFunction(); } public static Function config(final ConfigKey key) { - return new Function() { + class GetEntityConfigFunction implements Function { @Override public T apply(Entity input) { return (input == null) ? null : input.getConfig(key); } }; + return new GetEntityConfigFunction(); } public static Function displayName() { - return new Function() { + class GetEntityDisplayName implements Function { @Override public String apply(Entity input) { return (input == null) ? null : input.getDisplayName(); } }; + return new GetEntityDisplayName(); } public static Function id() { - return new Function() { + class GetIdFunction implements Function { @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 settingSensorsConstant(final Map,Object> values) { checkNotNull(values, "values"); - return new Function() { + class SettingSensorsConstantFunction implements Function { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Void apply(Entity input) { for (Map.Entry,Object> entry : values.entrySet()) { @@ -89,42 +95,37 @@ public static Function settingSensorsConstant(final Map,Object> values) { + public static Runnable settingSensorsConstant(final Entity entity, final Map,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 Function settingSensorsConstantFunction(final Entity entity, final Map,Object> values) { - checkNotNull(entity, "entity"); - checkNotNull(values, "values"); - return new Function() { - @Override - public Void apply(T input) { - return settingSensorsConstant(values).apply(entity); + public static Function updatingSensorMapEntry(final AttributeSensor> mapSensor, final K key, final Supplier valueSupplier) { + class UpdatingSensorMapEntryFunction implements Function { + @Override public Void apply(Entity input) { + ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get()); + return null; } - }; + } + return new UpdatingSensorMapEntryFunction(); + } + public static Runnable updatingSensorMapEntry(final Entity entity, final AttributeSensor> mapSensor, final K key, final Supplier valueSupplier) { + return Functionals.runnable(Suppliers.compose(updatingSensorMapEntry(mapSensor, key, valueSupplier), Suppliers.ofInstance(entity))); } public static Supplier> applications(final ManagementContext mgmt) { - return new Supplier>() { + class AppsSupplier implements Supplier> { @Override public Collection get() { return mgmt.getApplications(); } - }; + } + return new AppsSupplier(); } - } diff --git a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java index f0f0a9ec59..5e7d0fe15d 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. + *

    + * 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 static Lifecycle fromValue(String v) { 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 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 0000000000..bac451579d --- /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)+"%]"; + } + } + +} 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 0000000000..de4f1ad22b --- /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 SERVICE_UP = Attributes.SERVICE_UP; + public static final AttributeSensor> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS; + + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; + public static final AttributeSensor SERVICE_STATE_EXPECTED = Attributes.SERVICE_STATE_EXPECTED; + public static final AttributeSensor> SERVICE_PROBLEMS = Attributes.SERVICE_PROBLEMS; + + /** static only; not for instantiation */ + private ServiceStateLogic() {} + + @SuppressWarnings("unchecked") + public static void clearMapSensorEntry(EntityLocal entity, AttributeSensor> sensor, TKey key) { + updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE); + } + + /** update the given key in the given map sensor */ + public static void updateMapSensorEntry(EntityLocal entity, AttributeSensor> sensor, TKey key, TVal v) { + Map 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.> + ifNotEquals(null).apply(Functions.forPredicate(CollectionFunctionals.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> 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> 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} + *

    + * 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 { + 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 event) { + Preconditions.checkNotNull(entity, "Cannot handle subscriptions or compute state until associated with an entity"); + + Map 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 problems, Boolean serviceUp) { + if (Boolean.TRUE.equals(serviceUp) && (problems==null || problems.isEmpty())) { + return Lifecycle.RUNNING; + } else { + return Lifecycle.ON_FIRE; + } + } + + protected Lifecycle computeActualStateWhenNotExpectedRunning(Map 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 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 implements SensorEventListener { + /** 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 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 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 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 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> SOURCE_SENSORS = ImmutableList.>of(SERVICE_UP, SERVICE_STATE_ACTUAL); + @Override + protected Collection> 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 values = getValues(SERVICE_UP); + List violators = MutableList.of(); + for (Map.Entry 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 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> 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 { + private static final long serialVersionUID = -607444925297963712L; + + protected ComputeServiceIndicatorsFromChildrenAndMembersSpec() { + this(ComputeServiceIndicatorsFromChildrenAndMembers.class); + } + + protected ComputeServiceIndicatorsFromChildrenAndMembersSpec(Class 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); + } + +} 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 8f4c6b40fb..0000000000 --- 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 SERVICE_UP = Attributes.SERVICE_UP; - public static final AttributeSensor> 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.> - ifNotEquals(null).apply(Functions.forPredicate(CollectionFunctionals.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 void updateMapSensor(EntityLocal entity, AttributeSensor> sensor, - TKey key, Object v) { - Map 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); - } - -} diff --git a/core/src/main/java/brooklyn/entity/group/DynamicCluster.java b/core/src/main/java/brooklyn/entity/group/DynamicCluster.java index c44f302594..f51a9ef88c 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 @@ interface ZoneFailureDetector { ConfigKey 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 SERVICE_STATE = Attributes.SERVICE_STATE; + AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; BasicNotificationSensor ENTITY_QUARANTINED = new BasicNotificationSensor(Entity.class, "dynamiccluster.entityQuarantined", "Entity failed to start, and has been quarantined"); diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java index 27ffaf8089..1023db6d53 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 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.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; @@ -146,6 +144,17 @@ public void init() { setAttribute(SERVICE_UP, false); } + @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, Entity> val) { setConfig(REMOVAL_STRATEGY, checkNotNull(val, "removalStrategy")); @@ -247,13 +256,15 @@ public void start(Collection locsO) { 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 void start(Collection locsO) { 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() { - @Override - public void onEvent(SensorEvent event) { - setAttribute(SERVICE_STATE, calculateServiceState()); - } - }); - subscribeToChildren(this, SERVICE_UP, new SensorEventListener() { - @Override - public void onEvent(SensorEvent event) { - setAttribute(SERVICE_UP, calculateServiceUp()); - } - }); - } - - protected Lifecycle calculateServiceState() { - Lifecycle currentState = getAttribute(SERVICE_STATE); - if (EnumSet.of(Lifecycle.ON_FIRE, Lifecycle.RUNNING).contains(currentState)) { - Iterable 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 findSubLocations(Location loc) { @@ -386,10 +364,8 @@ protected List findSubLocations(Location loc) { @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 void stop() { // (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 @@ protected void cleanupFailedNodes(Collection failedEntities) { } } - /** - * Default impl is to be up when running, and !up otherwise. - */ - protected boolean calculateServiceUp() { - return getAttribute(SERVICE_STATE) == Lifecycle.RUNNING; - } - protected Map waitForTasksOnEntityStart(Map> tasks) { // TODO Could have CompoundException, rather than propagating first Map errors = Maps.newLinkedHashMap(); diff --git a/core/src/main/java/brooklyn/entity/group/DynamicFabric.java b/core/src/main/java/brooklyn/entity/group/DynamicFabric.java index abd7f5f27c..51735ec42c 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 CUSTOM_CHILD_FLAGS = new MapConfigKey( Object.class, "dynamicfabric.customChildFlags", "Additional flags to be passed to children when they are being created", ImmutableMap.of()); - public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; public void setMemberSpec(EntitySpec memberSpec); diff --git a/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicFabricImpl.java index a72f77f375..8ba08d0d17 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.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 void start(Collection locations) { if (newLocations.isEmpty()) newLocations.addAll(getLocations()); int locIndex = 0; - setAttribute(SERVICE_STATE, Lifecycle.STARTING); + ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING); try { Map> tasks = Maps.newLinkedHashMap(); @@ -158,10 +159,10 @@ public void start(Collection locations) { } 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 @@ protected void waitForTasksOnStart(Map> tasks) { @Override public void stop() { - setAttribute(SERVICE_STATE, Lifecycle.STOPPING); + ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING); try { Iterable 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); } } diff --git a/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java b/core/src/main/java/brooklyn/event/basic/BasicConfigKey.java index 5bb864d98d..91420e5229 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 static class BasicConfigKeyOverwriting extends BasicConfigKey { private final ConfigKey parentKey; public BasicConfigKeyOverwriting(ConfigKey key, T defaultValue) { + this(key, key.getDescription(), defaultValue); + } + + public BasicConfigKeyOverwriting(ConfigKey key, String newDescription, T defaultValue) { super(checkNotNull(key.getTypeToken(), "type"), checkNotNull(key.getName(), "name"), - key.getDescription(), defaultValue); + newDescription, defaultValue); parentKey = key; } diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java index 8630214955..9d8bf4b01b 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 static class Builder { /** * 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 Builder attributeWhenReady(Entity source, AttributeSensor 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) this; } /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java index 68f81625e4..6b4d49d6a0 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 String toString() { .add("name", name) .add("uniqueTag", uniqueTag) .add("running", isRunning()) + .add("entity", entity) .add("id", getId()) .toString(); } diff --git a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java index 1252e36fc3..59bc7e8521 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 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 void testTagsFromSpec() throws Exception { @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)); } } diff --git a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java index 3c4acbb337..f6096a4195 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 void testAttributeWhenReadyAbortsWhenOnfireByDefault() throws Exception { .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 void testAttributeWhenReadyAbortsWhenOnfireByDefault() throws Exception { @Test public void testAttributeWhenReadyAbortsWhenAlreadyOnfireByDefault() throws Exception { - entity.setAttribute(Attributes.SERVICE_STATE, Lifecycle.ON_FIRE); + ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE); final Task t = submit(DependentConfiguration.builder() .attributeWhenReady(entity, TestEntity.NAME) diff --git a/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java b/core/src/test/java/brooklyn/entity/basic/DynamicGroupTest.java index 12c3b55d58..b52332b2f8 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 @@ private void onCall(String msg) { }; ((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 void testDoesNotDeadlockOnManagedAndMemberAddedConcurrently() throws Exce }; ((EntityLocal)group2).setConfig(DynamicGroup.ENTITY_FILTER, Predicates.equalTo(e3)); app.addChild(group2); - group2.init(); Thread t1 = new Thread(new Runnable() { @Override public void run() { diff --git a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java index 6363a8144c..0a86e3121f 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.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 void testAddsPolicy() throws Exception { @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 void testAddsEnricherSpec() throws Exception { @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); diff --git a/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java b/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java index ae6b384127..168f72cf1e 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.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 void testAddPolicySpec() { @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 diff --git a/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java b/core/src/test/java/brooklyn/entity/group/DynamicClusterTest.java index c862f8aa01..ed1d7b69d3 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.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 void testServiceUpAfterStartingWithNoMembers() throws Exception { .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)); } diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindEnricherTest.java index 27e5edc2ca..fc89ec37be 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 MyEnricherWithoutNoArgConstructor(Map flags) { 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)); } 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 4b22592056..c71eaa8099 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 void testCheckPointAndLoadMemento() throws IOException, TimeoutException, 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 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 1a9cf9f12e..f112890f1b 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.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 Void apply(HttpToolResponse response) { return null; } }) - .onFailureOrException(EntityFunctions.settingSensorsConstantFunction(entity, MutableMap.,Object>of( + .onFailureOrException(Functionals.function(EntityFunctions.settingSensorsConstant(entity, MutableMap.,Object>of( SENSOR_INT, -1, - SENSOR_STRING, PollConfig.REMOVE))) + SENSOR_STRING, PollConfig.REMOVE)))) .period(100)) .build(); } diff --git a/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java b/core/src/test/java/brooklyn/qa/longevity/EntityCleanupLongevityTestFixture.java index fab5aa3f27..c3e6dce653 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 @@ protected void doTestManyTimesAndAssertNoMemoryLeak(String testName, Runnable it // 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()) diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java index 64131f5cd8..c2a3884c39 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 AbstractEntity configure(Map flags) { return super.configure(flags); } - @Override + @Override // made public for testing public boolean isLegacyConstruction() { return super.isLegacyConstruction(); } 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 0000000000..66ef7ae557 --- /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 + } + +} 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 46b00bbdef..a54ef58c07 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.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 clusterSpec = EntitySpec.create(CassandraDatacenter.class) .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class) @@ -204,17 +208,15 @@ public void start(Collection locations) { // TODO use a multi-region web cluster Collection 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); } 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 f1ed9496d7..bfd3a1c4f1 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)); 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 672158e950..b1d3915590 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))); 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 d2754186a2..e9ab4495ef 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 void restart() { 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 void restart() { 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() { 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 05fab5e9fd..f7dddddb1e 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.of()); - AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; + AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; @SuppressWarnings("rawtypes") AttributeSensor PROVISIONING_LOCATION = new BasicAttributeSensor( 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 eb906607a9..0551840704 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 @@ private ChildStartableMode(boolean isDisabled, boolean isBackground, boolean isL public static final AttributeSensor SERVICE_PROCESS_IS_RUNNING = Sensors.newBooleanSensor("service.process.isRunning", "Whether the process for the service is confirmed as running"); - public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; public static final AttributeSensor PID_FILE = Sensors.newStringSensor("softwareprocess.pid.file", "PID file"); 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 797d4a30f7..60e3eb025b 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 void restart() { 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); }}); } 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 25c23dfefa..e264dabf0a 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 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 @@ protected SoftwareProcessDriver newDriver(MachineLocation loc){ 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 @@ protected void callRebindHooks() { 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 void onManagementStarting() { 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 void onManagementStarted() { @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 void waitForServiceUp(long duration, TimeUnit units) { 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 void waitForEntityStart() { 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); } } 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 8407708f11..24c3eeb203 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.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.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 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 Void apply(HttpToolResponse input) { .period(getConfig(POLL_PERIOD)) .poll(HttpPollConfig.forMultiple() .onSuccess(mirrorSensors) - .onFailureOrException(EntityFunctions.settingSensorsConstantFunction(this, MutableMap.,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(); } 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 3272f3cbc3..b708b616e7 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.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 @@ protected Location getLocation(@Nullable Collection location // --------------------- - /** 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 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 @@ protected void preStartAtMachineAsync(final Supplier machineS) 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 @@ protected void postStartCustom() { /** 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() { public String call() { DynamicTasks.markInessential(); stopProcessesAtMachine(); @@ -407,11 +408,10 @@ public void restart() { 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 void stop() { log.info("Stopping {} in {}", entity(), entity().getLocations()); DynamicTasks.queue("pre-stop", new Callable() { 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 void stop() { // Release this machine (even if error trying to stop process - we rethrow that after) Task> stoppingMachine = DynamicTasks.queue("stopping (machine)", new Callable>() { public StopMachineDetails 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("Already stopped", 0); } @@ -478,9 +478,9 @@ public void stop() { } 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); } 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 6edd9a9f4e..daa6109cf2 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 void connectServiceUpIsRunning() { FunctionFeed.builder() .entity(this) .period(Duration.millis(10)) + .onlyIfServiceUp() .poll(new FunctionPollConfig(SERVICE_PROCESS_IS_RUNNING) .onException(Functions.constant(Boolean.FALSE)) .callable(new Callable() { 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 a1347bf653..f7198829ab 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 void testStartsAndStops() throws Exception { .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"}) 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 24c570b3f9..fa69522888 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 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 void addQueue(String name) { 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 void addTopic(String name) { @Override public void addTopic(String name, Map properties) { - checkModifiable(); + checkStartingOrRunning(); properties.put("name", name); topics.put(name, createTopic(properties)); } 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 520e0af0b0..511bb6be90 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 @@ protected void onEntityAdded(Entity member) { 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 locations) { super.start(locations); - setAttribute(Startable.SERVICE_UP, calculateServiceUp()); + List zookeeperServers = Lists.newArrayList(); for (Entity zookeeper : getMembers()) { zookeeperServers.add(zookeeper.getAttribute(Attributes.HOSTNAME)); @@ -106,13 +103,4 @@ public void start(Collection locations) { 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; - } - } 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 9bf442bab6..b04e362f66 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.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 Set get() { return ImmutableSet.of(); } else if (hasPublishedSeeds) { Set 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 @@ protected void connectEnrichers() { .build()); } - - subscribeToMembers(this, SERVICE_UP, new SensorEventListener() { - @Override public void onEvent(SensorEvent event) { - setAttribute(SERVICE_UP, calculateServiceUp()); - } - }); } @Override @@ -477,19 +472,11 @@ public void update() { setAttribute(THRIFT_PORT, null); setAttribute(CASSANDRA_CLUSTER_NODES, Collections.emptyList()); } - - setAttribute(SERVICE_UP, upNode.isPresent() && calculateServiceUp()); + + ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyList(this, CASSANDRA_CLUSTER_NODES); } } - @Override - protected boolean calculateServiceUp() { - if (!super.calculateServiceUp()) return false; - List 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: *
      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 5d3c219eb8..fe0b503196 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> defaultSeedSupplier = new Supplier>() { @Override public Set get() { // TODO Remove duplication from CassandraClusterImpl.defaultSeedSupplier @@ -95,7 +93,7 @@ public class CassandraFabricImpl extends DynamicFabricImpl implements CassandraF if (hasPublishedSeeds) { Set 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 void init() { 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)); 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 11c56d7513..b47f13309b 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.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 void start(Collection locations) { super.start(locations); connectSensors(); - connectEnrichers(); - + //start timeout before adding the servers Time.sleep(getConfig(SERVICE_UP_TIME_OUT)); @@ -165,7 +164,7 @@ public void start(Collection locations) { //check Repeater. } } else { - setAttribute(SERVICE_STATE, Lifecycle.ON_FIRE); + ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE); } } @@ -175,16 +174,6 @@ public void stop() { super.stop(); } - public void connectEnrichers() { - - subscribeToMembers(this, SERVICE_UP, new SensorEventListener() { - @Override - public void onEvent(SensorEvent event) { - setAttribute(SERVICE_UP, calculateServiceUp()); - } - }); - } - protected void connectSensors() { addPolicy(PolicySpec.create(MemberTrackingPolicy.class) .displayName("Controller targets tracker") @@ -292,13 +281,22 @@ private Entity getPrimaryNode() { } @Override - protected boolean calculateServiceUp() { - if (!super.calculateServiceUp()) return false; - Set 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 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 serversToAdd) { Preconditions.checkNotNull(serversToAdd); for (Entity e : serversToAdd) { 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 a149140cdf..4835f72bf2 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 String getClusterName() { return getAttribute(CLUSTER_NAME); } - @Override - public void start(Collection 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; - } } 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 da719dde95..a773006fc4 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 @@ import java.util.concurrent.atomic.AtomicInteger; -import brooklyn.entity.Entity; import brooklyn.entity.group.DynamicClusterImpl; import brooklyn.entity.proxying.EntitySpec; @@ -28,15 +27,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))); 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 b859765c8d..b1e2393627 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 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 @@ private void serverAdded(MongoDBServer server) { 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()) 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 28ee33a9ab..df85c36017 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 @@ protected EntitySpec getMemberSpec() { 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 locs) { super.start(locs); + + // TODO this should be an enricher Iterable memberHostNamesAndPorts = Iterables.transform(getMembers(), new Function() { @Override public String apply(Entity entity) { 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 9826300175..a75c422242 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 @@ 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.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 boolean apply(@Nullable Entity entity) { 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 @@ protected synchronized void onServerPoolMemberChanged(Entity member) { if (log.isTraceEnabled()) log.trace("For {}, considering membership of {} which is in locations {}", new Object[]{this, member, member.getLocations()}); + Map 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 nodes = getAttribute(RIAK_CLUSTER_NODES); if (nodes == null) nodes = Maps.newLinkedHashMap(); String riakName = getRiakName(member); @@ -151,7 +154,6 @@ public boolean apply(@Nullable Entity node) { } } } else { - Map nodes = getAttribute(RIAK_CLUSTER_NODES); if (nodes != null && nodes.containsKey(member)) { final Entity memberToBeRemoved = member; @@ -172,6 +174,8 @@ public boolean apply(@Nullable Entity node) { } } + + ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyMap(this, RIAK_CLUSTER_NODES); if (log.isTraceEnabled()) log.trace("Done {} checkEntity {}", this, member); } 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 9e0b15174e..d405fabbe4 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 @@ protected void postStart() { @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(); 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 8c4f389c64..bc8f88d74a 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 LoadBalancerClusterImpl() { super(); } - @Override - public void start(Collection 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 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 f4824222fa..3e36ada10c 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 void deploy(String archiveUrl) { 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 String getConfigFile() { 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; } 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 81e74fd226..8a3f2c6c9c 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 @@ private void reloadImpl() { // 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; } 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 0c263d95fc..72b2bb66d0 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 HOSTNAME = Attributes.HOSTNAME; - public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; public LoadBalancer getController(); 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 47e7ec93f2..acff382f19 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.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 ControlledDynamicWebAppClusterImpl(Entity parent) { @Deprecated public ControlledDynamicWebAppClusterImpl(Map flags, Entity parent) { super(flags, parent); - setAttribute(SERVICE_UP, false); } @Override @@ -137,6 +138,14 @@ public void init() { 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 synchronized DynamicWebAppCluster getCluster() { @Override public void start(Collection locations) { - setAttribute(Attributes.SERVICE_STATE, Lifecycle.STARTING); + ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING); try { if (isLegacyConstruction()) { @@ -202,13 +211,9 @@ public void start(Collection locations) { // (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 void start(Collection locations) { @Override public void stop() { - setAttribute(SERVICE_STATE, Lifecycle.STOPPING); + ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING); try { List tostop = Lists.newArrayList(); @@ -228,10 +233,9 @@ public void stop() { 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 void restart() { } 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 @@ void connectSensors() { .propagating(LoadBalancer.HOSTNAME, Attributes.ADDRESS, ROOT_URL) .from(getController()) .build()); - - SensorEventListener updateServiceUp = new SensorEventListener() { - @Override - public void onEvent(SensorEvent event) { - setAttribute(SERVICE_UP, calculateServiceUp()); - } - }; - SensorEventListener updateServiceState = new SensorEventListener() { - @Override - public void onEvent(SensorEvent 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 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 bfdc6669aa..951e3b46f9 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.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 void init() { } } - public void onManagementStarted() { - super.onManagementStarted(); - - subscribeToMembers(this, SERVICE_UP, new SensorEventListener() { - @Override public void onEvent(SensorEvent 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 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 ce8fdec7a1..9d2c7ecc19 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 void undeploy(String targetName) { 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 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 74e23b5e18..f731eca3ea 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.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 void testRebindsWithEmptyServerPool() throws Exception { 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)); 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 d5d960b8c9..df486aa895 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 void testStopOnChildUnmanaged() { Entities.unmanage(controller); cluster.stop(); - EntityTestUtils.assertAttributeEquals(cluster, ControlledDynamicWebAppCluster.SERVICE_STATE, Lifecycle.STOPPED); + EntityTestUtils.assertAttributeEquals(cluster, ControlledDynamicWebAppCluster.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); } } 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 7067898957..88e8da4845 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 ApplicationSummary apply(Application input) { 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; 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 a93c551601..ef66ded6eb 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 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 String toString() { } } + /** like guava {@link Functions#forSupplier(Supplier)} but parametrises the input generic type */ + public static Function function(final Supplier supplier) { + class SupplierAsFunction implements Function { + @Override public O apply(I input) { + return supplier.get(); + } + } + return new SupplierAsFunction(); + } + + public static Function function(final Runnable runnable) { + class RunnableAsFunction implements Function { + @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(); + } + } From cbc103a3b64cbcc7bcc9530cdd1914606e5b2520 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 27 Aug 2014 02:16:18 -0400 Subject: [PATCH 12/22] some notes on tidying the REST API --- .../rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java | 3 ++- usage/rest-api/src/main/java/brooklyn/rest/api/PolicyApi.java | 1 + .../src/main/java/brooklyn/rest/api/PolicyConfigApi.java | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) 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 c2ec68eb25..812ae2fa4c 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 Response invoke( @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 parameters ) ; 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 6f66965ff1..7154ca51b7 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 List list( @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 batchConfigRead( @ApiParam(value = "Application ID or name", required = true) @PathParam("application") String application, 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 1c53598af6..cfd66705fd 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 String get( @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") From d3886a055353f2efa435c84d45668c1a3ab0aa23 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Aug 2014 10:48:19 +0100 Subject: [PATCH 13/22] 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. --- .../java/brooklyn/entity/basic/Entities.java | 5 +-- .../entity/basic/EntityConfigMap.java | 8 ++--- .../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 +- .../LoadBalancingPolicyTest.java | 4 ++- .../loadbalancing/MockContainerEntity.java | 2 +- .../MockContainerEntityImpl.java | 3 +- 12 files changed, 71 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java index ea35432f25..4191110ac2 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 static void dumpInfo(Entity e, String currentIndentation, String tab) thr 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 @@ else if ((v instanceof Task) && ((Task)v).isDone()) { 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"); } diff --git a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java index c946fe020f..3f5884b9a8 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 T getConfig(ConfigKey key, T defaultValue) { // but that example doesn't have a default... ConfigKey ownKey = entity!=null ? (ConfigKey)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 T getConfig(ConfigKey key, T defaultValue) { T result = null; boolean complete = false; if (((ConfigKeySelfExtracting)ownKey).isSet(ownConfig)) { + ExecutionContext exec = entity.getExecutionContext(); result = ((ConfigKeySelfExtracting)ownKey).extractValue(ownConfig, exec); complete = true; } else if (((ConfigKeySelfExtracting)ownKey).isSet(inheritedConfig)) { - result = ((ConfigKeySelfExtracting)ownKey).extractValue(inheritedConfig, exec); - complete = true; + ExecutionContext exec = entity.getExecutionContext(); + result = ((ConfigKeySelfExtracting)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); diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java index bac451579d..17195e8b3d 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 static QuorumCheck atLeastOne() { 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); } diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index de4f1ad22b..9e663819e7 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 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 @@ /** 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 SERVICE_UP = Attributes.SERVICE_UP; public static final AttributeSensor> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS; @@ -206,6 +211,7 @@ protected Lifecycle computeActualStateWhenNotExpectedRunning(Map } 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 static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr "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 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 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 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 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 @@ protected void onUpdated() { protected Object computeServiceNotUp() { Map values = getValues(SERVICE_UP); List violators = MutableList.of(); + boolean ignoreNull = getConfig(IGNORE_NULL_VALUES); + int entries=0; for (Map.Entry state: values.entrySet()) { + if (ignoreNull && state.getValue()==null) + continue; + entries++; if (!Boolean.TRUE.equals(state.getValue())) { violators.add(state.getKey()); } @@ -312,11 +325,12 @@ protected Object computeServiceNotUp() { 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 @@ protected Object computeServiceNotUp() { } 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 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> 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 diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java index 56969d06d9..9a7ecb1d07 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 @@ 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 @@ * Membership of a balanceable container does not imply a parent-child relationship in the Brooklyn * management sense. */ -public interface BalanceableContainer extends Entity { +public interface BalanceableContainer extends Entity, AbstractGroup { public static BasicNotificationSensor ITEM_ADDED = new BasicNotificationSensor( Entity.class, "balanceablecontainer.item.added", "Movable item added to balanceable container"); public static BasicNotificationSensor ITEM_REMOVED = new BasicNotificationSensor( Entity.class, "balanceablecontainer.item.removed", "Movable item removed from balanceable container"); - + public static final ConfigKey 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 getBalanceableItems(); } diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java index 023edbe05f..ab18ce680c 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 @@ * 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. + *

      + * 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 { diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java index 905411ec2c..bb7b64b193 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.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 void onEvent(SensorEvent event) { 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)) { diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java b/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java index 6738d6c2b5..7cef21d243 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 eventHandler = new SensorEventListener() { + @SuppressWarnings({ "rawtypes", "unchecked" }) public void onEvent(SensorEvent event) { if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", LoadBalancingPolicy.this, event); Entity source = event.getSource(); @@ -119,6 +121,7 @@ public LoadBalancingPolicy(AttributeSensor metric, BalanceablePoolModel model) { this(MutableMap.of(), metric, model); } + @SuppressWarnings({ "unchecked", "rawtypes" }) public LoadBalancingPolicy(Map props, AttributeSensor metric, BalanceablePoolModel model) { @@ -133,6 +136,7 @@ public LoadBalancingPolicy(Map props, AttributeSensor metric, 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 @@ private void scheduleRebalance() { 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 void run() { 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 void run() { 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; } } } diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java index 0cd05a0834..244d8fafef 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 void before() { model = new DefaultBalanceablePoolModel("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))); diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java index df7d300078..c0ff7b77b7 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.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 void testAddContainerWhenHot() { 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), diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java index 1de0d47236..2fba8f034b 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 DELAY = new BasicConfigKey( Long.class, "mock.container.delay", "", 0L); - public static final Effector OFFLOAD_AND_STOP = new MethodEffector(MockContainerEntity.class, "offloadAndStop"); + public static final Effector OFFLOAD_AND_STOP = new MethodEffector(MockContainerEntity.class, "offloadAndStop"); public void lock(); diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java index 13baec5150..09906d01da 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 void removeItem(Entity item) { emit(BalanceableContainer.ITEM_REMOVED, item); } + @SuppressWarnings("unchecked") @Override public Set getBalanceableItems() { return (Set) Sets.newLinkedHashSet(getMembers()); @@ -137,8 +138,6 @@ public void start(Collection locs) { 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 { From b2daedf8336a891167602f67e7f8e576256dc5e8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Aug 2014 15:17:53 -0500 Subject: [PATCH 14/22] 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 --- api/src/main/java/brooklyn/entity/Entity.java | 2 + .../entity/basic/ServiceStateLogic.java | 33 +- .../internal/LocalSubscriptionManager.java | 6 +- .../util/task/BasicExecutionManager.java | 7 +- .../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 +- .../HighAvailabilityCassandraCluster.java | 5 +- .../brooklyn/demo/ResilientMongoDbApp.java | 2 +- .../brooklyn/demo/RiakClusterExample.java | 9 +- .../demo/WideAreaCassandraCluster.java | 5 +- .../policy/ha/ServiceFailureDetector.java | 409 +++++------------- .../entity/brooklyn/BrooklynMetricsTest.java | 20 +- .../AutoScalerPolicyMetricTest.java | 5 +- .../policy/ha/HaPolicyRebindTest.java | 9 +- ...rviceFailureDetectorStabilizationTest.java | 31 +- .../policy/ha/ServiceFailureDetectorTest.java | 203 +++++---- .../policy/ha/ServiceReplacerTest.java | 2 +- .../entity/webapp/JBossExample.groovy | 48 -- ...ntity.groovy => TestJavaWebAppEntity.java} | 50 +-- 23 files changed, 372 insertions(+), 508 deletions(-) delete mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/JBossExample.groovy rename software/webapp/src/test/java/brooklyn/test/entity/{TestJavaWebAppEntity.groovy => TestJavaWebAppEntity.java} (59%) diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java index 88a10fa253..9a0939229b 100644 --- a/api/src/main/java/brooklyn/entity/Entity.java +++ b/api/src/main/java/brooklyn/entity/Entity.java @@ -25,6 +25,8 @@ 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; diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index 9e663819e7..e3d509058d 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 TVal getMapSensorEntry(EntityLocal entity, AttributeSensor> sensor, TKey key) { + Map map = entity.getAttribute(sensor); + if (map==null) return null; + return map.get(key); + } + @SuppressWarnings("unchecked") public static void clearMapSensorEntry(EntityLocal entity, AttributeSensor> sensor, TKey key) { updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE); @@ -164,6 +170,15 @@ public static void updateNotUpIndicatorRequiringNonEmptyMap(EntityLocal entity, * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity. */ public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener { + 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 @@ protected Lifecycle computeActualStateWhenNotExpectedRunning(Map } 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 static final EnricherSpec newEnricherForServiceStateFromProblemsAndUp( return newEnricherForServiceState(ComputeServiceState.class); } public static final EnricherSpec newEnricherForServiceState(Class 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 static void updateProblemsIndicator(EntityLocal entity, Effector eff, 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 implements SensorEventListener { diff --git a/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java b/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java index 0bd8578a71..83cec75832 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 String toString() { 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(); } diff --git a/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java b/core/src/main/java/brooklyn/util/task/BasicExecutionManager.java index da8c4561e2..2f6e396784 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 T call() { 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); diff --git a/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java b/core/src/test/java/brooklyn/enricher/CustomAggregatingEnricherTest.java index f3e06f6c44..70d0c10987 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; diff --git a/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java b/core/src/test/java/brooklyn/location/basic/MultiLocationResolverTest.java index 937679decc..85bcfbdb4f 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.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(); } diff --git a/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java b/core/src/test/java/brooklyn/test/entity/TestClusterImpl.java index 0bd252182a..366352059d 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; @@ -37,6 +38,14 @@ public void init() { setAttribute(Startable.SERVICE_UP, true); } + @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; diff --git a/core/src/test/java/brooklyn/test/entity/TestEntity.java b/core/src/test/java/brooklyn/test/entity/TestEntity.java index ac88acd88e..c3ca9c2a93 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 NAME = Sensors.newStringSensor("test.name", "Test name"); public static final BasicNotificationSensor MY_NOTIF = new BasicNotificationSensor(Integer.class, "test.myNotif", "Test notification"); - public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE; + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; + @Deprecated + public static final AttributeSensor SERVICE_STATE = Attributes.SERVICE_STATE_ACTUAL; public static final MethodEffector MY_EFFECTOR = new MethodEffector(TestEntity.class, "myEffector"); public static final MethodEffector IDENTITY_EFFECTOR = new MethodEffector(TestEntity.class, "identityEffector"); diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java index c2a3884c39..3ccf614d60 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 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 synchronized void setSequenceValue(int value) { public void start(Collection 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 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 a54ef58c07..eaabaff262 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.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.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 void init() { .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) 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 2dce1eacfe..c21c4cf8cb 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 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 void init() { //.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) 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 8506b27f63..290f25ee8b 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 void onEvent(SensorEvent addition) { /** 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)); } 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 d53ce86238..0134e27183 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 @@ 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.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 void init() { 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)))); } 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 c40eb27738..8e30fc44fb 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 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.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 void init() { .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) diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java index 5efad2b262..c523aba5ac 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.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.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. + *

      + * 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 enum LastPublished { public static final BasicNotificationSensor 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 ONLY_REPORT_IF_PREVIOUSLY_UP = ConfigKeys.newBooleanConfigKey("onlyReportIfPreviouslyUp", "", true); + public static final ConfigKey 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 USE_SERVICE_STATE_RUNNING = ConfigKeys.newBooleanConfigKey("useServiceStateRunning", "", true); + public static final ConfigKey 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 SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true); + @SetFromFlag("serviceOnFireStabilizationDelay") + public static final ConfigKey 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 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 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 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 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 serviceIsUp = new AtomicReference(); - protected final AtomicReference serviceState = new AtomicReference(); - protected final AtomicReference serviceLastUp = new AtomicReference(); - protected final AtomicReference serviceLastDown = new AtomicReference(); + 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 subscriptionHandles = Lists.newCopyOnWriteArrayList(); - public ServiceFailureDetector() { this(new ConfigBag()); } @@ -130,161 +113,98 @@ public ServiceFailureDetector(ConfigBag configBag) { // 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 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() { - @Override public void onEvent(SensorEvent 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() { - @Override public void onEvent(SensorEvent 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 serviceProblems = entity.getAttribute(Attributes.SERVICE_PROBLEMS); + if (serviceProblems!=null && !serviceProblems.isEmpty()) { + Entry 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 @@ protected void schedulePublish(long delay) { 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 @@ protected void schedulePublish(long delay) { } } - 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 : ""), - (status != null ? status : ""), - Time.makeDateString(System.currentTimeMillis()), - (lastUpTime != null ? Time.makeDateString(lastUpTime) : ""), - lastPublished, - (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "") + " (stabilization "+makeTimeStringRounded(serviceFailedStabilizationDelay) + ")", - (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "") + " (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 timeRef) { - return getTimeStringSince(timeRef.get()); - } } diff --git a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java index 39be9d4107..319b345f40 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.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 void setUp() { 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 void tearDown() throws Exception { 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 void run() { 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 void run() { 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)); }}); } diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java index 2290a33e39..7cec2ed087 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.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 @@ 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 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)); } diff --git a/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java b/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java index 9d8f427fe5..0cccf14ceb 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 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.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 void testServiceReplacerWorksAfterRebind() throws Exception { @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 void testServiceFailureDetectorWorksAfterRebind() throws Exception { 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.equalTo(newEntity), null); diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java index 34265980f6..4649b36e85 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 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.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 void setUp() throws Exception { 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 void tearDown() throws Exception { @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 void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws 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 void testNotifiedOfFailureAfterStabilisationDelay() throws Exception { 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 void testFailuresThenUpDownResetsStabilisationCount() throws Exception { 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.equalTo(e1), null); @@ -157,8 +160,8 @@ public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws 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.equalTo(e1), null); @@ -173,8 +176,8 @@ public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception { 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.equalTo(e1), null); diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java index 98c28dec14..5c9dd66f0c 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 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.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> events; private SensorEventListener eventListener; @@ -73,6 +76,7 @@ public void setUp() throws Exception { 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 void tearDown() throws Exception { @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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.equalTo(e1), null); } diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java index 4b5a0d9804..8d1a6832d9 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 void testSetsOnFireWhenFailToReplaceMember() throws Exception { 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 members = Iterables.filter(managementContext.getEntityManager().getEntities(), Predicates.instanceOf(FailingEntity.class)); 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 c47ae769e0..0000000000 --- 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) - } - -} diff --git a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java similarity index 59% rename from software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy rename to software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java index 7fbf8bc269..96edbcde9a 100644 --- a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.groovy +++ b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java @@ -16,46 +16,48 @@ * specific language governing permissions and limitations * under the License. */ -package brooklyn.test.entity +package brooklyn.test.entity; -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import java.util.Collection; +import java.util.Map; -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 +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 START = new EffectorAndBody(SoftwareProcessImpl.START, new MethodEffector(TestJavaWebAppEntity.class, "customStart").getBody()); + public static final Effector START = new EffectorAndBody(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 TestJavaWebAppEntity() {} + public TestJavaWebAppEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) { super(flags, parent); } + public void waitForHttpPort() { } public void customStart(Collection loc) { - LOG.trace "Starting {}", this + LOG.trace("Starting {}", this); } @Override protected void doStop() { - LOG.trace "Stopping {}", this + LOG.trace("Stopping {}", this); } @Override @@ -63,13 +65,9 @@ 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) + Integer rc = getAttribute(WebAppServiceConstants.REQUEST_COUNT); + if (rc==null) rc = 0; + setAttribute(WebAppServiceConstants.REQUEST_COUNT, rc+1); } } From 5ec0a00b1c9deb4ff3b7ccc4dc7e7e6755fded26 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Aug 2014 16:29:52 -0500 Subject: [PATCH 15/22] use initApp for apps to ensure enrichers get added, fixing tests and other misc tidies --- .../entity/basic/AbstractApplication.java | 7 +++ .../entity/basic/BasicApplicationImpl.java | 7 --- .../catalog/internal/MyCatalogItems.java | 4 -- .../basic/DependentConfigurationTest.java | 35 ++++++++++++--- .../rebind/RebindTestFixtureWithApp.java | 3 +- .../test/entity/TestApplicationImpl.java | 5 --- .../TestApplicationNoEnrichersImpl.java | 44 +++++++++++++++++++ .../brooklyn/demo/GlobalWebFabricExample.java | 2 +- .../demo/StandaloneQpidBrokerExample.java | 2 +- .../brooklyn/demo/CumulusRDFApplication.java | 4 +- .../HighAvailabilityCassandraCluster.java | 2 +- .../brooklyn/demo/ResilientMongoDbApp.java | 2 +- .../brooklyn/demo/RiakClusterExample.java | 2 +- .../brooklyn/demo/SimpleCassandraCluster.java | 2 +- .../java/brooklyn/demo/StormSampleApp.java | 2 +- .../demo/WideAreaCassandraCluster.java | 2 +- .../brooklyn/demo/NodeJsTodoApplication.java | 2 +- .../brooklyn/demo/SingleWebServerExample.java | 2 +- .../demo/WebClusterDatabaseExample.java | 4 +- .../demo/WebClusterDatabaseExampleApp.java | 4 +- .../WebClusterDatabaseExampleGroovy.groovy | 2 +- .../java/brooklyn/demo/WebClusterExample.java | 2 +- .../entity/basic/lifecycle/MyEntityImpl.java | 3 +- .../lifecycle/NaiveScriptRunnerTest.java | 3 ++ .../basic/lifecycle/ScriptHelperTest.java | 13 +++--- .../brooklyn/entity/pool/ServerPoolTest.java | 13 +++--- .../longevity/webcluster/WebClusterApp.java | 2 +- .../rest/testing/mocks/RestMockApp.java | 8 ---- .../util/BrooklynRestResourceUtilsTest.java | 4 -- 29 files changed, 113 insertions(+), 74 deletions(-) create mode 100644 core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java index da33208367..35e0083c22 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 diff --git a/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java b/core/src/main/java/brooklyn/entity/basic/BasicApplicationImpl.java index 1dfc7bce45..69155564be 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 - } } diff --git a/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java b/core/src/test/java/brooklyn/catalog/internal/MyCatalogItems.java index 66fe0e6646..3506d49ea8 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") diff --git a/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java b/core/src/test/java/brooklyn/entity/basic/DependentConfigurationTest.java index f6096a4195..433d6774b8 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.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.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 @@ */ 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 void testAttributeWhenReadyWithAbortFailsWhenAbortConditionAlreadyHolds() } @Test - public void testAttributeWhenReadyAbortsWhenOnfireByDefault() throws Exception { + public void testAttributeWhenReadyAbortsWhenOnFireByDefault() { + log.info("starting test "+JavaClassNames.niceClassAndMethod()); final Task 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 t = submit(DependentConfiguration.builder() .attributeWhenReady(entity, TestEntity.NAME) @@ -281,7 +304,7 @@ private void assertNotDoneContinually(final Task t) { private T assertDoneEventually(final Task t) throws Exception { final AtomicReference exception = new AtomicReference(); - T result = Asserts.succeedsEventually(new Callable() { + T result = Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Callable() { @Override public T call() throws InterruptedException, TimeoutException { try { return t.get(Duration.ONE_SECOND); diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixtureWithApp.java index 1d54199b47..81ca411ce4 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 @@ 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 { protected TestApplication createApp() { - return ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class), origManagementContext); + return ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class), origManagementContext); } } diff --git a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java index ae5c963f63..8c7d844cfe 100644 --- a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java +++ b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java @@ -53,11 +53,6 @@ public TestApplicationImpl(Map flags) { super(flags); } - @Override - public void init() { - // no-op - } - @Override public T createAndManageChild(EntitySpec spec) { if (!getManagementSupport().isDeployed()) throw new IllegalStateException("Entity "+this+" not managed"); 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 0000000000..14354433cd --- /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 */ } + +} 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 004f118b0f..cf4fa24bac 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) 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 e042e0dba8..57400e34da 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) 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 eaabaff262..c560ca5c24 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 { * */ @Override - public void init() { - super.init(); - + public void initApp() { // Cassandra cluster EntitySpec clusterSpec = EntitySpec.create(CassandraDatacenter.class) .configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(CassandraNode.class) 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 c21c4cf8cb..a10578320c 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)) 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 290f25ee8b..4acbbea9c6 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)); 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 0134e27183..58b6f6001f 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 static void main(String[] argv) { 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) 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 577a9dce71..94f792c9fd 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")); 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 cb70578603..732ccd5b8a 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)); 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 8e30fc44fb..325502f8c1 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 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 54eb00802d..2eef128abb 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 @@ 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) 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 9cf027f37f..d974e358f8 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+"))); 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 bfd3a1c4f1..fd7c9aea94 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)); 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 b1d3915590..706445f1aa 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 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))); 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 fc58a02b00..c3f6cc7224 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); 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 28dab20ef7..6a5d064c90 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+")); 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 fcafc19e69..aa57e195c8 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.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 @@ public class MyEntityImpl extends SoftwareProcessImpl implements MyEntity { @Override - public Class getDriverInterface() { + public Class getDriverInterface() { return MyEntityDriver.class; } 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 eda5cbdf3c..15f4fd4bea 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 int execute(Map flags, List script, String summaryForLogging) { }; } + @SuppressWarnings("rawtypes") public static NaiveScriptRunner newLocalhostRunner() { return new NaiveScriptRunner() { LocalhostMachineProvisioningLocation location = new LocalhostMachineProvisioningLocation(); @@ -75,6 +77,7 @@ public static NaiveScriptRunner newLocalhostRunner() { public int execute(List script, String summaryForLogging) { return execute(new MutableMap(), script, summaryForLogging); } + @SuppressWarnings("unchecked") @Override public int execute(Map flags, List script, String summaryForLogging) { try { 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 daa6109cf2..bdb49b76cd 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.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 void testCheckRunningForcesInessential() { // 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 void connectServiceUpIsRunning() { FunctionFeed.builder() .entity(this) .period(Duration.millis(10)) - .onlyIfServiceUp() .poll(new FunctionPollConfig(SERVICE_PROCESS_IS_RUNNING) .onException(Functions.constant(Boolean.FALSE)) .callable(new Callable() { 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 f565c5439f..584fbfed6d 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.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 void testAppCanBeDeployedToServerPool() { 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 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 143dcbcaaa..a223861d0e 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 sinusoidalLoad = Sensors.newDoubleSensor("brooklyn.qa.sinusoidalLoad", "Sinusoidal server load"); AttributeSensor averageLoad = 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 97915c9a25..186ca76467 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 @@ import brooklyn.entity.basic.AbstractApplication; public class RestMockApp extends AbstractApplication { - - public RestMockApp() { - } - - @Override - public void init() { - // no-op - } } 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 58419bb581..4c7d16b44e 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 interface MyInterface { 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 { From bfb8737e647357053f65d5bd305a4f6783ad2866 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Aug 2014 16:35:55 -0500 Subject: [PATCH 16/22] fix failing test on service going on_fire not aborting dependent configuration --- .../java/brooklyn/event/basic/DependentConfiguration.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java index 9d8bf4b01b..dd73cc87a9 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 static T waitInTaskForAttributeReady(final Entity source, final Attri 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)"); From 5db500e43fbc1973d5b941ff41f681e51c134217 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 26 Aug 2014 08:46:02 -0500 Subject: [PATCH 17/22] 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) --- .../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 ++++-- .../entity/basic/SameServerEntity.java | 4 +++ .../entity/basic/SameServerEntityImpl.java | 6 ++++ .../ControlledDynamicWebAppClusterTest.java | 17 ++++++---- .../webapp/DynamicWebAppClusterTest.java | 12 ++++--- .../test/entity/TestJavaWebAppEntity.java | 6 ++++ .../camp/brooklyn/EnrichersYamlTest.java | 23 +++++++------ .../camp/brooklyn/EntitiesYamlTest.java | 2 +- .../rest/util/BrooklynRestResourceUtils.java | 2 +- .../resources/ApplicationResourceTest.java | 9 +++-- .../testing/BrooklynRestResourceTest.java | 26 +++++++++++--- .../testing/mocks/RestMockAppBuilder.java | 5 ++- .../testing/mocks/RestMockSimpleEntity.java | 11 +++--- .../java/brooklyn/test/EntityTestUtils.java | 1 + 22 files changed, 171 insertions(+), 59 deletions(-) diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java index 73b7a3b2f4..48b7a56da7 100644 --- a/core/src/main/java/brooklyn/enricher/Enrichers.java +++ b/core/src/main/java/brooklyn/enricher/Enrichers.java @@ -95,11 +95,11 @@ public B suppressDuplicates(Boolean suppressDuplicates) { protected abstract String getDefaultUniqueTag(); - protected EnricherSpec build() { + protected EnricherSpec build() { EnricherSpec spec = EnricherSpec.create(enricherType); String uniqueTag2 = uniqueTag; - if (uniqueTag!=null) + if (uniqueTag2==null) uniqueTag2 = getDefaultUniqueTag(); if (uniqueTag2!=null) spec.uniqueTag(uniqueTag2); diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java index 35e0083c22..961906fffb 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 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 AbstractApplication() { } public void init() { - initApp(); super.init(); + initApp(); } protected void initApp() {} diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index e3d509058d..749c83d67a 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 static boolean isExpectedState(Entity entity, Lifecycle state) { } 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 static final EnricherSpec newEnricherForServiceUpIfNoNotUpIndicators() Functionals.> ifNotEquals(null).apply(Functions.forPredicate(CollectionFunctionals.mapSizeEquals(0))) .defaultValue(Entities.REMOVE) ) - .uniqueTag("service.isUp if no service.notUp.indicators") + .uniqueTag(DEFAULT_ENRICHER_UNIQUE_TAG) .build(); } @@ -170,13 +172,16 @@ public static void updateNotUpIndicatorRequiringNonEmptyMap(EntityLocal entity, * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity. */ public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener { + + 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 static void clearProblemsIndicator(EntityLocal entity, String key) { public static class ComputeServiceIndicatorsFromChildrenAndMembers extends AbstractMultipleSensorAggregator implements SensorEventListener { /** 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 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 ComputeServiceIndicatorsFromChildrenAndMembersSpec requireRunningChildren } /** 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); } diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index 66d1bdd455..aeffe6a359 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 @@ @SuppressWarnings("serial") public class EnrichersTest extends BrooklynAppUnitTestSupport { + public static List getNonSystemEnrichers(Entity entity) { + List result = MutableList.copyOf(entity.getEnrichers()); + Iterator ri = result.iterator(); + while (ri.hasNext()) { + if (isSystemEnricher(ri.next())) ri.remove(); + } + return result; + } + + public static final List 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 NUM1 = Sensors.newIntegerSensor("test.num1"); public static final AttributeSensor NUM2 = Sensors.newIntegerSensor("test.num2"); public static final AttributeSensor NUM3 = Sensors.newIntegerSensor("test.num3"); @@ -81,12 +111,14 @@ public void setUp() throws Exception { @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); diff --git a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java index 59bc7e8521..8d6ed80bcc 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 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 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 INT_KEY = new BasicConfigKey(Integer.class, "bkey", "b key"); diff --git a/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java b/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java index 523a6e27df..bad3b1a470 100644 --- a/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java +++ b/core/src/test/java/brooklyn/entity/BrooklynAppUnitTestSupport.java @@ -52,6 +52,10 @@ public void setUp() throws Exception { if (mgmt == null) { mgmt = new LocalManagementContextForTests(); } + setUpApp(); + } + + protected void setUpApp() { EntitySpec appSpec = EntitySpec.create(TestApplication.class) .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution()); app = ApplicationBuilder.newManagedApp(appSpec, mgmt); diff --git a/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java b/policy/src/test/java/brooklyn/entity/brooklyn/BrooklynMetricsTest.java index 319b345f40..1c12319dfc 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.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 @@ 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 void run() { 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 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 + 2*DEFAULT_SUBSCRIPTIONS_PER_ENTITY)); + (1L + NUM_SUBSCRIPTIONS_PER_ENTITY)); }}); } diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java index 1bc8a3b8ad..eeab7cdde2 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); diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java index 5c9dd66f0c..2741ef63dd 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.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 @@ 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 void testDisablingPreviouslyUpRequirementForEntityFailed() throws Excepti } @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 void testOnFireAfterDelay() throws Exception { // 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 void testOnFailureDelayFromProblemAndRecover() throws Exception { 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); 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 f7dddddb1e..09a2cc4240 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 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.of()); + ConfigKey UP_QUORUM_CHECK = ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK; + ConfigKey RUNNING_QUORUM_CHECK = ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK; + AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; @SuppressWarnings("rawtypes") 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 f5092492e1..40a7db2c8e 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. *

      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 df486aa895..f80d10c3cd 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.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 void setUp() throws Exception { 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 void testSetsServiceLifecycle() { .configure("initialSize", 1) .configure("factory", new BasicConfigurableEntityFactory(TestJavaWebAppEntity.class))); + EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); + RecordingSensorEventListener listener = new RecordingSensorEventListener(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()); } 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 1cec3c9fd3..8def4385ff 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 void testSetsServiceUpIfMemberIsUp() throws Exception { 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); } 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 96edbcde9a..9a3e08a907 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 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 void waitForHttpPort() { } public void customStart(Collection loc) { + ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING); LOG.trace("Starting {}", this); + ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING); + setAttribute(Attributes.SERVICE_UP, true); } @Override 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 e3b0352e93..7d3feaf167 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.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 void testWithAppEnricher() throws Exception { 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 void testWithEntityEnricher() throws Exception { 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() { @Override public Integer get() { - return child.getEnrichers().size(); + return EnrichersTest.getNonSystemEnrichers(child).size(); } }, Predicates. 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 void testPropogateChildSensor() throws Exception { Asserts.eventually(new Supplier() { @Override public Integer get() { - return parentEntity.getEnrichers().size(); + return EnrichersTest.getNonSystemEnrichers(parentEntity).size(); } }, Predicates.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() { @@ -240,9 +242,10 @@ public Entity call() throws Exception { } private Enricher getEnricher(Entity entity) { - Assert.assertEquals(entity.getEnrichers().size(), 1); - Enricher enricher = entity.getEnrichers().iterator().next(); - Assert.assertTrue(enricher instanceof TestReferencingEnricher); + List 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; } 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 14c7c10474..53bb4aa06d 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 void testAppWithSameServerEntityStarts() throws Exception { 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); 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 7075fa5bc4..7c86d6af3f 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 @@ private brooklyn.entity.proxying.EntitySpec toCoreEntitySpec(b 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) ); 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 47e4b2c803..2b36c5d22d 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.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.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 void testDeployApplicationFromBuilder() throws Exception { // 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 void testTriggerSampleEffectorWithFormData() throws InterruptedException, @Test(dependsOnMethods = "testTriggerSampleEffector") public void testBatchSensorValues() { - Map sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state") - .get(new GenericType>() {}); + WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state"); + Map sensors = resource.get(new GenericType>() {}); assertTrue(sensors.size() > 0); assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4"); } 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 8ec14b2d21..672537efe0 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 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 @@ 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 @@ protected ClientResponse clientDeploy(ApplicationSpec spec) { } 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 @@ protected void waitForApplicationToBeRunning(final URI applicationRef) { 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 apps = getManagementContext().getApplications(); + for (Application app: apps) + Entities.dumpInfo(app); + } assertTrue(started); } 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 82d1ab7d22..cb68cf3853 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.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 RestMockAppBuilder() { @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")); } } 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 10ee1ccdc0..d1c6cb2132 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 RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags, Entity pare public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags) { super(flags); } + + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } @SetFromFlag("sampleConfig") public static final ConfigKey SAMPLE_CONFIG = new BasicConfigKey( @@ -75,11 +81,6 @@ public String sampleEffector(@EffectorParam(name="param1", description="param on return result; } - @Override - public void waitForServiceUp() { - return; - } - @SuppressWarnings("rawtypes") @Override public Class getDriverInterface() { 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 3b8dab2805..693c8077db 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 static void assertAttributeChangesEventually2(final Entity entity, fi assertAttributeEventually(entity, attribute, Predicates.not(Predicates.equalTo(entity.getAttribute(attribute)))); } + } From ab1381b413543b69de62b498fe61f05b46cf9cae Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 26 Aug 2014 22:17:20 -0500 Subject: [PATCH 18/22] 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()` --- .../downloads/BasicDownloadTargets.java | 2 +- .../brooklyn/event/basic/ListConfigKey.java | 2 +- .../brooklyn/event/basic/SetConfigKey.java | 2 +- .../brooklyn/entity/basic/EntityTypeTest.java | 4 +-- .../location/jclouds/JcloudsUtil.java | 2 +- .../entity/chef/KnifeTaskFactory.java | 4 +-- .../rest/transform/LocationTransformer.java | 2 +- .../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(-) 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 eca50ea97b..2b1c545f88 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 BasicDownloadTargets build() { protected BasicDownloadTargets(Builder builder) { primaries = ImmutableList.copyOf(builder.primaries); - fallbacks = MutableList.builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().toImmutable(); + fallbacks = MutableList.builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().asUnmodifiable(); canContinueResolving = builder.canContinueResolving; } diff --git a/core/src/main/java/brooklyn/event/basic/ListConfigKey.java b/core/src/main/java/brooklyn/event/basic/ListConfigKey.java index b56d9e9380..0ad4d54c69 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 ListConfigKey(Class subType, String name, String description, List merge(boolean unmodifiable, Iterable... sets) { MutableList result = MutableList.of(); for (Iterable set: sets) result.addAll(set); - if (unmodifiable) return result.toImmutable(); + if (unmodifiable) return result.asUnmodifiable(); return result; } diff --git a/core/src/main/java/brooklyn/event/basic/SetConfigKey.java b/core/src/main/java/brooklyn/event/basic/SetConfigKey.java index 2f046091ec..fcd4afe916 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 SetConfigKey(Class subType, String name, String description, Set merge(boolean unmodifiable, Iterable... sets) { MutableSet result = MutableSet.of(); for (Iterable set: sets) result.addAll(set); - if (unmodifiable) return result.toImmutable(); + if (unmodifiable) return result.asUnmodifiable(); return result; } diff --git a/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java b/core/src/test/java/brooklyn/entity/basic/EntityTypeTest.java index 5c54e22f3a..6dd5693a62 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 void testRemoveSensorThroughEntity() throws Exception{ 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 void testRemoveSensors() throws Exception { 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), 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 9cb48305c2..e71f72761a 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 static ComputeService findComputeService(ConfigBag conf, boolean allowReu .put("credential", credential) .putIfNotNull("endpoint", endpoint) .build() - .toImmutable(); + .asUnmodifiable(); if (allowReuse) { ComputeService result = cachedComputeServices.get(cacheKey); 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 243ff8d32e..8a0ffed7b5 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 List, Void>> getCompletionListeners() { MutableList, Void>> result = MutableList.copyOf(super.getCompletionListeners()); if (throwOnCommonKnifeErrors != Boolean.FALSE) insertKnifeCompletionListenerIntoCompletionListenersList(result); - return result.toImmutable(); + return result.asUnmodifiable(); } public KnifeTaskFactory notThrowingOnCommonKnifeErrors() { @@ -127,7 +127,7 @@ public List getCommands() { } if (numKnifes==0) result.add(buildKnifeCommand(numKnifes++)); - return result.toImmutable(); + return result.asUnmodifiable(); } /** creates the command for running knife. 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 d9b5ff56a6..79549f27b0 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 static LocationSummary newInstance(ManagementContext mgmt, Location l, Lo 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() ); } } 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 2c002437c3..a6638da294 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 @@ 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 extends ArrayList { private static final long serialVersionUID = -5533940507175152491L; + private static final Logger log = LoggerFactory.getLogger(MutableList.class); + public static MutableList of() { return new MutableList(); } @@ -74,10 +80,25 @@ public MutableList(Iterable source) { } } + /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated public ImmutableList toImmutable() { return ImmutableList.copyOf(this); } - + public List 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 asUnmodifiable() { + return Collections.unmodifiableList(this); + } + public List asUnmodifiableCopy() { + return Collections.unmodifiableList(MutableList.copyOf(this)); + } + public static Builder builder() { return new Builder(); } 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 2d21d67485..2630dbf6dc 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 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 @@ /** 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 extends LinkedHashMap { + private static final long serialVersionUID = -2463168443382874384L; + private static final Logger log = LoggerFactory.getLogger(MutableMap.class); public static MutableMap of() { return new MutableMap(); @@ -136,9 +142,24 @@ public Maybe getMaybe(K key) { 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 toImmutable() { return ImmutableMap.copyOf(this); } + public Map 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 asUnmodifiable() { + return Collections.unmodifiableMap(this); + } + public Map asUnmodifiableCopy() { + return Collections.unmodifiableMap(MutableMap.copyOf(this)); + } public static Builder builder() { return new Builder(); 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 cee4176a7c..0653cfd3c5 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 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 extends LinkedHashSet { + private static final long serialVersionUID = 2330133488446834595L; + private static final Logger log = LoggerFactory.getLogger(MutableSet.class); public static MutableSet of() { return new MutableSet(); @@ -69,10 +74,25 @@ public MutableSet(Iterable source) { super((source instanceof Collection) ? (Collection)source : Sets.newLinkedHashSet(source)); } + /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated public Set 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 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 asUnmodifiable() { + return Collections.unmodifiableSet(this); + } + public Set asUnmodifiableCopy() { + return Collections.unmodifiableSet(MutableSet.copyOf(this)); + } public static Builder builder() { return new Builder(); @@ -103,6 +123,12 @@ public Builder remove(V val) { return this; } + public Builder addAll(V[] values) { + for (V v : values) { + result.add(v); + } + return this; + } public Builder addAll(Iterable iterable) { if (iterable instanceof Collection) { result.addAll((Collection) iterable); 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 089cfe4278..e7dc5bf938 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 void testEqualsDifferentTypes2() { Assert.assertEquals(b, a); } + public void testContainingNullAndUnmodifiable() { + MutableList x = MutableList.of("x", null); + Assert.assertTrue(x.contains(null)); + + List x1 = x.asUnmodifiable(); + List x2 = x.asUnmodifiableCopy(); + List 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")); + } + } From 31c5a0c2fd15ec23904bf438ba7b39579064dc8c Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 26 Aug 2014 22:10:47 -0500 Subject: [PATCH 19/22] write comprehensive tests for ServiceStateLogic, and tidy some of the semantics and usages elsewhere --- .../brooklyn/enricher/basic/Transformer.java | 5 +- .../entity/basic/AbstractApplication.java | 10 +- .../brooklyn/entity/basic/AbstractEntity.java | 10 + .../brooklyn/entity/basic/EntityAdjuncts.java | 69 +++++ .../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(-) create mode 100644 core/src/main/java/brooklyn/entity/basic/EntityAdjuncts.java create mode 100644 core/src/test/java/brooklyn/entity/basic/ServiceStateLogicTest.java diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java index c517e881e7..81ee346160 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 void onEvent(SensorEvent event) { } protected Object compute(SensorEvent event) { - return transformation.apply(event); + U result = transformation.apply(event); + if (LOG.isTraceEnabled()) + LOG.trace("Enricher "+this+" computed "+result+" from "+event); + return result; } } diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java index 961906fffb..4cab77c237 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.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 @@ protected void initEnrichers() { // 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 void start(Collection locations) { Collection 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 void postStart(Collection locations) { 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 void stop() { 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); diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index eb5f09975b..a612b0ceb7 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 T getAttribute(AttributeSensor attribute) { return attributesInternal.getValue(attribute); } + @SuppressWarnings("unchecked") public T getAttributeByNameParts(List nameParts) { return (T) attributesInternal.getValue(nameParts); } @Override public T setAttribute(AttributeSensor 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 T setAttribute(AttributeSensor attribute, T val) { @Override public T setAttributeWithoutPublishing(AttributeSensor 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 T setAttributeWithoutPublishing(AttributeSensor attribute, T val) @Override public void removeAttribute(AttributeSensor attribute) { + if (LOG.isTraceEnabled()) + LOG.trace(""+this+" removeAttribute "+attribute); + attributesInternal.remove(attribute); entityType.removeSensor(attribute); } 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 0000000000..ab1d003de8 --- /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 findWithUniqueTag(Iterable adjuncts, Object tag) { + Preconditions.checkNotNull(tag, "tag"); + for (T adjunct: adjuncts) + if (tag.equals(adjunct.getUniqueTag())) + return adjunct; + return null; + } + + public static final List 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 getNonSystemEnrichers(Entity entity) { + List result = MutableList.copyOf(entity.getEnrichers()); + Iterator 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; + } + +} diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java index 17195e8b3d..523541339f 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 boolean isQuorate(int sizeHealthy, int totalSize) { @Override public String toString() { - return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%]"; + return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]"; } } diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index 749c83d67a..388f80da96 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.Date; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; @@ -47,6 +48,7 @@ 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.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 static final EnricherSpec newEnricherForServiceUpIfNoNotUpIndicators() } /** 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 @@ protected Lifecycle computeActualStateWhenExpectedRunning(Map pr protected Lifecycle computeActualStateWhenNotExpectedRunning(Map 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 @@ 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)); } + } public static final EnricherSpec newEnricherForServiceStateFromProblemsAndUp() { @@ -293,8 +313,12 @@ public static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr "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 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 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 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 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 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> IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES = ConfigKeys.newConfigKey(new TypeToken>() {}, + "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.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 void setEntity(EntityLocal entity) { } } + final static Set> RECONFIGURABLE_KEYS = ImmutableSet.>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 void doReconfigureConfig(ConfigKey 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> SOURCE_SENSORS = ImmutableList.>of(SERVICE_UP, SERVICE_STATE_ACTUAL); @Override protected Collection> getSourceSensors() { @@ -334,8 +379,13 @@ protected Collection> getSourceSensors() { @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 @@ protected void onUpdated() { protected Object computeServiceNotUp() { Map values = getValues(SERVICE_UP); List violators = MutableList.of(); - boolean ignoreNull = getConfig(IGNORE_NULL_VALUES); + boolean ignoreNull = getConfig(IGNORE_ENTITIES_WITH_SERVICE_UP_NULL); int entries=0; for (Map.Entry state: values.entrySet()) { if (ignoreNull && state.getValue()==null) @@ -379,13 +429,10 @@ protected Object computeServiceNotUp() { protected Object computeServiceProblems() { Map values = getValues(SERVICE_STATE_ACTUAL); int numRunning=0, numNotHealthy=0; - boolean ignoreNull = getConfig(IGNORE_NULL_VALUES); - boolean ignoreTransition = getConfig(IGNORE_TRANSITIONING); + Set 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); diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java index 1023db6d53..553d4534f2 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 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 DynamicClusterImpl() { @Override public void init() { super.init(); - setAttribute(SERVICE_UP, false); } @Override @@ -149,6 +149,9 @@ 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()); + setAttribute(SERVICE_UP, true); + } else { + setAttribute(SERVICE_UP, false); } super.initEnrichers(); // override previous enricher so that only members are checked diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java index aeffe6a359..12f1cad1ab 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.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.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 @@ @SuppressWarnings("serial") public class EnrichersTest extends BrooklynAppUnitTestSupport { - public static List getNonSystemEnrichers(Entity entity) { - List result = MutableList.copyOf(entity.getEnrichers()); - Iterator ri = result.iterator(); - while (ri.hasNext()) { - if (isSystemEnricher(ri.next())) ri.remove(); - } - return result; - } - - public static final List 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 NUM1 = Sensors.newIntegerSensor("test.num1"); public static final AttributeSensor NUM2 = Sensors.newIntegerSensor("test.num2"); public static final AttributeSensor NUM3 = Sensors.newIntegerSensor("test.num3"); @@ -117,7 +90,7 @@ public void testAdding() { .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); 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 0000000000..ca5d8c1078 --- /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.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.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.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.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 void assertAttributeEqualsEventually(Entity x, AttributeSensor 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 void assertAttributeEquals(Entity x, AttributeSensor 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); + } + } + +} diff --git a/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/brooklyn/test/entity/TestEntityImpl.java index 3ccf614d60..d58aa86323 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 void stop() { ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING); counter.decrementAndGet(); ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED); + setAttribute(SERVICE_UP, false); } @Override 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 7d3feaf167..4391ba917b 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.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 void testWithAppEnricher() throws Exception { 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 void testWithEntityEnricher() throws Exception { 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() { @Override public Integer get() { - return EnrichersTest.getNonSystemEnrichers(child).size(); + return EntityAdjuncts.getNonSystemEnrichers(child).size(); } }, Predicates. 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 void testPropogateChildSensor() throws Exception { Asserts.eventually(new Supplier() { @Override public Integer get() { - return EnrichersTest.getNonSystemEnrichers(parentEntity).size(); + return EntityAdjuncts.getNonSystemEnrichers(parentEntity).size(); } }, Predicates.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() { @@ -242,7 +242,7 @@ public Entity call() throws Exception { } private Enricher getEnricher(Entity entity) { - List enrichers = EnrichersTest.getNonSystemEnrichers(entity); + List 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); From 099a5496e4ec48bb31d11a3be82c92831aef2ceb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 27 Aug 2014 01:10:08 -0400 Subject: [PATCH 20/22] convert SERVICE_STATE to _ACTUAL or _EXPECTED in many more places, and force recompute of _ACTUAL whenever _EXPECTED is changed --- .../brooklyn/entity/basic/AbstractEntity.java | 2 +- .../entity/basic/ServiceStateLogic.java | 5 +++++ .../ha/MemberFailureDetectionPolicy.java | 8 ++++--- .../brooklyn/policy/ha/ServiceReplacer.java | 5 ++--- .../brooklyn/policy/ha/ServiceRestarter.java | 5 +++-- .../policy/ha/ServiceReplacerTest.java | 4 ++-- .../policy/ha/ServiceRestarterTest.java | 4 ++-- .../chef/ChefLifecycleEffectorTasks.java | 8 +++---- .../brooklyn/entity/pool/ServerPoolImpl.java | 4 ++-- .../MachineLifecycleEffectorTasks.java | 2 +- .../mysql/AbstractToyMySqlEntityTest.java | 4 ++-- .../cassandra/CassandraDatacenterImpl.java | 17 ++++++++++---- .../nosql/cassandra/CassandraFabricImpl.java | 2 +- .../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(-) diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index a612b0ceb7..5e21fd7e0a 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 void init() { } /** - * 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). diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index 388f80da96..5154bff748 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 static void updateMapSensorEntry(EntityLocal entity, Attribut 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); diff --git a/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java b/policy/src/main/java/brooklyn/policy/ha/MemberFailureDetectionPolicy.java index d1423a4e85..5a6fa0a7a3 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.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 USE_SERVICE_STATE_RUNNING = ConfigKeys.newBooleanConfigKey("useServiceStateRunning", "", true); + @SuppressWarnings("serial") @SetFromFlag("memberFilter") - public static final ConfigKey> MEMBER_FILTER = (ConfigKey) ConfigKeys.newConfigKey(Predicate.class, "memberFilter", "", Predicates.alwaysTrue()); + public static final ConfigKey> MEMBER_FILTER = ConfigKeys.newConfigKey(new TypeToken>() {}, "memberFilter", "", Predicates.alwaysTrue()); private final Map memberFailures = Maps.newLinkedHashMap(); private final Map memberLastUps = Maps.newLinkedHashMap(); @@ -108,7 +110,7 @@ public void setEntity(EntityLocal entity) { super.setEntity(entity); if (getConfig(USE_SERVICE_STATE_RUNNING)) { - subscribeToMembers((Group)entity, Attributes.SERVICE_STATE, new SensorEventListener() { + subscribeToMembers((Group)entity, Attributes.SERVICE_STATE_ACTUAL, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { if (!acceptsMember(event.getSource())) return; onMemberStatus(event.getSource(), event.getValue()); @@ -165,7 +167,7 @@ private synchronized void onMemberStatus(Entity member, Lifecycle status) { 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); } diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java b/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java index 18c6f53d26..9142c786d0 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 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 @@ protected void onReplacementFailed(String msg) { 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)); } diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java index cc86be0b1d..06e6c771d3 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.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 @@ protected synchronized void onDetectedFailure(SensorEvent event) { 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 @@ protected synchronized void onDetectedFailure(SensorEvent event) { 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)); } diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java index 8d1a6832d9..900405b2fb 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 void testDoesNotOnFireWhenFailToReplaceMember() throws Exception { // 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 void testStopFailureOfOldEntityDoesNotSetClusterOnFire() throws Exception // 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); }}); } diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java index 17df8c6088..0d27b0ca0e 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 void testEmitsFailureEventWhenRestarterFails() throws Exception { 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 void testDoesNotSetOnFireOnFailure() throws Exception { 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); }}); } 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 16a9bed98c..f257989757 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 @@ protected boolean tryStopService() { 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 @@ protected boolean tryStopWindowsService() { 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 @@ protected boolean tryStopWindowsService() { 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; } 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 7e4f907a35..ddc9add87f 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 Collection addExistingMachinesFromSpec(String spec) { */ @Override protected Collection 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 Function, Entity> getRemovalStrategy() { public Entity apply(Collection members) { synchronized (mutex) { Optional 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 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 b708b616e7..746a2bed52 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 @@ *
    • {@link #postStartCustom()} *
    • {@link #preStopCustom()} * - * 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 **/ 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 231ea8bade..bbc1a69b10 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 @@ protected Integer getPid(Entity mysql) { 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)); } 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 b04e362f66..603fb6d825 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 @@ private Set trim(int num, Set contenders) { protected SeedTracker seedTracker = new SeedTracker(); protected TokenGenerator tokenGenerator = null; - private MemberTrackingPolicy policy; public CassandraDatacenterImpl() { } @@ -176,6 +175,15 @@ public void onEvent(SensorEvent event) { seedTracker.onServiceUpChanged(event.getSource(), event.getValue()); } }); + subscribeToMembers(this, Attributes.SERVICE_STATE_ACTUAL, new SensorEventListener() { + @Override + public void onEvent(SensorEvent 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() { @@ -296,6 +304,7 @@ public Collection grow(int delta) { return super.grow(delta); } + @SuppressWarnings("deprecation") @Override protected Entity createNode(@Nullable Location loc, Map flags) { Map allflags = MutableMap.copyOf(flags); @@ -357,7 +366,7 @@ public void start(Collection locations) { 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 boolean isViableSeed(Entity member) { 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 boolean isViableSeed(Entity member) { 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; 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 fe0b503196..2f874e6b17 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 boolean isViableSeed(Entity member) { 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}); 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 944e99e13b..9c9cd627e6 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.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 void init() { .propagating(MongoDBConfigServerCluster.CONFIG_SERVER_ADDRESSES) .from(getAttribute(CONFIG_SERVER_CLUSTER)) .build()); + + ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "stopped"); } @Override public void start(Collection 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 void start(Collection locations) { .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 @@ protected void onEntityRemoved(Entity member) { @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 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 4437faf925..f4a786aaa6 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.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 static class DummyCassandraNodeImpl extends AbstractEntity implements Dum @Override public void start(Collection 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 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 2b70f7c078..0f053bc34d 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 INCLUDE_HOMELESS_ENTITIES = ConfigKeys.newBooleanConfigKey("geodns.includeHomeless", "Whether to include entities whose geo-coordinates cannot be inferred", false); public static final ConfigKey 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 SERVICE_STATE = Attributes.SERVICE_STATE; + public static final AttributeSensor SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; public static final AttributeSensor SERVICE_UP = Startable.SERVICE_UP; public static final AttributeSensor HOSTNAME = Attributes.HOSTNAME; public static final AttributeSensor ADDRESS = Attributes.ADDRESS; 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 3d632aa283..f931bb19d6 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.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 void destroy() { @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 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 024d01bb86..7e470ca30a 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.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 void onManagementBecomingMaster() { 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(); } 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 df0fd6a0d1..bda6bd90ef 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.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 void testWithDbDeploy() throws IOException { 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() { @@ -240,7 +241,7 @@ public void testWithPolicyDeploy() { 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() { 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 950c4654a7..336b5fc3bb 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 void testInvocationSensorAndEnricher() throws Exception { 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 pingerI = Iterables.filter(app.getChildren(), EntityPredicates.displayNameEqualTo("Simple Pinger")); @@ -85,10 +85,10 @@ public void testInvocationSensorAndEnricher() throws Exception { 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.of("message", "yo yo yo")); 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 bb54d06766..93b69f274c 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 void testServiceMirroring() throws Exception { 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 void testServiceMirroringHttps() throws Exception { 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); } } 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 e4314fcd59..d6554e4438 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 @@ private ObjectNode entityBase(Entity entity) { 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(); From 6a8af41e70ab96e361fb58953c2c3f072ff12678 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 26 Aug 2014 08:46:48 -0500 Subject: [PATCH 21/22] docs for service state --- .../entity/basic/AbstractGroupImpl.java | 1 + .../defining-applications/service-state.md | 73 +++++++++++++++++++ docs/use/guide/defining-applications/toc.json | 2 + 3 files changed, 76 insertions(+) create mode 100644 docs/use/guide/defining-applications/service-state.md diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java index e3520f6300..a8ace7f515 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 void init() { 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 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 0000000000..b142fb1154 --- /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 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`. + + + + +## 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. + diff --git a/docs/use/guide/defining-applications/toc.json b/docs/use/guide/defining-applications/toc.json index c8b32aa487..08940a82fa 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" } ] From 225f182078b700ab6282a3aac000d3d304870bdd Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 29 Aug 2014 18:35:08 -0400 Subject: [PATCH 22/22] address svet code review comments --- .../brooklyn/enricher/basic/AbstractAggregator.java | 2 +- .../basic/AbstractMultipleSensorAggregator.java | 11 ++++++++--- .../java/brooklyn/enricher/basic/Aggregator.java | 12 ++++++++---- .../java/brooklyn/entity/basic/AbstractEntity.java | 2 +- .../main/java/brooklyn/entity/basic/Lifecycle.java | 7 ------- .../brooklyn/entity/basic/ServiceStateLogic.java | 2 +- .../loadbalancing/LoadBalancingPolicyTest.java | 7 +++---- .../java/brooklyn/util/collections/MutableList.java | 8 ++++++++ .../java/brooklyn/util/collections/MutableMap.java | 5 +++++ .../java/brooklyn/util/collections/MutableSet.java | 6 ++++++ .../main/java/brooklyn/util/guava/IfFunctions.java | 6 +++--- .../java/brooklyn/util/guava/IfFunctionsTest.java | 2 +- 12 files changed, 45 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java index ba7762ce04..9568332c1c 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 void setEntity(EntityLocal entity) { 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) { diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractMultipleSensorAggregator.java index 85c36d852e..07b047773d 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 @@ protected void setEntityLoadingConfig() { 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 @@ protected void setEntityBeforeSubscribingProducerChildrenEvents() { } } + @Override protected void addProducerHardcoded(Entity producer) { for (Sensor sourceSensor: getSourceSensors()) { subscribe(producer, sourceSensor, this); @@ -69,17 +71,18 @@ protected void addProducerHardcoded(Entity producer) { 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 @@ protected void onProducerAdded(Entity producer) { } } + @Override protected void onProducerRemoved(Entity producer) { synchronized (values) { for (Sensor sensor: getSourceSensors()) { @@ -140,5 +144,6 @@ public Map getValues(Sensor sensor) { } } + @Override protected abstract Object compute(); } diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java index 034a6047f3..e865799f07 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 @@ protected void setEntityLoadingConfig() { this.transformation = (Function, ? 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 @@ protected void onProducerAdded(Entity producer) { } } + @Override protected void onProducerRemoved(Entity producer) { values.remove(producer); onUpdated(); @@ -142,6 +145,7 @@ protected void onUpdated() { } } + @Override protected Object compute() { synchronized (values) { // TODO Could avoid copying when filter not needed diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index 5e21fd7e0a..ec2d5e96cf 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 void init() { // 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()); } diff --git a/core/src/main/java/brooklyn/entity/basic/Lifecycle.java b/core/src/main/java/brooklyn/entity/basic/Lifecycle.java index 5e7d0fe15d..26d39161fc 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. diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java index 5154bff748..95d5e57996 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 static class ServiceNotUpLogic { 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) diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java index c0ff7b77b7..e0a720b096 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.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 void testAddItem() { 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 void testRebalancesAfterManualMove() { ImmutableList.of(40d, 40d)); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() { pool.removePolicy(policy); @@ -312,7 +311,7 @@ public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() { // 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); 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 a6638da294..1c1b2d2784 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.util.exceptions.Exceptions; + import com.google.common.collect.ImmutableList; public class MutableList extends ArrayList { @@ -84,17 +86,23 @@ public MutableList(Iterable source) { public ImmutableList 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 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 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 asUnmodifiableCopy() { return Collections.unmodifiableList(MutableList.copyOf(this)); } 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 2630dbf6dc..f8b2f6cd83 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 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 Maybe getMaybe(K key) { public ImmutableMap toImmutable() { return ImmutableMap.copyOf(this); } + /** as {@link MutableList#asImmutableCopy()} */ public Map 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 asUnmodifiable() { return Collections.unmodifiableMap(this); } + /** as {@link MutableList#asUnmodifiableCopy()} */ public Map asUnmodifiableCopy() { return Collections.unmodifiableMap(MutableMap.copyOf(this)); } 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 0653cfd3c5..e79515800f 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 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 Set toImmutable() { // Don't use ImmutableSet as that does not accept nulls return Collections.unmodifiableSet(Sets.newLinkedHashSet(this)); } + /** as {@link MutableList#asImmutableCopy()()} */ public Set 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 asUnmodifiable() { return Collections.unmodifiableSet(this); } + /** as {@link MutableList#asUnmodifiableCopy()} */ public Set asUnmodifiableCopy() { return Collections.unmodifiableSet(MutableSet.copyOf(this)); } 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 5384436b3a..ceea8c3393 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 static IfFunctionBuilder newInstance(Class testType, Class< return new IfFunctionBuilder(); } - public static IfFunctionBuilderApplyingFirst ifPredicate(Predicate test) { + public static IfFunctionBuilderApplyingFirst ifPredicate(Predicate test) { return new IfFunctionBuilderApplyingFirst(test); } - public static IfFunctionBuilderApplyingFirst ifEquals(I test) { + public static IfFunctionBuilderApplyingFirst ifEquals(I test) { return ifPredicate(Predicates.equalTo(test)); } - public static IfFunctionBuilderApplyingFirst ifNotEquals(I test) { + public static IfFunctionBuilderApplyingFirst ifNotEquals(I test) { return ifPredicate(Predicates.not(Predicates.equalTo(test))); } 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 74a5d5bc0b..e52dc34292 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 String apply(Boolean input) { @Test public void testWithCast() { - Function f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(); + Function f = IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?").build(); checkTF(f, "?"); }