| /* |
| * 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 static org.apache.brooklyn.policy.autoscaling.AutoScalerPolicyTest.currentSizeAsserter; |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertFalse; |
| import static org.testng.Assert.assertTrue; |
| |
| import java.util.List; |
| |
| import org.apache.brooklyn.api.entity.EntitySpec; |
| import org.apache.brooklyn.api.sensor.AttributeSensor; |
| import org.apache.brooklyn.api.sensor.SensorEvent; |
| import org.apache.brooklyn.api.sensor.SensorEventListener; |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.sensor.BasicNotificationSensor; |
| import org.apache.brooklyn.core.sensor.Sensors; |
| import org.apache.brooklyn.core.test.entity.TestApplication; |
| import org.apache.brooklyn.core.test.entity.TestCluster; |
| import org.apache.brooklyn.core.test.entity.TestEntity; |
| import org.apache.brooklyn.test.Asserts; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.testng.annotations.AfterMethod; |
| import org.testng.annotations.BeforeMethod; |
| import org.testng.annotations.Test; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| |
| public class AutoScalerPolicyMetricTest { |
| |
| private static long SHORT_WAIT_MS = 250; |
| |
| private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib"); |
| TestApplication app; |
| TestCluster tc; |
| |
| @BeforeMethod(alwaysRun=true) |
| public void before() { |
| app = TestApplication.Factory.newManagedInstanceForTests(); |
| tc = app.createAndManageChild(EntitySpec.create(TestCluster.class) |
| .configure("initialSize", 1)); |
| } |
| |
| @AfterMethod(alwaysRun=true) |
| public void tearDown() throws Exception { |
| if (app != null) Entities.destroyAll(app.getManagementContext()); |
| } |
| |
| @Test |
| public void testIncrementsSizeIffUpperBoundExceeded() { |
| tc.resize(1); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 100); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1)); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 101); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| } |
| |
| @Test |
| public void testDecrementsSizeIffLowerBoundExceeded() { |
| tc.resize(2); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 50); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2)); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 49); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 1)); |
| } |
| |
| @Test(groups="Integration") |
| public void testIncrementsSizeInProportionToMetric() { |
| tc.resize(5); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| // workload 200 so requires doubling size to 10 to handle: (200*5)/100 = 10 |
| tc.sensors().set(MY_ATTRIBUTE, 200); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 10)); |
| |
| // workload 5, requires 1 entity: (10*110)/100 = 11 |
| tc.sensors().set(MY_ATTRIBUTE, 110); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 11)); |
| } |
| |
| @Test(groups="Integration") |
| public void testDecrementsSizeInProportionToMetric() { |
| tc.resize(5); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| // workload can be handled by 4 servers, within its valid range: (49*5)/50 = 4.9 |
| tc.sensors().set(MY_ATTRIBUTE, 49); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 4)); |
| |
| // workload can be handled by 4 servers, within its valid range: (25*4)/50 = 2 |
| tc.sensors().set(MY_ATTRIBUTE, 25); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 0); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 1)); |
| } |
| |
| @Test(groups="Integration") |
| public void testObeysMinAndMaxSize() { |
| tc.resize(4); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE) |
| .metricLowerBound(50).metricUpperBound(100) |
| .minPoolSize(2).maxPoolSize(6) |
| .build(); |
| tc.policies().add(policy); |
| |
| // Decreases to min-size only |
| tc.sensors().set(MY_ATTRIBUTE, 0); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| |
| // Increases to max-size only |
| tc.sensors().set(MY_ATTRIBUTE, 100000); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 6)); |
| } |
| |
| @Test(groups="Integration",invocationCount=20) |
| public void testWarnsWhenMaxCapReached() { |
| final List<MaxPoolSizeReachedEvent> maxReachedEvents = Lists.newCopyOnWriteArrayList(); |
| tc.resize(1); |
| |
| BasicNotificationSensor<MaxPoolSizeReachedEvent> maxSizeReachedSensor = AutoScalerPolicy.DEFAULT_MAX_SIZE_REACHED_SENSOR; |
| |
| app.subscriptions().subscribe(tc, maxSizeReachedSensor, new SensorEventListener<MaxPoolSizeReachedEvent>() { |
| @Override public void onEvent(SensorEvent<MaxPoolSizeReachedEvent> event) { |
| maxReachedEvents.add(event.getValue()); |
| }}); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE) |
| .metricLowerBound(50).metricUpperBound(100) |
| .maxPoolSize(6) |
| .maxSizeReachedSensor(maxSizeReachedSensor) |
| .build(); |
| tc.policies().add(policy); |
| |
| // workload can be handled by 6 servers, so no need to notify: 6 <= (100*6)/50 |
| tc.sensors().set(MY_ATTRIBUTE, 600); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 6)); |
| assertTrue(maxReachedEvents.isEmpty()); |
| |
| // Increases to above max capacity: would require (100000*6)/100 = 6000 |
| tc.sensors().set(MY_ATTRIBUTE, 100000); |
| |
| // Assert our listener gets notified (once) |
| Asserts.succeedsEventually(new Runnable() { |
| public void run() { |
| assertEquals(maxReachedEvents.size(), 1); |
| assertEquals(maxReachedEvents.get(0).getMaxAllowed(), 6); |
| assertEquals(maxReachedEvents.get(0).getCurrentPoolSize(), 6); |
| assertEquals(maxReachedEvents.get(0).getCurrentUnbounded(), 6000); |
| assertEquals(maxReachedEvents.get(0).getMaxUnbounded(), 6000); |
| assertEquals(maxReachedEvents.get(0).getTimeWindow(), 0); |
| }}); |
| Asserts.succeedsContinually(new Runnable() { |
| @Override public void run() { |
| assertEquals(maxReachedEvents.size(), 1); |
| }}); |
| currentSizeAsserter(tc, 6).run(); |
| } |
| |
| @Test |
| public void testDestructionState() { |
| tc.resize(1); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| policy.destroy(); |
| assertTrue(policy.isDestroyed()); |
| assertFalse(policy.isRunning()); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 100000); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1)); |
| |
| // TODO Could assert all subscriptions have been de-registered as well, |
| // but that requires exposing more things just for testing... |
| } |
| |
| @Test |
| public void testSuspendState() { |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| policy.suspend(); |
| assertFalse(policy.isRunning()); |
| assertFalse(policy.isDestroyed()); |
| |
| policy.resume(); |
| assertTrue(policy.isRunning()); |
| assertFalse(policy.isDestroyed()); |
| } |
| |
| @Test |
| public void testPostSuspendActions() { |
| tc.resize(1); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| policy.suspend(); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 100000); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1)); |
| } |
| |
| @Test |
| public void testPostResumeActions() { |
| tc.resize(1); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build(); |
| tc.policies().add(policy); |
| |
| policy.suspend(); |
| policy.resume(); |
| tc.sensors().set(MY_ATTRIBUTE, 101); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| } |
| |
| @Test |
| public void testSubscribesToMetricOnSpecifiedEntity() { |
| TestEntity entityWithMetric = app.createAndManageChild(EntitySpec.create(TestEntity.class)); |
| |
| tc.resize(1); |
| |
| AutoScalerPolicy policy = new AutoScalerPolicy.Builder() |
| .metric(TestEntity.SEQUENCE) |
| .entityWithMetric(entityWithMetric) |
| .metricLowerBound(50) |
| .metricUpperBound(100) |
| .build(); |
| tc.policies().add(policy); |
| |
| // First confirm that tc is not being listened to for this entity |
| tc.sensors().set(TestEntity.SEQUENCE, 101); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1)); |
| |
| // Then confirm we listen to the correct "entityWithMetric" |
| entityWithMetric.sensors().set(TestEntity.SEQUENCE, 101); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| } |
| |
| @Test(groups="Integration") |
| public void testOnFailedGrowWillSetHighwaterMarkAndNotResizeAboveThatAgain() { |
| tc = app.createAndManageChild(EntitySpec.create(TestCluster.class) |
| .configure("initialSize", 0) |
| .configure(TestCluster.MAX_SIZE, 2)); |
| |
| tc.resize(1); |
| |
| tc.policies().add(AutoScalerPolicy.builder() |
| .metric(MY_ATTRIBUTE) |
| .metricLowerBound(50) |
| .metricUpperBound(100) |
| .buildSpec()); |
| |
| // workload 200 so requires doubling size to 2 to handle: (200*1)/100 = 2 |
| tc.sensors().set(MY_ATTRIBUTE, 200); |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2), "desired="+tc.getDesiredSizeHistory()); |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory()); |
| tc.sensors().set(MY_ATTRIBUTE, 100); |
| |
| // workload 110, requires 1 more entity: (2*110)/100 = 2.1 |
| // But max size is 2, so resize will fail. |
| tc.sensors().set(MY_ATTRIBUTE, 110); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory()); // TODO succeeds eventually? |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory()); |
| |
| // another attempt to resize will not cause it to ask |
| tc.sensors().set(MY_ATTRIBUTE, 110); |
| Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory()); |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory()); |
| |
| // but if we shrink down again, then we'll still be able to go up to the previous level. |
| // We'll only try to go as high as the previous high-water mark though. |
| tc.sensors().set(MY_ATTRIBUTE, 1); |
| Asserts.succeedsEventually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3, 1), "desired="+tc.getDesiredSizeHistory()); |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2, 1), "sizes="+tc.getSizeHistory()); |
| |
| tc.sensors().set(MY_ATTRIBUTE, 10000); |
| Asserts.succeedsEventually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3, 1, 2), "desired="+tc.getDesiredSizeHistory()); |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2, 1, 2), "sizes="+tc.getSizeHistory()); |
| } |
| |
| // When there is a resizeUpStabilizationDelay, it remembers all the previously requested sizes (in the recent history) |
| // and then looks at those in the stabilization-delay to determine the sustained desired. This test checks that |
| // we apply the highwater-mark even when the desired size had been recorded prior to the highwater mark being |
| // discovered. |
| @Test(groups="Integration") |
| public void testOnFailedGrowWithStabilizationDelayWillSetHighwaterMarkAndNotResizeAboveThatAgain() throws Exception { |
| tc = app.createAndManageChild(EntitySpec.create(TestCluster.class) |
| .configure("initialSize", 0) |
| .configure(TestCluster.MAX_SIZE, 2)); |
| |
| tc.resize(1); |
| |
| tc.policies().add(AutoScalerPolicy.builder() |
| .metric(MY_ATTRIBUTE) |
| .metricLowerBound(50) |
| .metricUpperBound(100) |
| .resizeUpStabilizationDelay(Duration.ONE_SECOND) |
| .buildSpec()); |
| |
| // workload 200 so requires doubling size to 2 to handle: (200*1)/100 = 2 |
| for (int i = 0; i < 10; i++) { |
| tc.sensors().set(MY_ATTRIBUTE, 200 + (i*100)); |
| Thread.sleep(100); |
| } |
| |
| Asserts.succeedsEventually(currentSizeAsserter(tc, 2)); |
| Asserts.succeedsContinually(currentSizeAsserter(tc, 2)); |
| assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory()); |
| assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory()); |
| } |
| } |