A group that sets a sequence of values as sensors on members
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/2728745b Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/2728745b Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/2728745b Branch: refs/heads/master Commit: 2728745be0f1222d84b0230bb5c5576b54765bdb Parents: f2bd653 Author: Andrew Donald Kennedy <[email protected]> Authored: Tue Jan 31 10:27:27 2017 +0000 Committer: Andrew Donald Kennedy <[email protected]> Committed: Tue Apr 11 14:34:26 2017 +0100 ---------------------------------------------------------------------- .../brooklyn/entity/group/SequenceGroup.java | 104 ++++++++++++++++ .../entity/group/SequenceGroupImpl.java | 85 +++++++++++++ .../entity/group/SequenceGroupTest.java | 118 +++++++++++++++++++ 3 files changed, 307 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2728745b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroup.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 0000000..a6dee56 --- /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 + * <p> + * Usage: + * <pre>{@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" + * }</pre> + */ +@ImplementedBy(SequenceGroupImpl.class) +public interface SequenceGroup extends DynamicGroup { + + @SetFromFlag("sequenceStart") + ConfigKey<Integer> SEQUENCE_START = ConfigKeys.builder(Integer.class) + .name("sequence.start") + .description("The starting point of the sequence") + .defaultValue(1) + .constraint(Predicates.<Integer>notNull()) + .build(); + + @SetFromFlag("sequenceIncrement") + ConfigKey<Integer> SEQUENCE_INCREMENT = ConfigKeys.builder(Integer.class) + .name("sequence.increment") + .description("The sequence increment for the next value") + .defaultValue(1) + .constraint(Predicates.<Integer>notNull()) + .build(); + + @SetFromFlag("sequenceFormat") + ConfigKey<String> 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<Integer> SEQUENCE_NEXT = Sensors.builder(Integer.class, "sequence.next") + .description("The next value of the sequence") + .build(); + + AttributeSensor<Map<String, Integer>> SEQUENCE_CACHE = Sensors.builder(new TypeToken<Map<String, Integer>>() { }, "sequence.cache") + .description("The current cache of entity ids to sequence numbers") + .build(); + + MethodEffector<Void> RESET = new MethodEffector<Void>(SequenceEntity.class, "reset"); + + @Effector(description = "Reset the sequence to initial value") + Void reset(); + + AttributeSensor<Integer> SEQUENCE_VALUE = Sensors.builder(Integer.class, "sequence.value") + .description("The current value of the sequence") + .build(); + + AttributeSensor<String> SEQUENCE_STRING = Sensors.builder(String.class, "sequence.string") + .description("The current value of the sequence formatted as a string") + .build(); + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2728745b/core/src/main/java/org/apache/brooklyn/entity/group/SequenceGroupImpl.java ---------------------------------------------------------------------- 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 0000000..f9fc04e --- /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.<String, Integer>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<String, Integer> 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; + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2728745b/core/src/test/java/org/apache/brooklyn/entity/group/SequenceGroupTest.java ---------------------------------------------------------------------- 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 0000000..afb5425 --- /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); + }}); + } +}
