From f2bd6538d32dddcab6820059f5910f09bda79770 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 31 Jan 2017 09:32:29 +0000 Subject: [PATCH 1/7] An entity that emits a sequence of integers --- .../brooklyn/entity/stock/SequenceEntity.java | 112 +++++++++++ .../entity/stock/SequenceEntityImpl.java | 99 ++++++++++ .../entity/stock/SequenceEntityTest.java | 181 ++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java create mode 100644 core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java create mode 100644 core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java new file mode 100644 index 0000000000..5135f3cc3e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java @@ -0,0 +1,112 @@ +/* + * 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 org.apache.brooklyn.entity.stock; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.MethodEffector; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.text.StringPredicates; + +import com.google.common.base.Predicates; + +/** + * An entity that supplies a sequence of values through an effector. + *

+ * Usage: + *

{@code
+ * - type: org.apache.brooklyn.entity.stock.SequenceEntity
+ *   id: global-sequence
+ *   brooklyn.config:
+ *     sequence.start: 0
+ *     sequence.increment: 1
+ *     sequence.format: "global-%03d"
+ *     sequence.name: "global"
+ * }
+ */ +@ImplementedBy(SequenceEntityImpl.class) +public interface SequenceEntity extends Entity, Startable { + + @SetFromFlag("sequenceStart") + ConfigKey SEQUENCE_START = ConfigKeys.builder(Integer.class) + .name("sequence.start") + .description("The starting point of the sequence") + .defaultValue(1) + .constraint(Predicates.notNull()) + .build(); + + @SetFromFlag("sequenceIncrement") + ConfigKey SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) + .name("sequence.increment") + .description("The sequence increment for the next value") + .defaultValue(1) + .constraint(Predicates.notNull()) + .build(); + + @SetFromFlag("sequenceFormat") + ConfigKey SEQUENCE_FORMAT = ConfigKeys.builder(String.class) + .name("sequence.format") + .description("A format used to generate a string representation of the sequence") + .defaultValue("%d") + .constraint(StringPredicates.containsRegex("%[-#+ 0,(]*[0-9]*[doxX]")) + .build(); + + @SetFromFlag("sequenceName") + AttributeSensorAndConfigKey SEQUENCE_NAME = ConfigKeys.newStringSensorAndConfigKey("sequence.name", "The name of the sequence", "sequence"); + + AttributeSensor SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") + .description("The current value of the sequence") + .build(); + + AttributeSensor SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") + .description("The current value of the sequence formatted as a string") + .build(); + + MethodEffector RESET = new MethodEffector(SequenceEntity.class, "reset"); + MethodEffector INCREMENT = new MethodEffector(SequenceEntity.class, "increment"); + MethodEffector CURRENT_VALUE = new MethodEffector(SequenceEntity.class, "currentValue"); + MethodEffector CURRENT_STRING = new MethodEffector(SequenceEntity.class, "currentString"); + MethodEffector NEXT_VALUE = new MethodEffector(SequenceEntity.class, "nextValue"); + MethodEffector NEXT_STRING = new MethodEffector(SequenceEntity.class, "nextString"); + + @Effector(description = "Reset the sequence to initial value") + Void reset(); + + @Effector(description = "Update the value of the sequence by the configured increment") + Void increment(); + + @Effector(description = "Return the current numeric value of the sequence") + Integer currentValue(); + + @Effector(description = "Return the current string representation of the sequence") + String currentString(); + + @Effector(description = "Update and return the next numeric value of the sequence") + Integer nextValue(); + + @Effector(description = "Update and return the next string representation of the sequence") + String nextString(); +} diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java new file mode 100644 index 0000000000..338b323ae9 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java @@ -0,0 +1,99 @@ +/* + * 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 org.apache.brooklyn.entity.stock; + +import java.util.Collection; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.entity.AbstractEntity; +import org.apache.brooklyn.core.feed.ConfigToAttributes; + +public class SequenceEntityImpl extends AbstractEntity implements SequenceEntity { + + public SequenceEntityImpl() { } + + public void init() { + super.init(); + + ConfigToAttributes.apply(this, SEQUENCE_NAME); + } + + @Override + public void start(Collection locations) { + addLocations(locations); + reset(); + sensors().set(SERVICE_UP, Boolean.TRUE); + } + + @Override + public void stop() { + sensors().set(SERVICE_UP, Boolean.FALSE); + } + + @Override + public void restart() { + stop(); + start(getLocations()); + } + + protected void sequence(Integer value) { + String format = config().get(SEQUENCE_FORMAT); + + sensors().set(SEQUENCE_VALUE, value); + sensors().set(SEQUENCE_STRING, String.format(format, value)); + } + + @Override + public Integer currentValue() { + return sensors().get(SEQUENCE_VALUE); + } + + @Override + public String currentString() { + return sensors().get(SEQUENCE_STRING); + } + + @Override + public synchronized Integer nextValue() { + increment(); + return currentValue(); + } + + @Override + public synchronized String nextString() { + increment(); + return currentString(); + } + + @Override + public synchronized Void increment() { + Integer increment = config().get(SEQUENCE_INCREMENT); + Integer current = currentValue(); + sequence(current + increment); + return null; + } + + @Override + public synchronized Void reset() { + Integer start = config().get(SEQUENCE_START); + sequence(start); + return null; + } + +} diff --git a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java new file mode 100644 index 0000000000..2b20f8d591 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java @@ -0,0 +1,181 @@ +/* + * 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 org.apache.brooklyn.entity.stock; + +import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEquals; +import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEqualsEventually; +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.location.SimulatedLocation; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class SequenceEntityTest extends BrooklynAppUnitTestSupport { + + private SimulatedLocation loc1; + private SequenceEntity sequence; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + loc1 = mgmt.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)); + } + + @Test + public void testSequenceInitial() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class) + .configure(SequenceEntity.SEQUENCE_START, 0)); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 0); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "0"); + } + + @Test + public void testSequenceInitialConfig() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class) + .configure(SequenceEntity.SEQUENCE_NAME, "forty-two") + .configure(SequenceEntity.SEQUENCE_START, 42) + .configure(SequenceEntity.SEQUENCE_FORMAT, "id-%02x")); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 42); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "id-2a"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_NAME, "forty-two"); + } + + @Test + public void testSequenceIncrementEffector() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class)); + app.start(ImmutableList.of(loc1)); + + EntityAsserts.assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertEquals(sequence.currentValue(), Integer.valueOf(1)); + assertEquals(sequence.currentString(), "1"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 1); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "1"); + + sequence.increment(); + + assertEquals(sequence.currentValue(), Integer.valueOf(2)); + assertEquals(sequence.currentString(), "2"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 2); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "2"); + + sequence.invoke(SequenceEntity.INCREMENT, ImmutableMap.of()).getUnchecked(); + + assertEquals(sequence.currentValue(), Integer.valueOf(3)); + assertEquals(sequence.currentString(), "3"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 3); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "3"); + } + + @Test + public void testSequenceIncrementEffectorConfig() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class) + .configure(SequenceEntity.SEQUENCE_START, 0) + .configure(SequenceEntity.SEQUENCE_INCREMENT, 2) + .configure(SequenceEntity.SEQUENCE_FORMAT, "%03d")); + app.start(ImmutableList.of(loc1)); + + EntityAsserts.assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertEquals(sequence.currentValue(), Integer.valueOf(0)); + assertEquals(sequence.currentString(), "000"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 0); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "000"); + + sequence.increment(); + + assertEquals(sequence.currentValue(), Integer.valueOf(2)); + assertEquals(sequence.currentString(), "002"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 2); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "002"); + + sequence.invoke(SequenceEntity.INCREMENT, ImmutableMap.of()).getUnchecked(); + + assertEquals(sequence.currentValue(), Integer.valueOf(4)); + assertEquals(sequence.currentString(), "004"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 4); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "004"); + } + + @Test + public void testSequenceNextEffectors() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class)); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertEquals(sequence.currentValue(), Integer.valueOf(1)); + assertEquals(sequence.currentString(), "1"); + + Integer nextValue = sequence.invoke(SequenceEntity.NEXT_VALUE, ImmutableMap.of()).getUnchecked(); + assertEquals(nextValue, Integer.valueOf(2)); + + String nextString = sequence.invoke(SequenceEntity.NEXT_STRING, ImmutableMap.of()).getUnchecked(); + assertEquals(nextString, "3"); + + assertEquals(sequence.currentValue(), Integer.valueOf(3)); + assertEquals(sequence.currentString(), "3"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 3); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "3"); + } + + @Test + public void testSequenceReset() throws Exception { + sequence = app.addChild(EntitySpec.create(SequenceEntity.class)); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); + + assertEquals(sequence.currentValue(), Integer.valueOf(1)); + + sequence.increment(); + sequence.increment(); + sequence.increment(); + + assertEquals(sequence.currentValue(), Integer.valueOf(4)); + assertEquals(sequence.currentString(), "4"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 4); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "4"); + + sequence.invoke(SequenceEntity.RESET, ImmutableMap.of()).getUnchecked(); + + assertEquals(sequence.currentValue(), Integer.valueOf(1)); + assertEquals(sequence.currentString(), "1"); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 1); + assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "1"); + } + +} From 2728745be0f1222d84b0230bb5c5576b54765bdb Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 31 Jan 2017 10:27:27 +0000 Subject: [PATCH 2/7] A group that sets a sequence of values as sensors on members --- .../brooklyn/entity/group/SequenceGroup.java | 104 +++++++++++++++ .../entity/group/SequenceGroupImpl.java | 85 +++++++++++++ .../entity/group/SequenceGroupTest.java | 118 ++++++++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java create mode 100644 core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java create mode 100644 core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java new file mode 100644 index 0000000000..a6dee56ec3 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java @@ -0,0 +1,104 @@ +/* + * 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 org.apache.brooklyn.entity.group; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.MethodEffector; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.entity.stock.SequenceEntity; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.text.StringPredicates; + +import com.google.common.base.Predicates; +import com.google.common.reflect.TypeToken; + +/** + * A group that sets a sequence of values as sensors on its member entities + *

+ * Usage: + *

{@code
+ * - type: org.apache.brooklyn.entity.stock.SequenceGroup
+ *   id: entity-sequence
+ *   brooklyn.config:
+ *     entityFilter:
+ *       $brooklyn:object:
+ *         type: org.apache.brooklyn.core.entity.EntityPredicates
+ *         factoryMethod.name: "applicationIdEqualTo"
+ *         factoryMethod.args:
+ *           - $brooklyn:attributeWhenReady("application.id")
+ *     sequenceStart: 0
+ *     sequenceIncrement: 1
+ *     sequenceFormat: "entity%04x"
+ * }
+ */ +@ImplementedBy(SequenceGroupImpl.class) +public interface SequenceGroup extends DynamicGroup { + + @SetFromFlag("sequenceStart") + ConfigKey SEQUENCE_START = ConfigKeys.builder(Integer.class) + .name("sequence.start") + .description("The starting point of the sequence") + .defaultValue(1) + .constraint(Predicates.notNull()) + .build(); + + @SetFromFlag("sequenceIncrement") + ConfigKey SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) + .name("sequence.increment") + .description("The sequence increment for the next value") + .defaultValue(1) + .constraint(Predicates.notNull()) + .build(); + + @SetFromFlag("sequenceFormat") + ConfigKey SEQUENCE_FORMAT = ConfigKeys.builder(String.class) + .name("sequence.format") + .description("A format used to generate a string representation of the sequence") + .defaultValue("%d") + .constraint(StringPredicates.containsRegex("%[-#+ 0,(]*[0-9]*[doxX]")) + .build(); + + AttributeSensor SEQUENCE_NEXT = Sensors.builder(Integer.class, "sequence.next") + .description("The next value of the sequence") + .build(); + + AttributeSensor> SEQUENCE_CACHE = Sensors.builder(new TypeToken>() { }, "sequence.cache") + .description("The current cache of entity ids to sequence numbers") + .build(); + + MethodEffector RESET = new MethodEffector(SequenceEntity.class, "reset"); + + @Effector(description = "Reset the sequence to initial value") + Void reset(); + + AttributeSensor SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") + .description("The current value of the sequence") + .build(); + + AttributeSensor SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") + .description("The current value of the sequence formatted as a string") + .build(); + +} diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java new file mode 100644 index 0000000000..f9fc04efb4 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.brooklyn.entity.group; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Maps; + +public class SequenceGroupImpl extends DynamicGroupImpl implements SequenceGroup { + + private static final Logger LOG = LoggerFactory.getLogger(SequenceGroupImpl.class); + + public SequenceGroupImpl() { } + + @Override + public Void reset() { + synchronized (memberChangeMutex) { + sensors().set(SEQUENCE_CACHE, Maps.newConcurrentMap()); + Integer initial = config().get(SEQUENCE_START); + sensors().set(SEQUENCE_NEXT, initial); + return null; + } + } + + @Override + public void rescanEntities() { + synchronized (memberChangeMutex) { + reset(); + super.rescanEntities(); + } + } + + @Override + public boolean addMember(Entity member) { + synchronized (memberChangeMutex) { + boolean changed = super.addMember(member); + if (changed) { + Map cache = sensors().get(SEQUENCE_CACHE); + if (!cache.containsKey(member.getId())) { + Integer value = sequence(member); + cache.put(member.getId(), value); + } + } + return changed; + } + } + + private Integer sequence(Entity entity) { + String format = config().get(SEQUENCE_FORMAT); + Integer current = sensors().get(SEQUENCE_NEXT); + String string = String.format(format, current); + + entity.sensors().set(SEQUENCE_VALUE, current); + entity.sensors().set(SEQUENCE_STRING,string); + + Integer increment = config().get(SEQUENCE_INCREMENT); + Integer next = current + increment; + LOG.debug("Sequence for {} incremented to {}", this, next); + + sensors().set(SEQUENCE_NEXT, next); + + return current; + } + +} diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java new file mode 100644 index 0000000000..afb54251a5 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java @@ -0,0 +1,118 @@ +/* + * 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 org.apache.brooklyn.entity.group; + +import static org.apache.brooklyn.test.Asserts.assertEqualsIgnoringOrder; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.test.Asserts; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class SequenceGroupTest extends BrooklynAppUnitTestSupport { + + private TestApplication app; + private SequenceGroup group; + private TestEntity e1; + private TestEntity e2; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + + app = TestApplication.Factory.newManagedInstanceForTests(); + group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class)); + e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + e2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + + super.tearDown(); + } + + @Test + public void testGroupWithMatchingFilterReturnsOnlyMatchingMembers() throws Exception { + group.setEntityFilter(EntityPredicates.idEqualTo(e1.getId())); + + assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); + EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + } + + @Test + public void testGroupWithMatchingFilterReturnsEverythingThatMatches() throws Exception { + group.setEntityFilter(Predicates.alwaysTrue()); + + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, app, group)); + EntityAsserts.assertAttributeEquals(app, SequenceGroup.SEQUENCE_VALUE, 1); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_VALUE, 2); + EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 3); + EntityAsserts.assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 4); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 5); + } + + @Test + public void testGroupDetectsNewlyManagedMatchingMember() throws Exception { + group.setEntityFilter(EntityPredicates.displayNameEqualTo("myname")); + final Entity e3 = app.addChild(EntitySpec.create(TestEntity.class).displayName("myname")); + + Asserts.succeedsEventually(new Runnable() { + public void run() { + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e3)); + EntityAsserts.assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 1); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + }}); + } + + @Test + public void testGroupUsesNewFilter() throws Exception { + group.setEntityFilter(EntityPredicates.hasInterfaceMatching(".*TestEntity")); + + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2)); + EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); + EntityAsserts.assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 2); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 3); + + final Entity e3 = app.addChild(EntitySpec.create(TestEntity.class)); + + Asserts.succeedsEventually(new Runnable() { + public void run() { + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); + EntityAsserts.assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 3); + EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 4); + }}); + } +} From a5aff6801dceb2df81b41aa8b178a8f570903cba Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 7 Feb 2017 09:43:50 +0000 Subject: [PATCH 3/7] Add support for configurable sequence sensors and update tests --- .../brooklyn/entity/group/SequenceGroup.java | 70 +++++++++--- .../entity/group/SequenceGroupImpl.java | 32 +++++- .../entity/group/SequenceGroupTest.java | 107 +++++++++++++----- .../entity/stock/SequenceEntityTest.java | 2 - 4 files changed, 161 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java index a6dee56ec3..851d3b786e 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java @@ -20,6 +20,7 @@ import java.util.Map; +import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.ImplementedBy; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.config.ConfigKey; @@ -27,10 +28,10 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.sensor.Sensors; -import org.apache.brooklyn.entity.stock.SequenceEntity; import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.apache.brooklyn.util.text.StringPredicates; +import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.reflect.TypeToken; @@ -44,18 +45,45 @@ * brooklyn.config: * entityFilter: * $brooklyn:object: - * type: org.apache.brooklyn.core.entity.EntityPredicates - * factoryMethod.name: "applicationIdEqualTo" + * type: com.google.common.base.Predicates + * factoryMethod.name: "and" * factoryMethod.args: - * - $brooklyn:attributeWhenReady("application.id") + * - $brooklyn:object: + * type: org.apache.brooklyn.core.entity.EntityPredicates + * factoryMethod.name: "applicationIdEqualTo" + * factoryMethod.args: + * - $brooklyn:attributeWhenReady("application.id") + * - $brooklyn:object: + * type: org.apache.brooklyn.core.entity.EntityPredicates + * factoryMethod.name: "configEquelTo" + * factoryMethod.args: + * - "sequence.set" + * - true * sequenceStart: 0 * sequenceIncrement: 1 - * sequenceFormat: "entity%04x" + * sequenceFormat: "Entity %04x" + * sequenceValueSensor: $brooklyn:sensor("entity.sequence") + * sequenceStringSensor: $brooklyn:sensor("entity.name") * } + *

+ * IMPORTANT Relationship management will fail in unpredictable ways + * if an entity is a member of multiple sequences that use the same + * {@link #SEQUENCE_VALUE_SENSOR sensor}, however this usage will probably + * not have the desired effect in any case. */ @ImplementedBy(SequenceGroupImpl.class) public interface SequenceGroup extends DynamicGroup { + AttributeSensor SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") + .description("The current value of the sequence") + .build(); + + AttributeSensor SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") + .description("The current value of the sequence formatted as a string") + .build(); + + ConfigKey> ENTITY_FILTER = ConfigKeys.newConfigKeyWithDefault(DynamicGroup.ENTITY_FILTER, Predicates.alwaysFalse()); + @SetFromFlag("sequenceStart") ConfigKey SEQUENCE_START = ConfigKeys.builder(Integer.class) .name("sequence.start") @@ -65,7 +93,7 @@ public interface SequenceGroup extends DynamicGroup { .build(); @SetFromFlag("sequenceIncrement") - ConfigKey SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) + ConfigKey SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) .name("sequence.increment") .description("The sequence increment for the next value") .defaultValue(1) @@ -80,6 +108,26 @@ public interface SequenceGroup extends DynamicGroup { .constraint(StringPredicates.containsRegex("%[-#+ 0,(]*[0-9]*[doxX]")) .build(); + @SetFromFlag("sequenceValueSensor") + ConfigKey> SEQUENCE_VALUE_SENSOR = ConfigKeys.builder(new TypeToken>() { }) + .name("sequence.sensor.value") + .description("The sensor for the sequence value") + .defaultValue(SEQUENCE_VALUE) + .constraint(Predicates.>notNull()) + .build(); + + @SetFromFlag("sequenceStringSensor") + ConfigKey> SEQUENCE_STRING_SENSOR = ConfigKeys.builder(new TypeToken>() { }) + .name("sequence.sensor.string") + .description("The sensor for the sequence string") + .defaultValue(SEQUENCE_STRING) + .constraint(Predicates.>notNull()) + .build(); + + AttributeSensor SEQUENCE_CURRENT = Sensors.builder(Entity.class, "sequence.current") + .description("The current entity in the sequence") + .build(); + AttributeSensor SEQUENCE_NEXT = Sensors.builder(Integer.class, "sequence.next") .description("The next value of the sequence") .build(); @@ -88,17 +136,9 @@ public interface SequenceGroup extends DynamicGroup { .description("The current cache of entity ids to sequence numbers") .build(); - MethodEffector RESET = new MethodEffector(SequenceEntity.class, "reset"); + MethodEffector RESET = new MethodEffector(SequenceGroup.class, "reset"); @Effector(description = "Reset the sequence to initial value") Void reset(); - AttributeSensor SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") - .description("The current value of the sequence") - .build(); - - AttributeSensor SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") - .description("The current value of the sequence formatted as a string") - .build(); - } diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java index f9fc04efb4..fc9c526294 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +29,7 @@ public class SequenceGroupImpl extends DynamicGroupImpl implements SequenceGroup { - private static final Logger LOG = LoggerFactory.getLogger(SequenceGroupImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(SequenceGroup.class); public SequenceGroupImpl() { } @@ -36,6 +37,7 @@ public SequenceGroupImpl() { } public Void reset() { synchronized (memberChangeMutex) { sensors().set(SEQUENCE_CACHE, Maps.newConcurrentMap()); + sensors().set(SEQUENCE_CURRENT, null); Integer initial = config().get(SEQUENCE_START); sensors().set(SEQUENCE_NEXT, initial); return null; @@ -58,6 +60,7 @@ public boolean addMember(Entity member) { Map cache = sensors().get(SEQUENCE_CACHE); if (!cache.containsKey(member.getId())) { Integer value = sequence(member); + cache.put(member.getId(), value); } } @@ -65,13 +68,36 @@ public boolean addMember(Entity member) { } } + @Override + public boolean removeMember(Entity member) { + synchronized (memberChangeMutex) { + boolean changed = super.removeMember(member); + if (changed) { + Map cache = sensors().get(SEQUENCE_CACHE); + if (cache.containsKey(member.getId())) { + cache.remove(member.getId()); + + AttributeSensor valueSensor = config().get(SEQUENCE_VALUE_SENSOR); + AttributeSensor stringSensor = config().get(SEQUENCE_STRING_SENSOR); + member.sensors().set(valueSensor, null); + member.sensors().set(stringSensor, null); + } + } + return changed; + } + } + private Integer sequence(Entity entity) { String format = config().get(SEQUENCE_FORMAT); Integer current = sensors().get(SEQUENCE_NEXT); String string = String.format(format, current); + AttributeSensor valueSensor = config().get(SEQUENCE_VALUE_SENSOR); + AttributeSensor stringSensor = config().get(SEQUENCE_STRING_SENSOR); + + entity.sensors().set(valueSensor, current); + entity.sensors().set(stringSensor, string); - entity.sensors().set(SEQUENCE_VALUE, current); - entity.sensors().set(SEQUENCE_STRING,string); + sensors().set(SEQUENCE_CURRENT, entity); Integer increment = config().get(SEQUENCE_INCREMENT); Integer next = current + increment; diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java index afb54251a5..148eb5bede 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java @@ -19,16 +19,20 @@ package org.apache.brooklyn.entity.group; import static org.apache.brooklyn.test.Asserts.assertEqualsIgnoringOrder; +import static org.apache.brooklyn.test.Asserts.*; +import static org.apache.brooklyn.core.entity.EntityAsserts.*; + +import java.util.Set; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.core.entity.Entities; -import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; -import org.apache.brooklyn.test.Asserts; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -38,11 +42,10 @@ import com.google.common.collect.ImmutableSet; public class SequenceGroupTest extends BrooklynAppUnitTestSupport { - + private TestApplication app; private SequenceGroup group; - private TestEntity e1; - private TestEntity e2; + private TestEntity e1, e2, e3; @BeforeMethod(alwaysRun=true) @Override @@ -50,9 +53,12 @@ public void setUp() throws Exception { super.setUp(); app = TestApplication.Factory.newManagedInstanceForTests(); - group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class)); + group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.SEQUENCE_STRING_SENSOR, Sensors.newStringSensor("test.sequence")) + .configure(SequenceGroup.SEQUENCE_FORMAT, "test-%02d")); e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); e2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + e3 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); } @AfterMethod(alwaysRun=true) @@ -63,37 +69,75 @@ public void tearDown() throws Exception { super.tearDown(); } + + @Test + public void testGroupDefaults() throws Exception { + assertTrue(group.getMembers().isEmpty()); + } + @Test public void testGroupWithMatchingFilterReturnsOnlyMatchingMembers() throws Exception { group.setEntityFilter(EntityPredicates.idEqualTo(e1.getId())); assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); - EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + } + + @Test + public void testGroupConfiguration() throws Exception { + group.setEntityFilter(EntityPredicates.idEqualTo(e1.getId())); + + assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); + assertAttributeEquals(e1, SequenceGroup.SEQUENCE_STRING, null); + assertAttributeEquals(e1, Sensors.newStringSensor("test.sequence"), "test-01"); + } + + @Test + public void testAlternateGroupConfiguration() throws Exception { + AttributeSensor value = Sensors.newIntegerSensor("test.value"); + AttributeSensor string = Sensors.newStringSensor("test.string"); + group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.SEQUENCE_START, 12345) + .configure(SequenceGroup.SEQUENCE_INCREMENT, 678) + .configure(SequenceGroup.SEQUENCE_VALUE_SENSOR, value) + .configure(SequenceGroup.SEQUENCE_STRING_SENSOR, string) + .configure(SequenceGroup.SEQUENCE_FORMAT, "0x%04X")); + group.setEntityFilter(EntityPredicates.hasInterfaceMatching(".*TestEntity")); + + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); + assertAttributeEquals(e1, value, 12345); + assertAttributeEquals(e1, string, "0x3039"); + assertAttributeEquals(e2, value, 13023); + assertAttributeEquals(e2, string, "0x32DF"); + assertAttributeEquals(e3, value, 13701); + assertAttributeEquals(e3, string, "0x3585"); } @Test public void testGroupWithMatchingFilterReturnsEverythingThatMatches() throws Exception { group.setEntityFilter(Predicates.alwaysTrue()); - assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, app, group)); - EntityAsserts.assertAttributeEquals(app, SequenceGroup.SEQUENCE_VALUE, 1); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_VALUE, 2); - EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 3); - EntityAsserts.assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 4); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 5); + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3, app, group)); + assertAttributeEquals(app, SequenceGroup.SEQUENCE_VALUE, 1); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_VALUE, 2); + assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 3); + assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 4); + assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 5); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 6); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e3); } - + @Test public void testGroupDetectsNewlyManagedMatchingMember() throws Exception { group.setEntityFilter(EntityPredicates.displayNameEqualTo("myname")); - final Entity e3 = app.addChild(EntitySpec.create(TestEntity.class).displayName("myname")); - - Asserts.succeedsEventually(new Runnable() { + final Entity e = app.addChild(EntitySpec.create(TestEntity.class).displayName("myname")); + + succeedsEventually(new Runnable() { public void run() { - assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e3)); - EntityAsserts.assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 1); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e)); + assertAttributeEquals(e, SequenceGroup.SEQUENCE_VALUE, 1); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); }}); } @@ -101,18 +145,21 @@ public void run() { public void testGroupUsesNewFilter() throws Exception { group.setEntityFilter(EntityPredicates.hasInterfaceMatching(".*TestEntity")); - assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2)); - EntityAsserts.assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); - EntityAsserts.assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 2); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 3); + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); + assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); + assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 2); + assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 3); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 4); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e3); - final Entity e3 = app.addChild(EntitySpec.create(TestEntity.class)); + final Entity e = app.addChild(EntitySpec.create(TestEntity.class)); - Asserts.succeedsEventually(new Runnable() { + succeedsEventually(new Runnable() { public void run() { - assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); - EntityAsserts.assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 3); - EntityAsserts.assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 4); + assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3, e)); + assertAttributeEquals(e, SequenceGroup.SEQUENCE_VALUE, 4); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 5); + assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e); }}); } } diff --git a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java index 2b20f8d591..2ec8a79155 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java @@ -61,7 +61,6 @@ public void testSequenceInitial() throws Exception { @Test public void testSequenceInitialConfig() throws Exception { sequence = app.addChild(EntitySpec.create(SequenceEntity.class) - .configure(SequenceEntity.SEQUENCE_NAME, "forty-two") .configure(SequenceEntity.SEQUENCE_START, 42) .configure(SequenceEntity.SEQUENCE_FORMAT, "id-%02x")); app.start(ImmutableList.of(loc1)); @@ -70,7 +69,6 @@ public void testSequenceInitialConfig() throws Exception { assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 42); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "id-2a"); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_NAME, "forty-two"); } @Test From 77d0ddb7336e60dcf9ed9b1f3085c422c6811a3f Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 7 Feb 2017 09:44:37 +0000 Subject: [PATCH 4/7] Use configuration definitions from SequenceGroup and add local mutex --- .../brooklyn/entity/stock/SequenceEntity.java | 44 ++--------- .../entity/stock/SequenceEntityImpl.java | 74 +++++++++++-------- 2 files changed, 52 insertions(+), 66 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java index 5135f3cc3e..f53ba65dd4 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java @@ -23,15 +23,10 @@ import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.annotation.Effector; -import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey; -import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.entity.group.SequenceGroup; import org.apache.brooklyn.util.core.flags.SetFromFlag; -import org.apache.brooklyn.util.text.StringPredicates; - -import com.google.common.base.Predicates; /** * An entity that supplies a sequence of values through an effector. @@ -44,46 +39,23 @@ * sequence.start: 0 * sequence.increment: 1 * sequence.format: "global-%03d" - * sequence.name: "global" * } */ @ImplementedBy(SequenceEntityImpl.class) public interface SequenceEntity extends Entity, Startable { + AttributeSensor SEQUENCE_VALUE = SequenceGroup.SEQUENCE_VALUE; + + AttributeSensor SEQUENCE_STRING = SequenceGroup.SEQUENCE_STRING; + @SetFromFlag("sequenceStart") - ConfigKey SEQUENCE_START = ConfigKeys.builder(Integer.class) - .name("sequence.start") - .description("The starting point of the sequence") - .defaultValue(1) - .constraint(Predicates.notNull()) - .build(); + ConfigKey SEQUENCE_START = SequenceGroup.SEQUENCE_START; @SetFromFlag("sequenceIncrement") - ConfigKey SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) - .name("sequence.increment") - .description("The sequence increment for the next value") - .defaultValue(1) - .constraint(Predicates.notNull()) - .build(); + ConfigKey SEQUENCE_INCREMENT = SequenceGroup.SEQUENCE_INCREMENT; @SetFromFlag("sequenceFormat") - ConfigKey SEQUENCE_FORMAT = ConfigKeys.builder(String.class) - .name("sequence.format") - .description("A format used to generate a string representation of the sequence") - .defaultValue("%d") - .constraint(StringPredicates.containsRegex("%[-#+ 0,(]*[0-9]*[doxX]")) - .build(); - - @SetFromFlag("sequenceName") - AttributeSensorAndConfigKey SEQUENCE_NAME = ConfigKeys.newStringSensorAndConfigKey("sequence.name", "The name of the sequence", "sequence"); - - AttributeSensor SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") - .description("The current value of the sequence") - .build(); - - AttributeSensor SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") - .description("The current value of the sequence formatted as a string") - .build(); + ConfigKey SEQUENCE_FORMAT = SequenceGroup.SEQUENCE_FORMAT; MethodEffector RESET = new MethodEffector(SequenceEntity.class, "reset"); MethodEffector INCREMENT = new MethodEffector(SequenceEntity.class, "increment"); diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java index 338b323ae9..0ddb13428a 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java @@ -22,17 +22,16 @@ import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.core.entity.AbstractEntity; -import org.apache.brooklyn.core.feed.ConfigToAttributes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SequenceEntityImpl extends AbstractEntity implements SequenceEntity { - public SequenceEntityImpl() { } + private static final Logger LOG = LoggerFactory.getLogger(SequenceEntity.class); - public void init() { - super.init(); + private Object mutex = new Object(); - ConfigToAttributes.apply(this, SEQUENCE_NAME); - } + public SequenceEntityImpl() { } @Override public void start(Collection locations) { @@ -52,48 +51,63 @@ public void restart() { start(getLocations()); } - protected void sequence(Integer value) { - String format = config().get(SEQUENCE_FORMAT); - - sensors().set(SEQUENCE_VALUE, value); - sensors().set(SEQUENCE_STRING, String.format(format, value)); - } - @Override public Integer currentValue() { - return sensors().get(SEQUENCE_VALUE); + synchronized (mutex) { + return sensors().get(SEQUENCE_VALUE); + } } @Override public String currentString() { - return sensors().get(SEQUENCE_STRING); + synchronized (mutex) { + return sensors().get(SEQUENCE_STRING); + } } @Override - public synchronized Integer nextValue() { - increment(); - return currentValue(); + public Integer nextValue() { + synchronized (mutex) { + increment(); + return currentValue(); + } } @Override - public synchronized String nextString() { - increment(); - return currentString(); + public String nextString() { + synchronized (mutex) { + increment(); + return currentString(); + } } @Override - public synchronized Void increment() { - Integer increment = config().get(SEQUENCE_INCREMENT); - Integer current = currentValue(); - sequence(current + increment); - return null; + public Void increment() { + synchronized (mutex) { + Integer increment = config().get(SEQUENCE_INCREMENT); + Integer current = currentValue(); + sequence(current + increment); + return null; + } } @Override - public synchronized Void reset() { - Integer start = config().get(SEQUENCE_START); - sequence(start); - return null; + public Void reset() { + synchronized (mutex) { + Integer start = config().get(SEQUENCE_START); + sequence(start); + return null; + } + } + + private void sequence(Integer value) { + String format = config().get(SEQUENCE_FORMAT); + String string = String.format(format, value); + + sensors().set(SEQUENCE_VALUE, value); + sensors().set(SEQUENCE_STRING, string); + + LOG.debug("Sequence for {} set to {}", this, value); } } From 68a6f1465b01e6cb49163149af173d4680ac7a7f Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 7 Feb 2017 14:26:22 +0000 Subject: [PATCH 5/7] Simplify and tidy up tests for both entities --- .../brooklyn/entity/group/SequenceGroup.java | 7 +- .../entity/group/SequenceGroupImpl.java | 14 ++- .../brooklyn/entity/stock/SequenceEntity.java | 27 ++---- .../entity/stock/SequenceEntityImpl.java | 28 +----- .../entity/group/SequenceGroupTest.java | 96 ++++++++++++------- .../entity/stock/SequenceEntityTest.java | 55 ++++------- 6 files changed, 98 insertions(+), 129 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java index 851d3b786e..4d7d082fa2 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java @@ -55,7 +55,7 @@ * - $brooklyn:attributeWhenReady("application.id") * - $brooklyn:object: * type: org.apache.brooklyn.core.entity.EntityPredicates - * factoryMethod.name: "configEquelTo" + * factoryMethod.name: "configEqualTo" * factoryMethod.args: * - "sequence.set" * - true @@ -65,11 +65,6 @@ * sequenceValueSensor: $brooklyn:sensor("entity.sequence") * sequenceStringSensor: $brooklyn:sensor("entity.name") * } - *

- * IMPORTANT Relationship management will fail in unpredictable ways - * if an entity is a member of multiple sequences that use the same - * {@link #SEQUENCE_VALUE_SENSOR sensor}, however this usage will probably - * not have the desired effect in any case. */ @ImplementedBy(SequenceGroupImpl.class) public interface SequenceGroup extends DynamicGroup { diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java index fc9c526294..0e8c39cc76 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java @@ -33,6 +33,11 @@ public class SequenceGroupImpl extends DynamicGroupImpl implements SequenceGroup public SequenceGroupImpl() { } + public void init() { + super.init(); + reset(); + } + @Override public Void reset() { synchronized (memberChangeMutex) { @@ -44,14 +49,6 @@ public Void reset() { } } - @Override - public void rescanEntities() { - synchronized (memberChangeMutex) { - reset(); - super.rescanEntities(); - } - } - @Override public boolean addMember(Entity member) { synchronized (memberChangeMutex) { @@ -96,6 +93,7 @@ private Integer sequence(Entity entity) { entity.sensors().set(valueSensor, current); entity.sensors().set(stringSensor, string); + LOG.debug("Sequence on {} set to to {}", entity, current); sensors().set(SEQUENCE_CURRENT, entity); diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java index f53ba65dd4..6b73d8f048 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java @@ -28,6 +28,8 @@ import org.apache.brooklyn.entity.group.SequenceGroup; import org.apache.brooklyn.util.core.flags.SetFromFlag; +import com.google.common.base.Supplier; + /** * An entity that supplies a sequence of values through an effector. *

@@ -38,31 +40,23 @@ * brooklyn.config: * sequence.start: 0 * sequence.increment: 1 - * sequence.format: "global-%03d" * } */ @ImplementedBy(SequenceEntityImpl.class) -public interface SequenceEntity extends Entity, Startable { +public interface SequenceEntity extends Entity, Startable, Supplier { AttributeSensor SEQUENCE_VALUE = SequenceGroup.SEQUENCE_VALUE; - AttributeSensor SEQUENCE_STRING = SequenceGroup.SEQUENCE_STRING; - @SetFromFlag("sequenceStart") ConfigKey SEQUENCE_START = SequenceGroup.SEQUENCE_START; @SetFromFlag("sequenceIncrement") ConfigKey SEQUENCE_INCREMENT = SequenceGroup.SEQUENCE_INCREMENT; - @SetFromFlag("sequenceFormat") - ConfigKey SEQUENCE_FORMAT = SequenceGroup.SEQUENCE_FORMAT; - MethodEffector RESET = new MethodEffector(SequenceEntity.class, "reset"); MethodEffector INCREMENT = new MethodEffector(SequenceEntity.class, "increment"); - MethodEffector CURRENT_VALUE = new MethodEffector(SequenceEntity.class, "currentValue"); - MethodEffector CURRENT_STRING = new MethodEffector(SequenceEntity.class, "currentString"); - MethodEffector NEXT_VALUE = new MethodEffector(SequenceEntity.class, "nextValue"); - MethodEffector NEXT_STRING = new MethodEffector(SequenceEntity.class, "nextString"); + MethodEffector GET = new MethodEffector(SequenceEntity.class, "get"); + MethodEffector INCREMENT_AND_GET = new MethodEffector(SequenceEntity.class, "incrementAndGet"); @Effector(description = "Reset the sequence to initial value") Void reset(); @@ -71,14 +65,9 @@ public interface SequenceEntity extends Entity, Startable { Void increment(); @Effector(description = "Return the current numeric value of the sequence") - Integer currentValue(); - - @Effector(description = "Return the current string representation of the sequence") - String currentString(); + Integer get(); - @Effector(description = "Update and return the next numeric value of the sequence") - Integer nextValue(); + @Effector(description = "Update and return the next value of the sequence") + Integer incrementAndGet(); - @Effector(description = "Update and return the next string representation of the sequence") - String nextString(); } diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java index 0ddb13428a..b731dfe230 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java @@ -52,32 +52,17 @@ public void restart() { } @Override - public Integer currentValue() { + public Integer get() { synchronized (mutex) { return sensors().get(SEQUENCE_VALUE); } } @Override - public String currentString() { - synchronized (mutex) { - return sensors().get(SEQUENCE_STRING); - } - } - - @Override - public Integer nextValue() { + public Integer incrementAndGet() { synchronized (mutex) { increment(); - return currentValue(); - } - } - - @Override - public String nextString() { - synchronized (mutex) { - increment(); - return currentString(); + return get(); } } @@ -85,7 +70,7 @@ public String nextString() { public Void increment() { synchronized (mutex) { Integer increment = config().get(SEQUENCE_INCREMENT); - Integer current = currentValue(); + Integer current = get(); sequence(current + increment); return null; } @@ -101,12 +86,7 @@ public Void reset() { } private void sequence(Integer value) { - String format = config().get(SEQUENCE_FORMAT); - String string = String.format(format, value); - sensors().set(SEQUENCE_VALUE, value); - sensors().set(SEQUENCE_STRING, string); - LOG.debug("Sequence for {} set to {}", this, value); } diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java index 148eb5bede..d835c7dd1a 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java @@ -18,22 +18,22 @@ */ package org.apache.brooklyn.entity.group; +import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttribute; +import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEquals; +import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEqualsEventually; import static org.apache.brooklyn.test.Asserts.assertEqualsIgnoringOrder; -import static org.apache.brooklyn.test.Asserts.*; -import static org.apache.brooklyn.core.entity.EntityAsserts.*; - -import java.util.Set; +import static org.apache.brooklyn.test.Asserts.assertTrue; +import static org.apache.brooklyn.test.Asserts.succeedsEventually; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.sensor.AttributeSensor; -import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.location.SimulatedLocation; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; -import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -43,7 +43,7 @@ public class SequenceGroupTest extends BrooklynAppUnitTestSupport { - private TestApplication app; + private SimulatedLocation loc1; private SequenceGroup group; private TestEntity e1, e2, e3; @@ -51,33 +51,35 @@ public class SequenceGroupTest extends BrooklynAppUnitTestSupport { @Override public void setUp() throws Exception { super.setUp(); - - app = TestApplication.Factory.newManagedInstanceForTests(); - group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class) - .configure(SequenceGroup.SEQUENCE_STRING_SENSOR, Sensors.newStringSensor("test.sequence")) - .configure(SequenceGroup.SEQUENCE_FORMAT, "test-%02d")); - e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); - e2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); - e3 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + loc1 = mgmt.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)); } - @AfterMethod(alwaysRun=true) - @Override - public void tearDown() throws Exception { - if (app != null) Entities.destroyAll(app.getManagementContext()); - - super.tearDown(); + protected void createTestEntities() throws Exception { + e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("test-one")); + e2 = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("test-two")); + e3 = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("test-three")); } - @Test public void testGroupDefaults() throws Exception { + group = app.addChild(EntitySpec.create(SequenceGroup.class)); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); + + assertAttribute(group, SequenceGroup.SEQUENCE_VALUE, Predicates.isNull()); assertTrue(group.getMembers().isEmpty()); } @Test public void testGroupWithMatchingFilterReturnsOnlyMatchingMembers() throws Exception { - group.setEntityFilter(EntityPredicates.idEqualTo(e1.getId())); + group = app.addChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.ENTITY_FILTER, EntityPredicates.displayNameEqualTo("test-one"))); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); @@ -86,9 +88,17 @@ public void testGroupWithMatchingFilterReturnsOnlyMatchingMembers() throws Excep @Test public void testGroupConfiguration() throws Exception { - group.setEntityFilter(EntityPredicates.idEqualTo(e1.getId())); + group = app.addChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.SEQUENCE_STRING_SENSOR, Sensors.newStringSensor("test.sequence")) + .configure(SequenceGroup.SEQUENCE_FORMAT, "test-%02d") + .configure(SequenceGroup.ENTITY_FILTER, EntityPredicates.displayNameEqualTo("test-one"))); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); + assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); assertAttributeEquals(e1, SequenceGroup.SEQUENCE_STRING, null); assertAttributeEquals(e1, Sensors.newStringSensor("test.sequence"), "test-01"); } @@ -97,13 +107,17 @@ public void testGroupConfiguration() throws Exception { public void testAlternateGroupConfiguration() throws Exception { AttributeSensor value = Sensors.newIntegerSensor("test.value"); AttributeSensor string = Sensors.newStringSensor("test.string"); - group = app.createAndManageChild(EntitySpec.create(SequenceGroup.class) + group = app.addChild(EntitySpec.create(SequenceGroup.class) .configure(SequenceGroup.SEQUENCE_START, 12345) .configure(SequenceGroup.SEQUENCE_INCREMENT, 678) .configure(SequenceGroup.SEQUENCE_VALUE_SENSOR, value) .configure(SequenceGroup.SEQUENCE_STRING_SENSOR, string) - .configure(SequenceGroup.SEQUENCE_FORMAT, "0x%04X")); - group.setEntityFilter(EntityPredicates.hasInterfaceMatching(".*TestEntity")); + .configure(SequenceGroup.SEQUENCE_FORMAT, "0x%04X") + .configure(SequenceGroup.ENTITY_FILTER, EntityPredicates.hasInterfaceMatching(".*TestEntity"))); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); assertAttributeEquals(e1, value, 12345); @@ -116,7 +130,12 @@ public void testAlternateGroupConfiguration() throws Exception { @Test public void testGroupWithMatchingFilterReturnsEverythingThatMatches() throws Exception { - group.setEntityFilter(Predicates.alwaysTrue()); + group = app.addChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.ENTITY_FILTER, Predicates.alwaysTrue())); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3, app, group)); assertAttributeEquals(app, SequenceGroup.SEQUENCE_VALUE, 1); @@ -130,8 +149,14 @@ public void testGroupWithMatchingFilterReturnsEverythingThatMatches() throws Exc @Test public void testGroupDetectsNewlyManagedMatchingMember() throws Exception { - group.setEntityFilter(EntityPredicates.displayNameEqualTo("myname")); - final Entity e = app.addChild(EntitySpec.create(TestEntity.class).displayName("myname")); + group = app.addChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.ENTITY_FILTER, EntityPredicates.displayNameEqualTo("test-four"))); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); + + final Entity e = app.addChild(EntitySpec.create(TestEntity.class).displayName("test-four")); succeedsEventually(new Runnable() { public void run() { @@ -142,8 +167,13 @@ public void run() { } @Test - public void testGroupUsesNewFilter() throws Exception { - group.setEntityFilter(EntityPredicates.hasInterfaceMatching(".*TestEntity")); + public void testGroupAddsNewMatchingMember() throws Exception { + group = app.addChild(EntitySpec.create(SequenceGroup.class) + .configure(SequenceGroup.ENTITY_FILTER, EntityPredicates.hasInterfaceMatching(".*TestEntity"))); + createTestEntities(); + app.start(ImmutableList.of(loc1)); + + assertAttributeEqualsEventually(group, SequenceGroup.RUNNING, true); assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3)); assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); diff --git a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java index 2ec8a79155..c4b3b863ca 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java @@ -55,20 +55,17 @@ public void testSequenceInitial() throws Exception { assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 0); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "0"); } @Test public void testSequenceInitialConfig() throws Exception { sequence = app.addChild(EntitySpec.create(SequenceEntity.class) - .configure(SequenceEntity.SEQUENCE_START, 42) - .configure(SequenceEntity.SEQUENCE_FORMAT, "id-%02x")); + .configure(SequenceEntity.SEQUENCE_START, 42)); app.start(ImmutableList.of(loc1)); assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 42); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "id-2a"); } @Test @@ -78,54 +75,41 @@ public void testSequenceIncrementEffector() throws Exception { EntityAsserts.assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); - assertEquals(sequence.currentValue(), Integer.valueOf(1)); - assertEquals(sequence.currentString(), "1"); + assertEquals(sequence.get(), Integer.valueOf(1)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 1); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "1"); sequence.increment(); - assertEquals(sequence.currentValue(), Integer.valueOf(2)); - assertEquals(sequence.currentString(), "2"); + assertEquals(sequence.get(), Integer.valueOf(2)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 2); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "2"); sequence.invoke(SequenceEntity.INCREMENT, ImmutableMap.of()).getUnchecked(); - assertEquals(sequence.currentValue(), Integer.valueOf(3)); - assertEquals(sequence.currentString(), "3"); + assertEquals(sequence.get(), Integer.valueOf(3)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 3); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "3"); } @Test public void testSequenceIncrementEffectorConfig() throws Exception { sequence = app.addChild(EntitySpec.create(SequenceEntity.class) .configure(SequenceEntity.SEQUENCE_START, 0) - .configure(SequenceEntity.SEQUENCE_INCREMENT, 2) - .configure(SequenceEntity.SEQUENCE_FORMAT, "%03d")); + .configure(SequenceEntity.SEQUENCE_INCREMENT, 2)); app.start(ImmutableList.of(loc1)); EntityAsserts.assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); - assertEquals(sequence.currentValue(), Integer.valueOf(0)); - assertEquals(sequence.currentString(), "000"); + assertEquals(sequence.get(), Integer.valueOf(0)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 0); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "000"); sequence.increment(); - assertEquals(sequence.currentValue(), Integer.valueOf(2)); - assertEquals(sequence.currentString(), "002"); + assertEquals(sequence.get(), Integer.valueOf(2)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 2); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "002"); sequence.invoke(SequenceEntity.INCREMENT, ImmutableMap.of()).getUnchecked(); - assertEquals(sequence.currentValue(), Integer.valueOf(4)); - assertEquals(sequence.currentString(), "004"); + assertEquals(sequence.get(), Integer.valueOf(4)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 4); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "004"); } @Test @@ -135,19 +119,16 @@ public void testSequenceNextEffectors() throws Exception { assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); - assertEquals(sequence.currentValue(), Integer.valueOf(1)); - assertEquals(sequence.currentString(), "1"); + assertEquals(sequence.get(), Integer.valueOf(1)); - Integer nextValue = sequence.invoke(SequenceEntity.NEXT_VALUE, ImmutableMap.of()).getUnchecked(); + Integer nextValue = sequence.invoke(SequenceEntity.INCREMENT_AND_GET, ImmutableMap.of()).getUnchecked(); assertEquals(nextValue, Integer.valueOf(2)); - String nextString = sequence.invoke(SequenceEntity.NEXT_STRING, ImmutableMap.of()).getUnchecked(); - assertEquals(nextString, "3"); + nextValue = sequence.invoke(SequenceEntity.INCREMENT_AND_GET, ImmutableMap.of()).getUnchecked(); + assertEquals(nextValue, Integer.valueOf(3)); - assertEquals(sequence.currentValue(), Integer.valueOf(3)); - assertEquals(sequence.currentString(), "3"); + assertEquals(sequence.get(), Integer.valueOf(3)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 3); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "3"); } @Test @@ -157,23 +138,19 @@ public void testSequenceReset() throws Exception { assertAttributeEqualsEventually(sequence, Startable.SERVICE_UP, true); - assertEquals(sequence.currentValue(), Integer.valueOf(1)); + assertEquals(sequence.get(), Integer.valueOf(1)); sequence.increment(); sequence.increment(); sequence.increment(); - assertEquals(sequence.currentValue(), Integer.valueOf(4)); - assertEquals(sequence.currentString(), "4"); + assertEquals(sequence.get(), Integer.valueOf(4)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 4); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "4"); sequence.invoke(SequenceEntity.RESET, ImmutableMap.of()).getUnchecked(); - assertEquals(sequence.currentValue(), Integer.valueOf(1)); - assertEquals(sequence.currentString(), "1"); + assertEquals(sequence.get(), Integer.valueOf(1)); assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_VALUE, 1); - assertAttributeEquals(sequence, SequenceEntity.SEQUENCE_STRING, "1"); } } From ff6e7a376a135ffd2e8e672d25bacefdb662b594 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 8 Feb 2017 19:47:11 +0000 Subject: [PATCH 6/7] Use AtomicIntegers for internal sequencer state --- .../brooklyn/entity/group/SequenceGroup.java | 5 +++-- .../entity/group/SequenceGroupImpl.java | 16 +++++++-------- .../brooklyn/entity/stock/SequenceEntity.java | 4 ++++ .../entity/stock/SequenceEntityImpl.java | 20 +++++++++++-------- .../entity/group/SequenceGroupTest.java | 19 ++++++++++++------ .../entity/stock/SequenceEntityTest.java | 2 +- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java index 4d7d082fa2..170e90d70f 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.entity.group; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.ImplementedBy; @@ -123,8 +124,8 @@ public interface SequenceGroup extends DynamicGroup { .description("The current entity in the sequence") .build(); - AttributeSensor SEQUENCE_NEXT = Sensors.builder(Integer.class, "sequence.next") - .description("The next value of the sequence") + AttributeSensor SEQUENCE_STATE = Sensors.builder(AtomicInteger.class, "sequence.state") + .description("The current state of the sequence") .build(); AttributeSensor> SEQUENCE_CACHE = Sensors.builder(new TypeToken>() { }, "sequence.cache") diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java index 0e8c39cc76..6aca7afe80 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.entity.group; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.sensor.AttributeSensor; @@ -44,7 +45,8 @@ public Void reset() { sensors().set(SEQUENCE_CACHE, Maps.newConcurrentMap()); sensors().set(SEQUENCE_CURRENT, null); Integer initial = config().get(SEQUENCE_START); - sensors().set(SEQUENCE_NEXT, initial); + AtomicInteger state = new AtomicInteger(initial); + sensors().set(SEQUENCE_STATE, state); return null; } } @@ -86,7 +88,10 @@ public boolean removeMember(Entity member) { private Integer sequence(Entity entity) { String format = config().get(SEQUENCE_FORMAT); - Integer current = sensors().get(SEQUENCE_NEXT); + Integer increment = config().get(SEQUENCE_INCREMENT); + AtomicInteger state = sensors().get(SEQUENCE_STATE); + Integer current = state.getAndAdd(increment); + String string = String.format(format, current); AttributeSensor valueSensor = config().get(SEQUENCE_VALUE_SENSOR); AttributeSensor stringSensor = config().get(SEQUENCE_STRING_SENSOR); @@ -96,12 +101,7 @@ private Integer sequence(Entity entity) { LOG.debug("Sequence on {} set to to {}", entity, current); sensors().set(SEQUENCE_CURRENT, entity); - - Integer increment = config().get(SEQUENCE_INCREMENT); - Integer next = current + increment; - LOG.debug("Sequence for {} incremented to {}", this, next); - - sensors().set(SEQUENCE_NEXT, next); + LOG.debug("Sequence for {} incremented to {}", this, state.get()); return current; } diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java index 6b73d8f048..bc58f47190 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntity.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.entity.stock; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.ImplementedBy; import org.apache.brooklyn.api.sensor.AttributeSensor; @@ -47,6 +49,8 @@ public interface SequenceEntity extends Entity, Startable, Supplier { AttributeSensor SEQUENCE_VALUE = SequenceGroup.SEQUENCE_VALUE; + AttributeSensor SEQUENCE_STATE = SequenceGroup.SEQUENCE_STATE; + @SetFromFlag("sequenceStart") ConfigKey SEQUENCE_START = SequenceGroup.SEQUENCE_START; diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java index b731dfe230..06fee53610 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/stock/SequenceEntityImpl.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.entity.stock; import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.core.entity.AbstractEntity; @@ -61,7 +62,7 @@ public Integer get() { @Override public Integer incrementAndGet() { synchronized (mutex) { - increment(); + sequence(); return get(); } } @@ -69,9 +70,7 @@ public Integer incrementAndGet() { @Override public Void increment() { synchronized (mutex) { - Integer increment = config().get(SEQUENCE_INCREMENT); - Integer current = get(); - sequence(current + increment); + sequence(); return null; } } @@ -80,14 +79,19 @@ public Void increment() { public Void reset() { synchronized (mutex) { Integer start = config().get(SEQUENCE_START); - sequence(start); + AtomicInteger state = new AtomicInteger(start); + sensors().set(SEQUENCE_STATE, state); + sensors().set(SEQUENCE_VALUE, state.get()); return null; } } - private void sequence(Integer value) { - sensors().set(SEQUENCE_VALUE, value); - LOG.debug("Sequence for {} set to {}", this, value); + private void sequence() { + Integer increment = config().get(SEQUENCE_INCREMENT); + AtomicInteger state = sensors().get(SEQUENCE_STATE); + Integer current = state.addAndGet(increment); + sensors().set(SEQUENCE_VALUE, current); + LOG.debug("Sequence for {} set to {}", this, current); } } diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java index d835c7dd1a..f1d85c5c06 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java @@ -22,9 +22,11 @@ import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEquals; import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEqualsEventually; import static org.apache.brooklyn.test.Asserts.assertEqualsIgnoringOrder; -import static org.apache.brooklyn.test.Asserts.assertTrue; +import static org.apache.brooklyn.test.Asserts.*; import static org.apache.brooklyn.test.Asserts.succeedsEventually; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.LocationSpec; @@ -83,7 +85,8 @@ public void testGroupWithMatchingFilterReturnsOnlyMatchingMembers() throws Excep assertEqualsIgnoringOrder(group.getMembers(), ImmutableList.of(e1)); assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); - assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + AtomicInteger state = group.sensors().get(SequenceGroup.SEQUENCE_STATE); + assertEquals(state.get(), 2); } @Test @@ -143,8 +146,9 @@ public void testGroupWithMatchingFilterReturnsEverythingThatMatches() throws Exc assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 3); assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 4); assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 5); - assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 6); assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e3); + AtomicInteger state = group.sensors().get(SequenceGroup.SEQUENCE_STATE); + assertEquals(state.get(), 6); } @Test @@ -162,7 +166,8 @@ public void testGroupDetectsNewlyManagedMatchingMember() throws Exception { public void run() { assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e)); assertAttributeEquals(e, SequenceGroup.SEQUENCE_VALUE, 1); - assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 2); + AtomicInteger state = group.sensors().get(SequenceGroup.SEQUENCE_STATE); + assertEquals(state.get(), 2); }}); } @@ -179,8 +184,9 @@ public void testGroupAddsNewMatchingMember() throws Exception { assertAttributeEquals(e1, SequenceGroup.SEQUENCE_VALUE, 1); assertAttributeEquals(e2, SequenceGroup.SEQUENCE_VALUE, 2); assertAttributeEquals(e3, SequenceGroup.SEQUENCE_VALUE, 3); - assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 4); assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e3); + AtomicInteger state = group.sensors().get(SequenceGroup.SEQUENCE_STATE); + assertEquals(state.get(), 4); final Entity e = app.addChild(EntitySpec.create(TestEntity.class)); @@ -188,8 +194,9 @@ public void testGroupAddsNewMatchingMember() throws Exception { public void run() { assertEqualsIgnoringOrder(group.getMembers(), ImmutableSet.of(e1, e2, e3, e)); assertAttributeEquals(e, SequenceGroup.SEQUENCE_VALUE, 4); - assertAttributeEquals(group, SequenceGroup.SEQUENCE_NEXT, 5); assertAttributeEquals(group, SequenceGroup.SEQUENCE_CURRENT, e); + AtomicInteger state = group.sensors().get(SequenceGroup.SEQUENCE_STATE); + assertEquals(state.get(), 5); }}); } } diff --git a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java index c4b3b863ca..0acafaf832 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/stock/SequenceEntityTest.java @@ -113,7 +113,7 @@ public void testSequenceIncrementEffectorConfig() throws Exception { } @Test - public void testSequenceNextEffectors() throws Exception { + public void testSequenceIncrementAndGetEffector() throws Exception { sequence = app.addChild(EntitySpec.create(SequenceEntity.class)); app.start(ImmutableList.of(loc1)); From 62989ac9d615d89d0e95e44fe6f772b116a9aaf8 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 28 Feb 2017 14:20:44 +0000 Subject: [PATCH 7/7] Formatting --- .../entity/lifecycle/ServiceStateLogic.java | 150 +++++++++++------- 1 file changed, 95 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java index 21d8b5884b..386eb56be0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java +++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java @@ -50,6 +50,7 @@ import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.EntityPredicates; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle.Transition; +import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.enricher.stock.AbstractMultipleSensorAggregator; import org.apache.brooklyn.enricher.stock.Enrichers; import org.apache.brooklyn.enricher.stock.UpdatingMap; @@ -58,6 +59,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.collections.QuorumCheck; +import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks; import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.guava.Functionals; import org.apache.brooklyn.util.guava.Maybe; @@ -77,15 +79,15 @@ import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; -/** Logic, sensors and enrichers, and conveniences, for computing service status */ +/** 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; public static final AttributeSensor> SERVICE_NOT_UP_DIAGNOSTICS = Attributes.SERVICE_NOT_UP_DIAGNOSTICS; - + 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; @@ -98,7 +100,7 @@ public static TVal getMapSensorEntry(Entity entity, AttributeSensor< if (map==null) return null; return map.get(key); } - + @SuppressWarnings("unchecked") public static void clearMapSensorEntry(Entity entity, AttributeSensor> sensor, TKey key) { updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE); @@ -109,9 +111,9 @@ public static void updateMapSensorEntry(Entity entity, AttributeSens /* * Important to *not* modify the existing attribute value; must make a copy, modify that, and publish. * This is because a Propagator enricher will set this same value on another entity. There was very - * strange behaviour when this was done for a SERVICE_UP_INDICATORS sensor - the updates done here + * strange behaviour when this was done for a SERVICE_UP_INDICATORS sensor - the updates done here * applied to the attribute of both entities! - * + * * Need to do this update atomically (i.e. sequentially) because there is no threading control for * what is calling updateMapSensorEntity. It is called directly on start, on initialising enrichers, * and in event listeners. These calls could be concurrent. @@ -120,7 +122,7 @@ public static void updateMapSensorEntry(Entity entity, AttributeSens @Override public Maybe> apply(Map map) { boolean created = (map==null); if (created) map = MutableMap.of(); - + boolean changed; if (v == Entities.REMOVE) { changed = map.containsKey(key); @@ -147,22 +149,22 @@ public static void updateMapSensorEntry(Entity entity, AttributeSens } } }; - - if (!Entities.isNoLongerManaged(entity)) { + + if (!Entities.isNoLongerManaged(entity)) { entity.sensors().modify(sensor, modifier); } } - + public static void setExpectedState(Entity entity, Lifecycle state) { waitBrieflyForServiceUpIfStateIsRunning(entity, state); ((EntityInternal)entity).sensors().set(Attributes.SERVICE_STATE_EXPECTED, new Lifecycle.Transition(state, new Date())); - + Maybe enricher = EntityAdjuncts.tryFindWithUniqueTag(entity.enrichers(), ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG); if (enricher.isPresent() && enricher.get() instanceof ComputeServiceState) { ((ComputeServiceState)enricher.get()).onEvent(null); } } - + public static Lifecycle getExpectedState(Entity entity) { Transition expected = entity.getAttribute(Attributes.SERVICE_STATE_EXPECTED); if (expected==null) return null; @@ -197,13 +199,13 @@ public static Lifecycle getActualState(Entity entity) { public static boolean isExpectedState(Entity entity, Lifecycle state) { return getExpectedState(entity)==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() {} - + public static final EnricherSpec newEnricherForServiceUpIfNotUpIndicatorsEmpty() { // The function means: if the serviceNotUpIndicators is not null, then serviceNotUpIndicators.size()==0; // otherwise return the default value. @@ -217,8 +219,8 @@ public static final EnricherSpec newEnricherForServiceUpIfNotUpIndicatorsEmpt .uniqueTag(DEFAULT_ENRICHER_UNIQUE_TAG) .build(); } - - /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the + + /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the * {@link UpdatingMap} enricher for the given key */ public static void updateNotUpIndicator(Entity entity, String key, Object value) { updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, key, value); @@ -246,17 +248,17 @@ public static void updateNotUpIndicatorRequiringNonEmptyMap(Entity entity, Attri 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 + + /** 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 + * was to {@link Lifecycle#RUNNING} and * {@link #computeActualStateWhenNotExpectedRunning(Map, Boolean, org.apache.brooklyn.core.entity.lifecycle.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 + * 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 { @@ -267,13 +269,13 @@ public static class ComputeServiceState extends AbstractEnricher implements Sens public ComputeServiceState() {} public ComputeServiceState(Map flags) { super(flags); } - + @Override public void init() { super.init(); if (uniqueTag==null) uniqueTag = DEFAULT_ENRICHER_UNIQUE_TAG; } - + @Override public void setEntity(EntityLocal entity) { super.setEntity(entity); @@ -281,7 +283,7 @@ public void setEntity(EntityLocal entity) { // only publish on changes, unless it is configured otherwise suppressDuplicates = true; } - + Map notifyOfInitialValue = ImmutableMap.of("notifyOfInitialValue", Boolean.TRUE); subscriptions().subscribe(notifyOfInitialValue, entity, SERVICE_PROBLEMS, this); subscriptions().subscribe(notifyOfInitialValue, entity, SERVICE_UP, this); @@ -291,11 +293,11 @@ public void setEntity(EntityLocal entity) { @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 { @@ -315,12 +317,12 @@ protected Maybe computeActualStateWhenExpectedRunning(Map 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 Maybe.of(stateTransition.getState()); - + } else if (problems!=null && !problems.isEmpty()) { // 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)) { @@ -335,7 +337,7 @@ protected Maybe computeActualStateWhenNotExpectedRunning(Map state) { } } - + public static final EnricherSpec newEnricherForServiceStateFromProblemsAndUp() { return newEnricherForServiceState(ComputeServiceState.class); } public static final EnricherSpec newEnricherForServiceState(Class type) { + newEnricherForServiceUpFromChildren(); return EnricherSpec.create(type); } - + + /** + * An enricher that sets {@link Startable#SERVICE_UP servive.isUp} on an entity only when all children are + * also reporting as healthy. + *

+ * Equivalent to the following YAML configuration. + *

+     * brooklyn.enrichers:
+     *   - type: org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic$ComputeServiceIndicatorsFromChildrenAndMembers
+     *     brooklyn.config:
+     *       enricher.aggregating.fromChildren: true
+     *       enricher.aggregating.fromMembers: false
+     *       enricher.suppressDuplicates: true
+     *       enricher.service_state.children_and_members.quorum.up: all
+     *       enricher.service_state.children_and_members.ignore_entities.service_state_values: [ "STOPPING", "STOPPED", "DESTROYED" ]
+     * 
+ */ + public static final EnricherSpec newEnricherForServiceUpFromChildren() { + return newEnricherForServiceUp(Boolean.TRUE, Boolean.FALSE); + } + public static final EnricherSpec newEnricherForServiceUpFromMembers() { + return newEnricherForServiceUp(Boolean.FALSE, Boolean.TRUE); + } + public static final EnricherSpec newEnricherForServiceUpFromChildrenWithQuorumCheck(QuorumCheck quorumCheck) { + EnricherSpec serviceUp = newEnricherForServiceUpFromChildren() + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, quorumCheck); + return serviceUp; + } + public static final EnricherSpec newEnricherForServiceUp(Boolean fromChildren, Boolean fromMembers) { + EnricherSpec serviceUp = EnricherSpec.create(ComputeServiceIndicatorsFromChildrenAndMembers.class) + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, fromChildren) + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, fromMembers) + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.SUPPRESS_DUPLICATES, Boolean.TRUE) + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumChecks.all()) + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES, ImmutableSet.of(Lifecycle.STOPPING, Lifecycle.STOPPED, Lifecycle.DESTROYED)); + return serviceUp; + } + 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 + + /** 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(Entity entity, Sensor sensor, Object value) { updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, sensor.getName(), value); @@ -393,11 +433,11 @@ public static void clearProblemsIndicator(Entity entity, String key) { clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, 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 DEFAULT_UNIQUE_TAG = "service-lifecycle-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"; @@ -406,7 +446,7 @@ public static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr .defaultValue(QuorumCheck.QuorumChecks.atLeastOneUnlessEmpty()) .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED) .build(); - public static final ConfigKey RUNNING_QUORUM_CHECK = ConfigKeys.builder(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.running") + public static final ConfigKey RUNNING_QUORUM_CHECK = ConfigKeys.builder(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.running") .description("Logic for checking whether this service is healthy, based on children and/or members running, defaulting to requiring none to be ON-FIRE") .defaultValue(QuorumCheck.QuorumChecks.all()) .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED) @@ -417,8 +457,8 @@ public static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr 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)", + "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() { @@ -455,9 +495,9 @@ 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, + 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)) { @@ -466,14 +506,14 @@ protected void doReconfigureConfig(ConfigKey key, T val) { 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() { @@ -511,7 +551,7 @@ protected Object computeServiceNotUp() { continue; entries++; Lifecycle entityState = state.getKey().getAttribute(SERVICE_STATE_ACTUAL); - + if (Boolean.TRUE.equals(state.getValue())) numUp++; else if (!ignoreStates.contains(entityState)) { violators.add(state.getKey()); @@ -544,7 +584,7 @@ protected Object computeServiceProblems() { Set ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES); for (Map.Entry state: values.entrySet()) { if (state.getValue()==Lifecycle.RUNNING) numRunning++; - else if (!ignoreStates.contains(state.getValue())) + else if (!ignoreStates.contains(state.getValue())) onesNotHealthy.add(state.getKey()); } @@ -582,14 +622,14 @@ 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); } @@ -626,14 +666,14 @@ public ComputeServiceIndicatorsFromChildrenAndMembersSpec requireRunningChildren configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, check); return self(); } - + public ComputeServiceIndicatorsFromChildrenAndMembersSpec entityFilter(Predicate val) { configure(ComputeServiceIndicatorsFromChildrenAndMembers.ENTITY_FILTER, val); return self(); } } - /** provides the default {@link ComputeServiceIndicatorsFromChildrenAndMembers} enricher, + /** provides the default {@link ComputeServiceIndicatorsFromChildrenAndMembers} enricher, * 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 */ @@ -642,10 +682,10 @@ public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFrom .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG); } - /** as {@link #newEnricherFromChildren()} but only publishing service not-up indicators, + /** as {@link #newEnricherFromChildren()} but only publishing service not-up indicators, * using a different unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#DEFAULT_UNIQUE_TAG_UP}), * listening to children only, ignoring lifecycle/service-state, - * and using the same logic + * 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() @@ -653,14 +693,14 @@ public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFrom .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 + * 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); } - + }