From 68d38e45662830e99428c0abdf631dae6db6342c Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Wed, 4 May 2016 21:30:01 +0100 Subject: [PATCH] Issue #288: Disable auto-scale down of docker-host-cluster --- .../policy/ContainerHeadroomEnricher.java | 8 +++- .../policy/ContainerHeadroomEnricherTest.java | 42 ++++++++++++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docker/src/main/java/clocker/docker/policy/ContainerHeadroomEnricher.java b/docker/src/main/java/clocker/docker/policy/ContainerHeadroomEnricher.java index c537e51e..3837b416 100644 --- a/docker/src/main/java/clocker/docker/policy/ContainerHeadroomEnricher.java +++ b/docker/src/main/java/clocker/docker/policy/ContainerHeadroomEnricher.java @@ -73,6 +73,11 @@ public class ContainerHeadroomEnricher extends AbstractEnricher { public static final ConfigKey CONTAINER_HEADROOM_PERCENTAGE = ConfigKeys.newDoubleConfigKey( "docker.container.cluster.headroom.percent", "Required headroom (percentage free containers) for the Docker cluster"); + public static final ConfigKey CLUSTER_TOO_COLD_ENABLED = ConfigKeys.newBooleanConfigKey( + "docker.container.cluster.cold.enabled", + "Whether to emit too-cold events (which can trigger auto-scaling down)", + false); + public static final AttributeSensor CONTAINERS_NEEDED = Sensors.newIntegerSensor( "docker.container.cluster.needed", "Number of containers needed to give requierd headroom"); public static final AttributeSensor DOCKER_CONTAINER_UTILISATION = Sensors.newDoubleSensor( @@ -130,6 +135,7 @@ private void recalculate() { if (maxContainers == null) { maxContainers = MaxContainersPlacementStrategy.DEFAULT_MAX_CONTAINERS; } + boolean tooColdEnabled = Boolean.TRUE.equals(config().get(CLUSTER_TOO_COLD_ENABLED)); // Calculate cluster state Integer containers = entity.sensors().get(DockerInfrastructure.DOCKER_CONTAINER_COUNT); @@ -172,7 +178,7 @@ private void recalculate() { if (needed > 0) { lastPublished = DOCKER_CONTAINER_CLUSTER_HOT; emit(DOCKER_CONTAINER_CLUSTER_HOT, properties); - } else if (available > (headroom + maxContainers)) { + } else if (tooColdEnabled && available > (headroom + maxContainers)) { lastPublished = DOCKER_CONTAINER_CLUSTER_COLD; emit(DOCKER_CONTAINER_CLUSTER_COLD, properties); } else { diff --git a/docker/src/test/java/clocker/docker/policy/ContainerHeadroomEnricherTest.java b/docker/src/test/java/clocker/docker/policy/ContainerHeadroomEnricherTest.java index efa87048..996a4283 100644 --- a/docker/src/test/java/clocker/docker/policy/ContainerHeadroomEnricherTest.java +++ b/docker/src/test/java/clocker/docker/policy/ContainerHeadroomEnricherTest.java @@ -38,6 +38,7 @@ import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.sensor.SensorEvent; import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.sensor.BasicNotificationSensor; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; @@ -45,7 +46,6 @@ import org.apache.brooklyn.entity.group.DynamicGroup; import org.apache.brooklyn.entity.stock.BasicStartableImpl; import org.apache.brooklyn.test.Asserts; -import org.apache.brooklyn.test.EntityTestUtils; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; @@ -66,14 +66,14 @@ public void setUp() throws Exception { .configure(MaxContainersPlacementStrategy.DOCKER_CONTAINER_CLUSTER_MAX_SIZE, 8)); listener = new RecordingSensorEventListener(); - app.subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_HOT, listener); - app.subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_COLD, listener); - app.subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_OK, listener); + app.subscriptions().subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_HOT, listener); + app.subscriptions().subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_COLD, listener); + app.subscriptions().subscribe(entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_CLUSTER_OK, listener); } @Test public void testNoEventsWhenAllOk() throws Exception { - entity.addEnricher(EnricherSpec.create(ContainerHeadroomEnricher.class) + entity.enrichers().add(EnricherSpec.create(ContainerHeadroomEnricher.class) .configure(ContainerHeadroomEnricher.CONTAINER_HEADROOM, 4)); entity.sensors().set(DockerInfrastructure.DOCKER_HOST_COUNT, 2); @@ -87,7 +87,7 @@ public void testNoEventsWhenAllOk() throws Exception { // second later then our subsequent assertion will fail. @Test(groups="integration") public void testTooHotWhenHeadroomExceeded() throws Exception { - entity.addEnricher(EnricherSpec.create(ContainerHeadroomEnricher.class) + entity.enrichers().add(EnricherSpec.create(ContainerHeadroomEnricher.class) .configure(ContainerHeadroomEnricher.CONTAINER_HEADROOM, 4)); // Too hot: headroom insufficient by one container @@ -138,8 +138,9 @@ public void testTooHotWhenHeadroomExceeded() throws Exception { // See comment on testTooHotThenOk. @Test(groups="integration") public void testTooColdThenOk() throws Exception { - entity.addEnricher(EnricherSpec.create(ContainerHeadroomEnricher.class) - .configure(ContainerHeadroomEnricher.CONTAINER_HEADROOM, 4)); + entity.enrichers().add(EnricherSpec.create(ContainerHeadroomEnricher.class) + .configure(ContainerHeadroomEnricher.CONTAINER_HEADROOM, 4) + .configure(ContainerHeadroomEnricher.CLUSTER_TOO_COLD_ENABLED, true)); // Too cold - only need one host rather than 10 entity.sensors().set(DockerInfrastructure.DOCKER_HOST_COUNT, 10); @@ -152,7 +153,7 @@ public void testTooColdThenOk() throws Exception { .lowThreshold((80d - (4 + 8)) / 80) .highThreshold(76d/80)); - // Too hot - only need one host rather than 2 + // Too cold - only need one host rather than 2 listener.clearEventsContinually(); entity.sensors().set(DockerInfrastructure.DOCKER_HOST_COUNT, 2); @@ -181,6 +182,25 @@ public void testTooColdThenOk() throws Exception { assertNoEventsContinually(); } + // Integration because takes over a second, and because time-sensitive: + // See comment on testTooHotThenOk. + @Test(groups="integration") + public void testTooColdNotPublishedByDefault() throws Exception { + entity.enrichers().add(EnricherSpec.create(ContainerHeadroomEnricher.class) + .configure(ContainerHeadroomEnricher.CONTAINER_HEADROOM, 4)); + + // Too cold - only need one host rather than 10. + // Same state as in testTooColdThenOk, so know that it really is "too cold". + entity.sensors().set(DockerInfrastructure.DOCKER_HOST_COUNT, 10); + entity.sensors().set(DockerInfrastructure.DOCKER_CONTAINER_COUNT, 1); + + // Make everything ok again; do not expect an "ok" + entity.sensors().set(DockerInfrastructure.DOCKER_HOST_COUNT, 2); + entity.sensors().set(DockerInfrastructure.DOCKER_CONTAINER_COUNT, 8); + + assertNoEventsContinually(); + } + private void assertNoEventsContinually() { Asserts.succeedsContinually(new Runnable() { public void run() { @@ -201,8 +221,8 @@ private void assertOk(final CurrentStatus status) { } private void assertTemperatureEvent(final CurrentStatus status, final BasicNotificationSensor eventType) { - EntityTestUtils.assertAttributeEqualsEventually(assertMap, entity, ContainerHeadroomEnricher.CONTAINERS_NEEDED, status.needed); - EntityTestUtils.assertAttributeEqualsEventually(assertMap, entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_UTILISATION, status.utilization); + EntityAsserts.assertAttributeEqualsEventually(assertMap, entity, ContainerHeadroomEnricher.CONTAINERS_NEEDED, status.needed); + EntityAsserts.assertAttributeEqualsEventually(assertMap, entity, ContainerHeadroomEnricher.DOCKER_CONTAINER_UTILISATION, status.utilization); Asserts.succeedsEventually(assertMap, new Runnable() { public void run() {