Package com.elastisys.scale.cloudadapters.commons.resizeplanner

Source Code of com.elastisys.scale.cloudadapters.commons.resizeplanner.ResizePlanner

package com.elastisys.scale.cloudadapters.commons.resizeplanner;

import static com.elastisys.scale.cloudadapers.api.types.Machine.withState;
import static com.elastisys.scale.cloudadapers.api.types.MachineState.REQUESTED;
import static com.elastisys.scale.commons.util.time.UtcTime.now;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.filter;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.elastisys.scale.cloudadapers.api.types.Machine;
import com.elastisys.scale.cloudadapers.api.types.MachinePool;
import com.elastisys.scale.cloudadapters.commons.scaledown.TerminationScheduler;
import com.elastisys.scale.cloudadapters.commons.scaledown.VictimSelectionPolicy;
import com.elastisys.scale.cloudadapters.commons.scaledown.VictimSelector;
import com.elastisys.scale.cloudadapters.commons.termqueue.ScheduledTermination;
import com.elastisys.scale.cloudadapters.commons.termqueue.TerminationQueue;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;

/**
* A {@link ResizePlanner} determines, for a certain machine pool, necessary
* scaling actions needed to meet a certain desired pool size.
* <p/>
* Given a machine pool and a termination queue (holding pool members currently
* scheduled for termination) and policies for scale-downs, a
* {@link ResizePlanner} produces a {@link ResizePlan} that tells a cloud
* adapter how to modify the pool in order to reach a certain desired pool size.
* <p/>
* The effective size of the machine pool is considered to be the set of
* allocated machines (see {@link Machine#isAllocated()}) that have not been
* scheduled for termination.
*
* @see ResizePlan
*
*
*
*/
public class ResizePlanner {
  static final Logger LOG = LoggerFactory.getLogger(ResizePlanner.class);

  /** The current pool members. */
  private final MachinePool machinePool;
  /**
   * Termination queue holding the pool members currently scheduled for
   * termination.
   */
  private final TerminationQueue terminationQueue;
  /** The {@link VictimSelectionPolicy} to use when shrinking the pool. */
  private final VictimSelectionPolicy victimSelectionPolicy;
  /**
   * How many seconds prior to the next instance hour machines should be
   * scheduled for termination. This should be set to a conservative and safe
   * value to prevent machines from being billed for an additional hour. If
   * set to {@code 0}, machines are scheduled for immediate termination. This
   * may be useful in clouds that allow per-minute billing, or in private
   * clouds where machine termination should be carried out as quickly as
   * possible.
   */
  private final long instanceHourMargin;

  /**
   * Creates a new {@link ResizePlanner} for a certain machine pool.
   *
   * @param machinePool
   *            The current pool members.
   * @param terminationQueue
   *            Termination queue holding the pool members currently scheduled
   *            for termination.
   * @param victimSelectionPolicy
   *            The {@link VictimSelectionPolicy} to use when shrinking the
   *            pool.
   * @param instanceHourMargin
   *            How many seconds prior to the next instance hour machines
   *            should be scheduled for termination. This should be set to a
   *            conservative and safe value to prevent machines from being
   *            billed for an additional hour. If set to {@code 0}, machines
   *            are scheduled for immediate termination. This may be useful in
   *            clouds that allow per-minute billing, or in private clouds
   *            where machine termination should be carried out as quickly as
   *            possible.
   */
  public ResizePlanner(MachinePool machinePool,
      TerminationQueue terminationQueue,
      VictimSelectionPolicy victimSelectionPolicy, long instanceHourMargin) {
    this.machinePool = machinePool;
    this.terminationQueue = terminationQueue;
    this.victimSelectionPolicy = victimSelectionPolicy;
    this.instanceHourMargin = instanceHourMargin;
    validate();
  }

  /**
   * Performs a basic sanity check of this {@link ResizePlanner}. If values
   * are sane, the method simply returns. Should the {@link ResizePlanner}
   * contain an illegal mix of values, an {@link IllegalArgumentException} is
   * thrown.
   *
   * @throws IllegalArgumentException
   */
  public void validate() throws IllegalArgumentException {
    checkArgument(this.machinePool != null, "missing machinePool");
    checkArgument(this.terminationQueue != null,
        "missing termination queue");
    checkArgument(this.victimSelectionPolicy != null,
        "missing victim selection policy");

    long hourSeconds = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS);
    checkArgument(
        Range.closedOpen(0L, hourSeconds).contains(
            this.instanceHourMargin),
        "instanceHourMargin must be within interval [0, 3600) seconds.");
  }

  /**
   * Returns the <i>effective size</i> of the machine pool, being the number
   * of allocated machines (see {@link Machine#isAllocated()}) that have not
   * been scheduled for termination in the termination queue.
   *
   * @return
   */
  public int getEffectiveSize() {
    List<Machine> allocatedPoolMembers = allocatedMachines(this.machinePool);
    int currentPoolSize = allocatedPoolMembers.size();
    // number of pool machines currently scheduled for termination
    int termQueueSize = this.terminationQueue.size();
    // number of pool members that are not marked for termination
    int netSize = currentPoolSize - termQueueSize;
    return netSize;
  }

  /**
   * Calculates how the pool should be resized to reach a certain desired
   * size.
   *
   * @param desiredSize
   *            The desired number of active machines in the machine pool.
   * @return A {@link ResizePlan} to reach the desired pool size.
   */
  public ResizePlan calculateResizePlan(int desiredSize) {
    checkArgument(desiredSize >= 0, "desired pool size must be >= 0");

    int toRequest = 0;
    int toSpare = 0;
    List<ScheduledTermination> toTerminate = Lists.newArrayList();

    List<Machine> allocatedMachines = allocatedMachines(this.machinePool);
    int currentPoolSize = allocatedMachines.size();
    int termQueueSize = this.terminationQueue.size();
    int netSize = getEffectiveSize();

    LOG.debug("desired pool size: {}, " + "current pool size: {}, "
        + "net size (excluding marked for termination): {}, "
        + "termination queue: {}", desiredSize, currentPoolSize,
        netSize, this.terminationQueue);

    if (desiredSize > netSize) {
      // need to scale up
      int missingMachines = desiredSize - netSize;
      toSpare = Math.min(termQueueSize, missingMachines);
      toRequest = missingMachines - toSpare;
    } else if (desiredSize < netSize) {
      // need to scale down
      int excessMachines = netSize - desiredSize;
      toTerminate = scheduleForTermination(excessMachines);
    } else {
      LOG.debug("desired size {} equals net pool size, nothing to do",
          desiredSize);
    }

    ResizePlan resizePlan = new ResizePlan(toRequest, toSpare, toTerminate);
    LOG.debug("suggested resize plan: {}", resizePlan);
    return resizePlan;
  }

  /**
   * Selects a number of victim machines and schedules them for termination in
   * order to shrink the machine pool.
   *
   * @param excessMachines
   *            The number of machines to terminate.
   * @return
   */
  private List<ScheduledTermination> scheduleForTermination(int excessMachines) {
    List<ScheduledTermination> toTerminate = Lists.newArrayList();
    List<Machine> candidates = allocatedMachines(this.machinePool);

    LOG.debug("need to select {} victim(s) for termination from "
        + "{} allocated machine(s)", excessMachines, candidates.size());

    // Favor termination of REQUESTED machines (since these are likely
    // to not yet incur cost). Terminate them immediately.
    Iterable<Machine> inRequestedState = filter(candidates,
        withState(REQUESTED));
    Iterator<Machine> requestedStateMachines = inRequestedState.iterator();
    while ((excessMachines > 0) && requestedStateMachines.hasNext()) {
      toTerminate.add(new ScheduledTermination(requestedStateMachines
          .next(), now()));
      excessMachines--;
    }

    // use victim selection policy to pick victims from any remaining
    // candidates
    candidates.removeAll(Lists.newArrayList(inRequestedState));
    List<Machine> victims = victimSelector().selectVictims(candidates,
        excessMachines);
    for (Machine victim : victims) {
      toTerminate.add(scheduleTermination(victim));
    }
    return toTerminate;
  }

  private ScheduledTermination scheduleTermination(Machine victim) {
    return new TerminationScheduler(this.instanceHourMargin)
        .scheduleEviction(victim);
  }

  private VictimSelector victimSelector() {
    return new VictimSelector(this.terminationQueue,
        this.victimSelectionPolicy.getVictimSelectionStrategy());
  }

  /**
   * Collects all allocated machines in a pool (see
   * {@link Machine#isAllocated()}).
   *
   * @param machinePool
   *
   * @return
   */
  private List<Machine> allocatedMachines(MachinePool machinePool) {
    return machinePool.getAllocatedMachines();
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(this.machinePool, this.terminationQueue,
        this.victimSelectionPolicy, this.instanceHourMargin);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof ResizePlanner) {
      ResizePlanner that = (ResizePlanner) obj;
      return Objects.equal(this.machinePool, that.machinePool)
          && Objects.equal(this.terminationQueue,
              that.terminationQueue)
          && Objects.equal(this.victimSelectionPolicy,
              that.victimSelectionPolicy)
          && Objects.equal(this.instanceHourMargin,
              that.instanceHourMargin);
    }
    return super.equals(obj);
  }
}
TOP

Related Classes of com.elastisys.scale.cloudadapters.commons.resizeplanner.ResizePlanner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.