Adds configurable removal strategies
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/0ebd0669 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/0ebd0669 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/0ebd0669 Branch: refs/heads/master Commit: 0ebd0669ed9e80dc7968edff269ed2100d0e7e64 Parents: a5d65fd Author: Martin Harris <[email protected]> Authored: Tue Apr 12 13:23:36 2016 +0100 Committer: Martin Harris <[email protected]> Committed: Mon May 9 10:57:15 2016 +0100 ---------------------------------------------------------------------- .../entity/group/DynamicClusterImpl.java | 18 ++++--- .../entity/group/FirstFromRemovalStrategy.java | 53 ++++++++++++++++++++ .../brooklyn/entity/group/RemovalStrategy.java | 30 +++++++++++ .../group/SensorMatchingRemovalStrategy.java | 50 ++++++++++++++++++ .../entity/group/DynamicClusterTest.java | 51 +++++++++++++++++++ 5 files changed, 194 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0ebd0669/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java index db263d7..3577004 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java @@ -151,12 +151,14 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus */ protected final Object mutex = new Object[0]; - private static final Function<Collection<Entity>, Entity> defaultRemovalStrategy = new Function<Collection<Entity>, Entity>() { - @Override public Entity apply(Collection<Entity> contenders) { + public static class DefaultRemovalStrategy extends RemovalStrategy { + @Nullable + @Override + public Entity apply(@Nullable Collection<Entity> contenders) { /* * Choose the newest entity (largest cluster member ID or latest timestamp) that is stoppable. * If none are stoppable, take the newest non-stoppable. - * + * * Both cluster member ID and timestamp must be taken into consideration to account for legacy * clusters that were created before the addition of the cluster member ID config value. */ @@ -171,8 +173,8 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus boolean newer = (contenderClusterMemberId != null && contenderClusterMemberId > largestClusterMemberId) || contenderCreationTime > newestTime; - if ((contender instanceof Startable && newer) || - (!(newest instanceof Startable) && ((contender instanceof Startable) || newer))) { + if ((contender instanceof Startable && newer) || + (!(newest instanceof Startable) && ((contender instanceof Startable) || newer))) { newest = contender; if (contenderClusterMemberId != null) largestClusterMemberId = contenderClusterMemberId; @@ -182,7 +184,7 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus return newest; } - }; + } private static class NextClusterMemberIdSupplier implements Supplier<Integer> { private AtomicInteger nextId = new AtomicInteger(0); @@ -259,7 +261,7 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus // override previous enricher so that only members are checked ServiceStateLogic.newEnricherFromChildrenUp().checkMembersOnly().requireUpChildren(getConfig(UP_QUORUM_CHECK)).addTo(this); } - + @Override public void setRemovalStrategy(Function<Collection<Entity>, Entity> val) { config().set(REMOVAL_STRATEGY, checkNotNull(val, "removalStrategy")); @@ -267,7 +269,7 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus protected Function<Collection<Entity>, Entity> getRemovalStrategy() { Function<Collection<Entity>, Entity> result = getConfig(REMOVAL_STRATEGY); - return (result != null) ? result : defaultRemovalStrategy; + return (result != null) ? result : new DefaultRemovalStrategy(); } @Override http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0ebd0669/core/src/main/java/org/apache/brooklyn/entity/group/FirstFromRemovalStrategy.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/FirstFromRemovalStrategy.java b/core/src/main/java/org/apache/brooklyn/entity/group/FirstFromRemovalStrategy.java new file mode 100644 index 0000000..9af48f0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/FirstFromRemovalStrategy.java @@ -0,0 +1,53 @@ +/* + * 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.Collection; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; + +import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; + +public class FirstFromRemovalStrategy extends RemovalStrategy { + + public static final ConfigKey<List<RemovalStrategy>> STRATEGIES = ConfigKeys.newConfigKey(new TypeToken<List<RemovalStrategy>>() {}, "firstfrom.strategies", + "An ordered list of removal strategies to be used to determine which entity to remove"); + + @Nullable + @Override + public Entity apply(@Nullable Collection<Entity> input) { + List<RemovalStrategy> strategies = config().get(STRATEGIES); + if (strategies == null || Iterables.isEmpty(strategies)) { + return null; + } + for (RemovalStrategy strategy : strategies) { + Entity entity = strategy.apply(input); + if (entity != null) { + return entity; + } + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0ebd0669/core/src/main/java/org/apache/brooklyn/entity/group/RemovalStrategy.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/RemovalStrategy.java b/core/src/main/java/org/apache/brooklyn/entity/group/RemovalStrategy.java new file mode 100644 index 0000000..61fc3fa --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/RemovalStrategy.java @@ -0,0 +1,30 @@ +/* + * 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.Collection; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.objs.BasicConfigurableObject; + +import com.google.common.base.Function; + +public abstract class RemovalStrategy extends BasicConfigurableObject implements Function<Collection<Entity>, Entity> { + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0ebd0669/core/src/main/java/org/apache/brooklyn/entity/group/SensorMatchingRemovalStrategy.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SensorMatchingRemovalStrategy.java b/core/src/main/java/org/apache/brooklyn/entity/group/SensorMatchingRemovalStrategy.java new file mode 100644 index 0000000..037da44 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/SensorMatchingRemovalStrategy.java @@ -0,0 +1,50 @@ +/* + * 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.Collection; +import java.util.Objects; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; + +import com.google.common.reflect.TypeToken; + +public class SensorMatchingRemovalStrategy<T> extends RemovalStrategy { + public static final ConfigKey<AttributeSensor> SENSOR = ConfigKeys.newConfigKey(new TypeToken<AttributeSensor>() {}, "sensor.matching.sensor"); + // Would be nice to use ConfigKey<T>, but TypeToken<T> cannot be instantiated at runtime + public static final ConfigKey<Object> DESIRED_VALUE = ConfigKeys.newConfigKey(new TypeToken<Object>() {}, "sensor.matching.value"); + + @Nullable + @Override + public Entity apply(@Nullable Collection<Entity> input) { + AttributeSensor<T> sensor = config().get(SENSOR); + Object desiredValue = config().get(DESIRED_VALUE); + for (Entity entity : input) { + if (Objects.equals(desiredValue, entity.sensors().get(sensor))) { + return entity; + } + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0ebd0669/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java index b58c630..fe9e09f 100644 --- a/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java +++ b/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java @@ -49,6 +49,7 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.sensor.SensorEvent; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.RecordingSensorEventListener; import org.apache.brooklyn.core.entity.factory.EntityFactory; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; @@ -1161,6 +1162,56 @@ public class DynamicClusterTest extends BrooklynAppUnitTestSupport { } } + @Test + public void testResizeStrategies() throws Exception { + int clusterSize = 5; + + ImmutableList.Builder<RemovalStrategy> sensorMatchingStrategiesBuilder = ImmutableList.builder(); + for (int i = 0; i < clusterSize; i++){ + SensorMatchingRemovalStrategy sensorMatchingRemovalStrategy = new SensorMatchingRemovalStrategy(); + sensorMatchingRemovalStrategy.config().set(SensorMatchingRemovalStrategy.SENSOR, TestEntity.SEQUENCE); + sensorMatchingRemovalStrategy.config().set(SensorMatchingRemovalStrategy.DESIRED_VALUE, i); + sensorMatchingStrategiesBuilder.add(sensorMatchingRemovalStrategy); + } + + RemovalStrategy firstFrom = new FirstFromRemovalStrategy(); + firstFrom.config().set(FirstFromRemovalStrategy.STRATEGIES, sensorMatchingStrategiesBuilder.build()); + + DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class)) + .configure(DynamicCluster.INITIAL_SIZE, clusterSize) + .configure(DynamicCluster.REMOVAL_STRATEGY, firstFrom)); + + cluster.start(ImmutableList.of(loc)); + + assertEquals(cluster.getMembers().size(), clusterSize); + EntityAsserts.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true); + + // Set the sensor values of the entities in a non-linear pattern (4, 0, 3, 1, 2), then resize the cluster + // down by 1 and see if the correct entity has been removed, then resize down by 3 + Iterator<Entity> childIterator = cluster.getMembers().iterator(); + for (int i : new int[] {4, 0, 3, 1, 2}) { + childIterator.next().sensors().set(TestEntity.SEQUENCE, i); + } + + assertEntityCollectionContainsSequence(cluster.getMembers(), ImmutableSet.of(0, 1, 2, 3, 4)); + + cluster.resizeByDelta(-1); + EntityAsserts.assertAttributeEqualsEventually(cluster, DynamicCluster.GROUP_SIZE, 4); + assertEntityCollectionContainsSequence(cluster.getMembers(), ImmutableSet.of(1, 2, 3, 4)); + + cluster.resizeByDelta(-3); + EntityAsserts.assertAttributeEqualsEventually(cluster, DynamicCluster.GROUP_SIZE, 1); + assertEntityCollectionContainsSequence(cluster.getMembers(), ImmutableSet.of(4)); + } + + private void assertEntityCollectionContainsSequence(Collection<Entity> entities, Set<Integer> expected) { + assertEquals(entities.size(), expected.size()); + for (Entity entity : entities) { + assertTrue(expected.contains(entity.sensors().get(TestEntity.SEQUENCE))); + } + } + private void assertFirstAndNonFirstCounts(Collection<Entity> members, int expectedFirstCount, int expectedNonFirstCount) { Set<Entity> found = MutableSet.of(); for (Entity e: members) {
