Java tutorial
/* * Copyright 2014-2016 by Cloudsoft Corporation Limited * * Licensed 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 clocker.docker.policy; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import clocker.docker.entity.DockerInfrastructure; import clocker.docker.location.strategy.DockerAwarePlacementStrategy; import clocker.docker.location.strategy.basic.MaxContainersPlacementStrategy; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.SensorEvent; import org.apache.brooklyn.api.sensor.SensorEventListener; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.render.RendererHints; import org.apache.brooklyn.core.enricher.AbstractEnricher; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.sensor.BasicNotificationSensor; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.apache.brooklyn.util.math.MathFunctions; /** * An {@link Enricher} that emits {@link #DOCKER_CONTAINER_CLUSTER_HOT hot} or {@link #DOCKER_CONTAINER_CLUSTER_COLD cold} * notifications based on the {@link #DOCKER_CONTAINER_UTILISATION container utilisation} and * {@link #CONTAINER_HEADROOM headroom} requirements. * <p> * This enricher must be applied to the {@link DockerInfrastructure} entity, and is normally created during the * entity {@link AbstractEntity#init() initialisation} along with an {@link AutoScalerPolicy}. Notifications * are fed into the policy, which resizes the child Docker host cluster. Workrates for the policy are calculated * as resource utilisation percentages, based on the configured maximum number of containers per host. This is * obtained from the {@link MaxContainersPlacementStrategy} or through the * {@link MaxContainersPlacementStrategy#DOCKER_CONTAINER_CLUSTER_MAX_SIZE maxContainers} configuration on the * infrastructure entity. Workrate thresholds are calculated based on cluster utilisation with the specific headroom * available, and the cluster will be resized as appropriate. */ public class ContainerHeadroomEnricher extends AbstractEnricher { private static final Logger LOG = LoggerFactory.getLogger(ContainerHeadroomEnricher.class); @SetFromFlag("headroom") public static final ConfigKey<Integer> CONTAINER_HEADROOM = ConfigKeys.newIntegerConfigKey( "docker.container.cluster.headroom.count", "Required headroom (number of free containers) for the Docker cluster"); @SetFromFlag("headroomPercent") public static final ConfigKey<Double> CONTAINER_HEADROOM_PERCENTAGE = ConfigKeys.newDoubleConfigKey( "docker.container.cluster.headroom.percent", "Required headroom (percentage free containers) for the Docker cluster"); public static final ConfigKey<Boolean> 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<Integer> CONTAINERS_NEEDED = Sensors.newIntegerSensor( "docker.container.cluster.needed", "Number of containers needed to give requierd headroom"); public static final AttributeSensor<Double> DOCKER_CONTAINER_UTILISATION = Sensors.newDoubleSensor( "docker.container.cluster.utilisation", "Resource utilisation of Docker container cluster"); public static final BasicNotificationSensor<Map> DOCKER_CONTAINER_CLUSTER_HOT = new BasicNotificationSensor<Map>( Map.class, "docker.container.cluster.hot", "Docker cluster has insufficient containers for current workload"); public static final BasicNotificationSensor<Map> DOCKER_CONTAINER_CLUSTER_COLD = new BasicNotificationSensor<Map>( Map.class, "docker.container.cluster.cold", "Docker cluster has too many containers for current workload"); public static final BasicNotificationSensor<Map> DOCKER_CONTAINER_CLUSTER_OK = new BasicNotificationSensor<Map>( Map.class, "docker.container.cluster.ok", "Docker cluster is ok for the current workload"); static { RendererHints.register(DOCKER_CONTAINER_UTILISATION, RendererHints.displayValue(MathFunctions.percent(3))); } private BasicNotificationSensor lastPublished = null; @Override public void setEntity(EntityLocal entity) { Preconditions.checkArgument(entity instanceof DockerInfrastructure, "Entity must be a DockerInfrastructure: %s", entity); super.setEntity(entity); Integer headroom = config().get(CONTAINER_HEADROOM); Double percent = config().get(CONTAINER_HEADROOM_PERCENTAGE); Preconditions.checkArgument((headroom != null) ^ (percent != null), "Headroom must be configured as either number or percentage for this enricher"); if (headroom != null) { Preconditions.checkArgument(headroom > 0, "Headroom must be a positive integer: %d", headroom); } if (percent != null) { Preconditions.checkArgument(percent > 0d && percent < 1d, "Headroom percentage must be between 0.0 and 1.0: %f", percent); } subscriptions().subscribe(entity, DockerInfrastructure.DOCKER_CONTAINER_COUNT, new Listener()); subscriptions().subscribe(entity, DockerInfrastructure.DOCKER_HOST_COUNT, new Listener()); } private class Listener implements SensorEventListener<Object> { @Override public void onEvent(SensorEvent<Object> event) { recalculate(); } } private void recalculate() { Integer maxContainers = null; List<DockerAwarePlacementStrategy> strategies = entity.config() .get(DockerInfrastructure.PLACEMENT_STRATEGIES); Optional<DockerAwarePlacementStrategy> lookup = Iterables.tryFind(strategies, Predicates.instanceOf(MaxContainersPlacementStrategy.class)); if (lookup.isPresent()) { maxContainers = ((MaxContainersPlacementStrategy) lookup.get()).config() .get(MaxContainersPlacementStrategy.DOCKER_CONTAINER_CLUSTER_MAX_SIZE); } if (maxContainers == null) { maxContainers = entity.config().get(MaxContainersPlacementStrategy.DOCKER_CONTAINER_CLUSTER_MAX_SIZE); } 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); Integer hosts = entity.sensors().get(DockerInfrastructure.DOCKER_HOST_COUNT); if (containers == null || hosts == null) return; int possible = maxContainers * hosts; int available = possible - containers; // Calculate headroom Integer headroom = config().get(CONTAINER_HEADROOM); Double percent = config().get(CONTAINER_HEADROOM_PERCENTAGE); if (headroom == null) { headroom = (int) Math.ceil(percent * possible); } // Calculate requirements int needed = headroom - available; double utilisation = (double) containers / (double) possible; if (LOG.isDebugEnabled()) { LOG.debug("Headroom enricher: {} containers on {} hosts, {} available and {} needed", new Object[] { containers, hosts, available, needed }); } double lowThreshold = (double) (possible - (headroom + maxContainers)) / (double) possible; lowThreshold = Math.max(0d, lowThreshold); double highThreshold = (double) (possible - headroom) / (double) possible; highThreshold = Math.max(0d, highThreshold); // Emit current status of the pool as sensor data emit(CONTAINERS_NEEDED, needed); emit(DOCKER_CONTAINER_UTILISATION, utilisation); // Emit pool hot or cold sensors if headroom requirements not met Map<String, Object> properties = ImmutableMap.<String, Object>of(AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, hosts, AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, utilisation, AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, lowThreshold, AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, highThreshold); if (needed > 0) { lastPublished = DOCKER_CONTAINER_CLUSTER_HOT; emit(DOCKER_CONTAINER_CLUSTER_HOT, properties); } else if (tooColdEnabled && available > (headroom + maxContainers)) { lastPublished = DOCKER_CONTAINER_CLUSTER_COLD; emit(DOCKER_CONTAINER_CLUSTER_COLD, properties); } else { // Only emit ok if we weren't previously if (lastPublished != null && lastPublished != DOCKER_CONTAINER_CLUSTER_OK) { lastPublished = DOCKER_CONTAINER_CLUSTER_OK; emit(DOCKER_CONTAINER_CLUSTER_OK, properties); } } } }