All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.singularity.data.AbstractMachineManager Maven / Gradle / Ivy

package com.hubspot.singularity.data;

import com.codahale.metrics.MetricRegistry;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hubspot.singularity.MachineState;
import com.hubspot.singularity.SingularityCreateResult;
import com.hubspot.singularity.SingularityDeleteResult;
import com.hubspot.singularity.SingularityMachineAbstraction;
import com.hubspot.singularity.SingularityMachineStateHistoryUpdate;
import com.hubspot.singularity.config.SingularityConfiguration;
import com.hubspot.singularity.data.transcoders.Transcoder;
import com.hubspot.singularity.expiring.SingularityExpiringMachineState;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.utils.ZKPaths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractMachineManager>
  extends CuratorAsyncManager {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractMachineManager.class);

  private static final String HISTORY_PATH = "history";
  private static final String EXPIRING_PATH = "expiring";

  private final Transcoder transcoder;
  private final Transcoder historyTranscoder;
  private final Transcoder expiringMachineStateTranscoder;
  private final int maxHistoryEntries;

  public AbstractMachineManager(
    CuratorFramework curator,
    SingularityConfiguration configuration,
    MetricRegistry metricRegistry,
    Transcoder transcoder,
    Transcoder historyTranscoder,
    Transcoder expiringMachineStateTranscoder
  ) {
    super(curator, configuration, metricRegistry);
    this.transcoder = transcoder;
    this.historyTranscoder = historyTranscoder;
    this.expiringMachineStateTranscoder = expiringMachineStateTranscoder;
    this.maxHistoryEntries = configuration.getMaxMachineHistoryEntries();
  }

  protected abstract String getRoot();

  private String getHistoryPath(String objectId) {
    return ZKPaths.makePath(getObjectPath(objectId), HISTORY_PATH);
  }

  public List getHistory(String objectId) {
    return getAsyncChildren(getHistoryPath(objectId), historyTranscoder);
  }

  public List getObjects() {
    List fromCache = getObjectsFromLeaderCache();
    if (fromCache != null) {
      return fromCache;
    } else {
      return getObjectsNoCache(getRoot());
    }
  }

  protected abstract List getObjectsFromLeaderCache();

  public List getObjectIds() {
    return getChildren(getRoot());
  }

  public int getNumObjectsAtState(MachineState state) {
    return getObjectsFiltered(state).size();
  }

  public int getNumActive() {
    return getNumObjectsAtState(MachineState.ACTIVE);
  }

  public Map getObjectsByIdForState(MachineState state) {
    List filteredObjects = getObjectsFiltered(state);

    Map filteredObjectIds = Maps.newHashMapWithExpectedSize(
      filteredObjects.size()
    );

    for (T filteredObject : filteredObjects) {
      filteredObjectIds.put(filteredObject.getId(), filteredObject);
    }

    return filteredObjectIds;
  }

  public List getObjectsFiltered(MachineState state) {
    List fromCache = getObjectsFromLeaderCache();
    if (fromCache != null) {
      return fromCache
        .stream()
        .filter(o -> o.getCurrentState().getState() == state)
        .collect(Collectors.toList());
    } else {
      return getObjectsFiltered(Optional.of(state));
    }
  }

  public List getObjectsFiltered(Optional state) {
    List objects = getObjects();

    if (!state.isPresent()) {
      return objects;
    }

    return getObjectsFiltered(objects, state.get());
  }

  private List getObjectsFiltered(List objects, MachineState state) {
    List filtered = Lists.newArrayListWithCapacity(objects.size());

    for (T object : objects) {
      if (object.getCurrentState().getState() == state) {
        filtered.add(object);
      }
    }

    return filtered;
  }

  private String getObjectPath(String objectId) {
    return ZKPaths.makePath(getRoot(), objectId);
  }

  public Optional getObject(String objectId) {
    Optional maybeCached = getObjectFromLeaderCache(objectId);
    if (!maybeCached.isPresent()) {
      return getObjectNoCache(objectId);
    } else {
      return maybeCached;
    }
  }

  protected abstract Optional getObjectFromLeaderCache(String objectId);

  public Optional getObjectNoCache(String objectId) {
    return getData(getObjectPath(objectId), transcoder);
  }

  protected List getObjectsNoCache(String root) {
    return getAsyncChildren(root, transcoder);
  }

  protected abstract void deleteFromLeaderCache(String objectId);

  public enum StateChangeResult {
    FAILURE_NOT_FOUND,
    FAILURE_ALREADY_AT_STATE,
    FAILURE_ILLEGAL_TRANSITION,
    SUCCESS
  }

  public StateChangeResult changeState(
    String objectId,
    MachineState newState,
    Optional message,
    Optional user
  ) {
    Optional maybeObject = getObject(objectId);

    if (!maybeObject.isPresent()) {
      return StateChangeResult.FAILURE_NOT_FOUND;
    }

    final T object = maybeObject.get();

    return changeState(object, newState, message, user);
  }

  public StateChangeResult changeState(
    T object,
    MachineState newState,
    Optional message,
    Optional user
  ) {
    Optional maybeInvalidStateChange = getInvalidStateChangeResult(
      object.getCurrentState().getState(),
      newState,
      false
    );

    if (maybeInvalidStateChange.isPresent()) {
      return maybeInvalidStateChange.get();
    }

    clearExpiringStateChangeIfInvalid(object, newState);

    SingularityMachineStateHistoryUpdate newStateUpdate = new SingularityMachineStateHistoryUpdate(
      object.getId(),
      newState,
      System.currentTimeMillis(),
      user,
      message
    );

    LOG.debug(
      "{} changing state from {} to {} by {}",
      object.getId(),
      object.getCurrentState().getState(),
      newState,
      user
    );

    saveObject(object.changeState(newStateUpdate));

    return StateChangeResult.SUCCESS;
  }

  private void clearExpiringStateChangeIfInvalid(T object, MachineState newState) {
    Optional maybeExpiring = getExpiringObject(
      object.getId()
    );
    if (!maybeExpiring.isPresent()) {
      return;
    }
    MachineState targetExpiringState = maybeExpiring.get().getRevertToState();

    Optional maybeInvalidStateChange = getInvalidStateChangeResult(
      newState,
      targetExpiringState,
      true
    );

    if (maybeInvalidStateChange.isPresent()) {
      LOG.info(
        "Cannot complete expiring state transition from {} to {}, removing expiring action for {}",
        newState,
        targetExpiringState,
        object.getId()
      );
      deleteExpiringObject(object.getId());
    }
  }

  private Optional getInvalidStateChangeResult(
    MachineState currentState,
    MachineState newState,
    boolean expiringAction
  ) {
    if (currentState == newState) {
      return Optional.of(StateChangeResult.FAILURE_ALREADY_AT_STATE);
    }

    if (
      newState == MachineState.STARTING_DECOMMISSION && currentState.isDecommissioning()
    ) {
      return Optional.of(StateChangeResult.FAILURE_ILLEGAL_TRANSITION);
    }

    // can't jump from FROZEN or ACTIVE to DECOMMISSIONING or DECOMMISSIONED
    if (
      (
        (newState == MachineState.DECOMMISSIONING) ||
        (newState == MachineState.DECOMMISSIONED)
      ) &&
      (currentState == MachineState.FROZEN || currentState == MachineState.ACTIVE)
    ) {
      return Optional.of(StateChangeResult.FAILURE_ILLEGAL_TRANSITION);
    }

    // can't jump from a decommissioning state to FROZEN
    if ((newState == MachineState.FROZEN) && currentState.isDecommissioning()) {
      return Optional.of(StateChangeResult.FAILURE_ILLEGAL_TRANSITION);
    }

    // User/Expiring can't jump from inactive to active state
    if (currentState.isInactive() && expiringAction) {
      return Optional.of(StateChangeResult.FAILURE_ILLEGAL_TRANSITION);
    }

    return Optional.empty();
  }

  private String getHistoryUpdatePath(
    SingularityMachineStateHistoryUpdate historyUpdate
  ) {
    final String historyChildPath = String.format(
      "%s-%s",
      historyUpdate.getState().name(),
      historyUpdate.getTimestamp()
    );

    return ZKPaths.makePath(
      getHistoryPath(historyUpdate.getObjectId()),
      historyChildPath
    );
  }

  public void clearOldHistory(String machineId) {
    List histories = getHistory(machineId);
    histories.sort(
      Comparator
        .comparingLong(SingularityMachineStateHistoryUpdate::getTimestamp)
        .reversed()
    );
    histories
      .stream()
      .skip(maxHistoryEntries)
      .forEach(
        history -> {
          delete(getHistoryUpdatePath(history));
        }
      );
  }

  private SingularityCreateResult saveHistoryUpdate(
    SingularityMachineStateHistoryUpdate historyUpdate
  ) {
    return create(getHistoryUpdatePath(historyUpdate), historyUpdate, historyTranscoder);
  }

  public SingularityDeleteResult deleteObject(String objectId) {
    deleteFromLeaderCache(objectId);
    return delete(getObjectPath(objectId));
  }

  public void saveObject(T object) {
    saveHistoryUpdate(object.getCurrentState());
    save(getObjectPath(object.getId()), object, transcoder);
    saveObjectToLeaderCache(object);
  }

  protected abstract void saveObjectToLeaderCache(T object);

  private String getExpiringPath(String machineId) {
    return ZKPaths.makePath(getRoot(), EXPIRING_PATH, machineId);
  }

  public List getExpiringObjects() {
    return getAsyncChildren(
      ZKPaths.makePath(getRoot(), EXPIRING_PATH),
      expiringMachineStateTranscoder
    );
  }

  public Optional getExpiringObject(String machineId) {
    return getData(getExpiringPath(machineId), expiringMachineStateTranscoder);
  }

  public SingularityCreateResult saveExpiringObject(
    SingularityExpiringMachineState expiringMachineState,
    String machineId
  ) {
    return save(
      getExpiringPath(machineId),
      expiringMachineState,
      expiringMachineStateTranscoder
    );
  }

  public SingularityDeleteResult deleteExpiringObject(String machineId) {
    return delete(getExpiringPath(machineId));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy