Repository: brooklyn-server Updated Branches: refs/heads/master 86070d8a1 -> acb5a68c3
AutoScalerPolicy to resize to limits on expunge Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/00318ef3 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/00318ef3 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/00318ef3 Branch: refs/heads/master Commit: 00318ef30bac60f0f3d0147f868918999b1b1833 Parents: d961de3 Author: Martin Harris <[email protected]> Authored: Thu Aug 17 14:52:10 2017 +0100 Committer: Martin Harris <[email protected]> Committed: Wed Aug 23 10:36:34 2017 +0100 ---------------------------------------------------------------------- .../test/entity/TestSizeRecordingCluster.java | 39 +++++++ .../entity/TestSizeRecordingClusterImpl.java | 59 ++++++++++ .../policy/autoscaling/AutoScalerPolicy.java | 21 ++++ .../AutoScalerPolicyPoolSizeTest.java | 110 +++++++++++++++++++ 4 files changed, 229 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/00318ef3/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingCluster.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingCluster.java b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingCluster.java new file mode 100644 index 0000000..107e65d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingCluster.java @@ -0,0 +1,39 @@ +/* + * 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.core.test.entity; + +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.entity.group.DynamicCluster; + +import java.util.List; + +/** + * Similar to {@link TestCluster}, however the intercepts are simply used to record the resize, and not + * mock them, instead delegating to {@link DynamicCluster} + */ +@ImplementedBy(TestSizeRecordingClusterImpl.class) +public interface TestSizeRecordingCluster extends DynamicCluster { + + AttributeSensor<Integer> SIZE_HISTORY_RECORD_COUNT = Sensors.newIntegerSensor("size.history.count", "Number of entries in the size history record"); + + List<Integer> getSizeHistory(); + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/00318ef3/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingClusterImpl.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingClusterImpl.java b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingClusterImpl.java new file mode 100644 index 0000000..5a59c77 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestSizeRecordingClusterImpl.java @@ -0,0 +1,59 @@ +/* + * 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.core.test.entity; + +import com.google.common.collect.ImmutableList; +import org.apache.brooklyn.entity.group.DynamicClusterImpl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TestSizeRecordingClusterImpl extends DynamicClusterImpl implements TestSizeRecordingCluster { + + public TestSizeRecordingClusterImpl () { + } + + private final List<Integer> sizeHistory = Collections.synchronizedList(new ArrayList<Integer>()); + + @Override + public Integer resize(Integer desiredSize) { + int existingSize = getCurrentSize(); + int newSize = super.resize(desiredSize); + if (newSize != existingSize) { + addSizeHistory(newSize); + } + return newSize; + } + + @Override + public List<Integer> getSizeHistory() { + synchronized (sizeHistory) { + return ImmutableList.copyOf(sizeHistory); + } + } + + private void addSizeHistory(int newSize) { + synchronized (sizeHistory) { + sizeHistory.add(newSize); + sensors().set(SIZE_HISTORY_RECORD_COUNT, sizeHistory.size()); + } + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/00318ef3/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java ---------------------------------------------------------------------- diff --git a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java index a3760da..a09c39d 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java @@ -45,6 +45,7 @@ import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.policy.AbstractPolicy; import org.apache.brooklyn.core.sensor.BasicNotificationSensor; +import org.apache.brooklyn.entity.group.DynamicCluster; import org.apache.brooklyn.policy.autoscaling.SizeHistory.WindowSummary; import org.apache.brooklyn.policy.loadbalancing.LoadBalancingPolicy; import org.apache.brooklyn.util.collections.MutableMap; @@ -458,6 +459,25 @@ public class AutoScalerPolicy extends AbstractPolicy { } }; + /** + * This should usually be a no-op as the min and max pool sizes are taken into account when + * an automatic resize event occurs, however this covers the special case where the user + * has manually resized the pool + */ + private SensorEventListener<? super Integer> poolEventHandler = new SensorEventListener<Integer>() { + @Override + public void onEvent(SensorEvent<Integer> event) { + Sensor<Integer> sensor = event.getSensor(); + Preconditions.checkArgument(sensor.equals(DynamicCluster.GROUP_SIZE), "Unexpected sensor " + sensor); + Integer size = event.getValue(); + if (size > getMaxPoolSize()) { + scheduleResize(getMaxPoolSize()); + } else if (size < getMinPoolSize()) { + scheduleResize(getMinPoolSize()); + } + } + }; + public AutoScalerPolicy() { } @@ -687,6 +707,7 @@ public class AutoScalerPolicy extends AbstractPolicy { subscriptions().subscribe(poolEntity, getPoolColdSensor(), utilizationEventHandler); subscriptions().subscribe(poolEntity, getPoolHotSensor(), utilizationEventHandler); subscriptions().subscribe(poolEntity, getPoolOkSensor(), utilizationEventHandler); + subscriptions().subscribe(poolEntity, DynamicCluster.GROUP_SIZE, poolEventHandler); } private ThreadFactory newThreadFactory() { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/00318ef3/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyPoolSizeTest.java ---------------------------------------------------------------------- diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyPoolSizeTest.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyPoolSizeTest.java new file mode 100644 index 0000000..b03c194 --- /dev/null +++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyPoolSizeTest.java @@ -0,0 +1,110 @@ +/* + * 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.policy.autoscaling; + +import com.google.common.collect.ImmutableList; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.trait.Resizable; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestCluster; +import org.apache.brooklyn.core.test.entity.TestSizeRecordingCluster; +import org.apache.brooklyn.entity.stock.BasicStartable; +import org.apache.brooklyn.util.collections.MutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; + +public class AutoScalerPolicyPoolSizeTest extends BrooklynAppUnitTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(AutoScalerPolicyPoolSizeTest.class); + + private static final int CLUSTER_INIITIAL_SIZE = 3; + private static final int CLUSTER_MIN_SIZE = 2; + private static final int CLUSTER_MAX_SIZE = 4; + + AutoScalerPolicy policy; + TestSizeRecordingCluster cluster; + List<Integer> resizes = MutableList.of(); + + @BeforeMethod(alwaysRun = true) + @Override + public void setUp() throws Exception { + super.setUp(); + LOG.info("resetting " + getClass().getSimpleName()); + cluster = app.createAndManageChild(EntitySpec.create(TestSizeRecordingCluster.class) + .configure(TestCluster.INITIAL_SIZE, CLUSTER_INIITIAL_SIZE) + .configure(TestCluster.MEMBER_SPEC, EntitySpec.create(BasicStartable.class)) + ); + PolicySpec<AutoScalerPolicy> policySpec = PolicySpec.create(AutoScalerPolicy.class) + .configure(AutoScalerPolicy.RESIZE_OPERATOR, new ResizeOperator() { + @Override + public Integer resize(Entity entity, Integer desiredSize) { + LOG.info("resizing to " + desiredSize); + resizes.add(desiredSize); + return ((Resizable) entity).resize(desiredSize); + } + }) + .configure(AutoScalerPolicy.MIN_POOL_SIZE, CLUSTER_MIN_SIZE) + .configure(AutoScalerPolicy.MAX_POOL_SIZE, CLUSTER_MAX_SIZE); + policy = cluster.policies().add(policySpec); + app.start(ImmutableList.of()); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() throws Exception { + try { + if (policy != null) policy.destroy(); + } finally { + super.tearDown(); + cluster = null; + policy = null; + } + } + + @Test + public void testResizeUp() throws Exception { + EntityAsserts.assertAttributeEqualsEventually(cluster, TestCluster.GROUP_SIZE, CLUSTER_INIITIAL_SIZE); + // Simulate user expunging the entities manually + for (int i = 0; i < CLUSTER_MAX_SIZE - CLUSTER_MIN_SIZE; i++) { + Entities.destroyCatching(cluster.getMembers().iterator().next()); + } + EntityAsserts.assertAttributeEqualsEventually(cluster, TestSizeRecordingCluster.SIZE_HISTORY_RECORD_COUNT, 2); + Assert.assertEquals((int)cluster.getSizeHistory().get(0), CLUSTER_INIITIAL_SIZE); + Assert.assertEquals((int)cluster.getSizeHistory().get(1), CLUSTER_MIN_SIZE); + } + + @Test + public void testResizeDown() throws Exception { + EntityAsserts.assertAttributeEqualsEventually(cluster, TestCluster.GROUP_SIZE, CLUSTER_INIITIAL_SIZE); + cluster.resize(CLUSTER_MAX_SIZE + 2); + EntityAsserts.assertAttributeEqualsEventually(cluster, TestSizeRecordingCluster.SIZE_HISTORY_RECORD_COUNT, 3); + Assert.assertEquals((int)cluster.getSizeHistory().get(0), CLUSTER_INIITIAL_SIZE); + Assert.assertEquals((int)cluster.getSizeHistory().get(1), CLUSTER_MAX_SIZE + 2); + Assert.assertEquals((int)cluster.getSizeHistory().get(2), CLUSTER_MAX_SIZE); + } +}
