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) {

Reply via email to