ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nc...@apache.org
Subject [26/37] ambari git commit: AMBARI-21450. Initial cherry-picking for feature branch (ncole)
Date Thu, 13 Jul 2017 19:14:44 GMT
http://git-wip-us.apache.org/repos/asf/ambari/blob/48f7fb22/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContext.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContext.java b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContext.java
index b97dc80..d2c0ea2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContext.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContext.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -17,31 +17,70 @@
  */
 package org.apache.ambari.server.state;
 
-import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.VERSION;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_DIRECTION;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_FAIL_ON_CHECK_WARNINGS;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_HOST_ORDERED_HOSTS;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_PACK;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_REVERT_UPGRADE_ID;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_SKIP_FAILURES;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_SKIP_MANUAL_VERIFICATION;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES;
+import static org.apache.ambari.server.controller.internal.UpgradeResourceProvider.UPGRADE_TYPE;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.ambari.annotations.Experimental;
 import org.apache.ambari.annotations.ExperimentalFeature;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.ServiceNotFoundException;
 import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
 import org.apache.ambari.server.agent.ExecutionCommand.KeyNames;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider;
+import org.apache.ambari.server.controller.internal.PreUpgradeCheckResourceProvider;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.UpgradeDAO;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
+import org.apache.ambari.server.orm.entities.UpgradeHistoryEntity;
 import org.apache.ambari.server.stack.MasterHostResolver;
 import org.apache.ambari.server.stageplanner.RoleGraphFactory;
+import org.apache.ambari.server.state.repository.VersionDefinitionXml;
+import org.apache.ambari.server.state.stack.PrereqCheckStatus;
 import org.apache.ambari.server.state.stack.UpgradePack;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
 import org.apache.ambari.server.state.stack.upgrade.Grouping;
+import org.apache.ambari.server.state.stack.upgrade.HostOrderGrouping;
+import org.apache.ambari.server.state.stack.upgrade.HostOrderItem;
+import org.apache.ambari.server.state.stack.upgrade.HostOrderItem.HostOrderActionType;
 import org.apache.ambari.server.state.stack.upgrade.UpgradeScope;
 import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Objects;
 import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 import com.google.inject.Inject;
@@ -49,11 +88,14 @@ import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 
 /**
- * Used to hold various helper objects required to process an upgrade pack.
+ * The {@link UpgradeContext} is used to hold all information pertaining to an
+ * upgrade. It is initialized directly from an existing {@link UpgradeEntity} or
+ * from a request to create an upgrade/downgrade.
  */
 public class UpgradeContext {
 
-  public static final String COMMAND_PARAM_VERSION = VERSION;
+  private static final Logger LOG = LoggerFactory.getLogger(UpgradeContext.class);
+
   public static final String COMMAND_PARAM_CLUSTER_NAME = "clusterName";
   public static final String COMMAND_PARAM_DIRECTION = "upgrade_direction";
   public static final String COMMAND_PARAM_UPGRADE_PACK = "upgrade_pack";
@@ -62,21 +104,6 @@ public class UpgradeContext {
   public static final String COMMAND_PARAM_UPGRADE_TYPE = "upgrade_type";
   public static final String COMMAND_PARAM_TASKS = "tasks";
   public static final String COMMAND_PARAM_STRUCT_OUT = "structured_out";
-  public static final String COMMAND_DOWNGRADE_FROM_VERSION = "downgrade_from_version";
-
-  /**
-   * The original "current" stack of the cluster before the upgrade started.
-   * This is the same regardless of whether the current direction is
-   * {@link Direction#UPGRADE} or {@link Direction#DOWNGRADE}.
-   */
-  public static final String COMMAND_PARAM_ORIGINAL_STACK = "original_stack";
-
-  /**
-   * The target upgrade stack before the upgrade started. This is the same
-   * regardless of whether the current direction is {@link Direction#UPGRADE} or
-   * {@link Direction#DOWNGRADE}.
-   */
-  public static final String COMMAND_PARAM_TARGET_STACK = "target_stack";
 
   /**
    * The cluster that the upgrade is for.
@@ -94,31 +121,42 @@ public class UpgradeContext {
   final private UpgradeType m_type;
 
   /**
-   * The request parameters from the REST API for creating this upgrade.
+   * The upgrade pack for this upgrade.
    */
-  final private Map<String, Object> m_upgradeRequestMap;
+  private UpgradePack m_upgradePack;
 
   /**
-   * The upgrade pack for this upgrade.
+   * Upgrades will always have a single version being upgraded to and downgrades
+   * will have a single version being downgraded from. This repository
+   * represents that version.
+   * <p/>
+   * When the direction is {@link Direction#UPGRADE}, this represents the target
+   * repository. <br/>
+   * When the direction is {@link Direction#DOWNGRADE}, this represents the
+   * repository being downgraded from.
    */
-  private UpgradePack m_upgradePack;
+  private final RepositoryVersionEntity m_repositoryVersion;
 
   /**
-   * The source of the upgrade/downgrade.
+   * Resolves master components on hosts.
    */
-  private final RepositoryVersionEntity m_fromRepositoryVersion;
+  private final MasterHostResolver m_resolver;
 
   /**
-   * The target of the upgrade/downgrade.
+   * A collection of hosts in the cluster which are unhealthy and will not
+   * participate in the upgrade.
    */
-  private final RepositoryVersionEntity m_toRepositoryVersion;
+  private final List<ServiceComponentHost> m_unhealthy = new ArrayList<>();
 
+  /**
+   * Mapping of service name to display name.
+   */
+  private final Map<String, String> m_serviceNames = new HashMap<>();
 
-  private MasterHostResolver m_resolver;
-  private AmbariMetaInfo m_metaInfo;
-  private List<ServiceComponentHost> m_unhealthy = new ArrayList<>();
-  private Map<String, String> m_serviceNames = new HashMap<>();
-  private Map<String, String> m_componentNames = new HashMap<>();
+  /**
+   * Mapping of component name to display name.
+   */
+  private final Map<String, String> m_componentNames = new HashMap<>();
 
   /**
    * {@code true} if slave/client component failures should be automatically
@@ -139,9 +177,27 @@ public class UpgradeContext {
    */
   private boolean m_autoSkipManualVerification = false;
 
-  private Set<String> m_supported = new HashSet<>();
+  /**
+   * A set of services which are included in this upgrade. If this is empty,
+   * then all cluster services are included.
+   */
+  private Set<String> m_services = new HashSet<>();
 
-  private UpgradeScope m_scope = UpgradeScope.ANY;
+  /**
+   * A mapping of service to target repository. On an upgrade, this will be the
+   * same for all services. On a downgrade, this may be different for each
+   * service depending on which repository the service was on before the failed
+   * upgrade.
+   */
+  private final Map<String, RepositoryVersionEntity> m_targetRepositoryMap = new HashMap<>();
+
+  /**
+   * A mapping of service to source (from) repository. On an upgrade, this will
+   * be the current desired repository of every service. When downgrading, this
+   * will be the same for all components and will represent the value returned
+   * from {@link #getRepositoryVersion()}.
+   */
+  private final Map<String, RepositoryVersionEntity> m_sourceRepositoryMap = new HashMap<>();
 
   /**
    * Used by some {@link Grouping}s to generate commands. It is exposed here
@@ -164,29 +220,249 @@ public class UpgradeContext {
   private Gson m_gson;
 
   /**
-   * Constructor.
-   *
-   * @param cluster
-   *          the cluster that the upgrade is for
-   * @param type
-   *          the type of upgrade, either rolling or non_rolling
-   * @param direction
-   *          the direction for the upgrade
-   * @param upgradeRequestMap
-   *          the original map of paramters used to create the upgrade
+   * Used for looking up information about components and services.
+   */
+  @Inject
+  private AmbariMetaInfo m_metaInfo;
+
+  /**
+   * Used to suggest upgrade packs during creation of an upgrade context.
+   */
+  @Inject
+  private UpgradeHelper m_upgradeHelper;
+
+  /**
+   * Used to lookup the repository version from an ID.
+   */
+  @Inject
+  private RepositoryVersionDAO m_repoVersionDAO;
+
+  /**
+   * Used to lookup a prior upgrade by ID.
    */
+  @Inject
+  private UpgradeDAO m_upgradeDAO;
+
+  /**
+   * Used as a quick way to tell if the upgrade is to revert a patch.
+   */
+  private boolean m_isRevert = false;
+
+  /**
+   * Defines orchestration type.  This is not the repository type when reverting a patch.
+   */
+  private RepositoryType m_orchestration = RepositoryType.STANDARD;
+
   @AssistedInject
-  public UpgradeContext(@Assisted Cluster cluster, @Assisted UpgradeType type,
-      @Assisted Direction direction,
-      @Assisted("fromRepositoryVersion") RepositoryVersionEntity fromRepositoryVersion,
-      @Assisted("toRepositoryVersion") RepositoryVersionEntity toRepositoryVersion,
-      @Assisted Map<String, Object> upgradeRequestMap) {
+  public UpgradeContext(@Assisted Cluster cluster,
+      @Assisted Map<String, Object> upgradeRequestMap, Gson gson, UpgradeHelper upgradeHelper,
+      UpgradeDAO upgradeDAO, RepositoryVersionDAO repoVersionDAO, ConfigHelper configHelper)
+      throws AmbariException {
+    // injected constructor dependencies
+    m_gson = gson;
+    m_upgradeHelper = upgradeHelper;
+    m_upgradeDAO = upgradeDAO;
+    m_repoVersionDAO = repoVersionDAO;
+
     m_cluster = cluster;
-    m_type = type;
-    m_direction = direction;
-    m_fromRepositoryVersion = fromRepositoryVersion;
-    m_toRepositoryVersion = toRepositoryVersion;
-    m_upgradeRequestMap = upgradeRequestMap;
+
+    // determine upgrade type (default is ROLLING)
+    String upgradeTypeProperty = (String) upgradeRequestMap.get(UPGRADE_TYPE);
+    if (StringUtils.isNotBlank(upgradeTypeProperty)) {
+      try {
+        m_type = UpgradeType.valueOf(upgradeRequestMap.get(UPGRADE_TYPE).toString());
+      } catch (Exception e) {
+        throw new AmbariException(String.format("Property %s has an incorrect value of %s.",
+            UPGRADE_TYPE, upgradeTypeProperty));
+      }
+    } else {
+      // default type
+      m_type= UpgradeType.ROLLING;
+    }
+
+    m_isRevert = upgradeRequestMap.containsKey(UPGRADE_REVERT_UPGRADE_ID);
+
+    if (m_isRevert) {
+      Long revertUpgradeId = Long.valueOf(upgradeRequestMap.get(UPGRADE_REVERT_UPGRADE_ID).toString());
+      UpgradeEntity revertUpgrade = m_upgradeDAO.findUpgrade(revertUpgradeId);
+
+      if (null == revertUpgrade) {
+          throw new AmbariException(String.format("Could not find Upgrade with id %s to revert.", revertUpgradeId));
+      }      
+      
+      if (revertUpgrade.getOrchestration() != RepositoryType.PATCH) {
+        throw new AmbariException("Can only revert upgrades that have been done as a patch.");
+      }
+
+      if (revertUpgrade.getDirection() != Direction.UPGRADE) {
+        throw new AmbariException("Can only revert successful upgrades, not downgrades.");
+      }
+
+      Set<RepositoryVersionEntity> priors = new HashSet<>();
+      for (UpgradeHistoryEntity history : revertUpgrade.getHistory()) {
+        priors.add(history.getFromReposistoryVersion());
+
+        // !!! build all service-specific
+        m_services.add(history.getServiceName());
+        m_sourceRepositoryMap.put(history.getServiceName(), history.getTargetRepositoryVersion());
+        m_targetRepositoryMap.put(history.getServiceName(), history.getFromReposistoryVersion());
+      }
+
+      if (priors.size() != 1) {
+        String message = String.format("Upgrade from %s could not be reverted as there is no single "
+            + " repository across services.", revertUpgrade.getRepositoryVersion().getVersion());
+
+        throw new AmbariException(message);
+      }
+
+      m_repositoryVersion = priors.iterator().next();
+
+      // !!! the version is used later in validators
+      upgradeRequestMap.put(UPGRADE_REPO_VERSION_ID, m_repositoryVersion.getId().toString());
+      // !!! use the same upgrade pack that was used in the upgrade being reverted
+      upgradeRequestMap.put(UPGRADE_PACK, revertUpgrade.getUpgradePackage());
+
+      // !!! direction can ONLY be an downgrade on revert
+      m_direction = Direction.DOWNGRADE;
+      m_orchestration = RepositoryType.PATCH;
+    } else {
+
+      // determine direction
+      String directionProperty = (String) upgradeRequestMap.get(UPGRADE_DIRECTION);
+      if (StringUtils.isEmpty(directionProperty)) {
+        throw new AmbariException(String.format("%s is required", UPGRADE_DIRECTION));
+      }
+
+      m_direction = Direction.valueOf(directionProperty);
+
+      // depending on the direction, we must either have a target repository or an upgrade we are downgrading from
+      switch(m_direction){
+        case UPGRADE:{
+          String repositoryVersionId = (String) upgradeRequestMap.get(UPGRADE_REPO_VERSION_ID);
+          if (null == repositoryVersionId) {
+            throw new AmbariException(
+                String.format("The property %s is required when the upgrade direction is %s",
+                    UPGRADE_REPO_VERSION_ID, m_direction));
+          }
+
+          // depending on the repository, add services
+          m_repositoryVersion = m_repoVersionDAO.findByPK(Long.valueOf(repositoryVersionId));
+          m_orchestration = m_repositoryVersion.getType();
+
+          if (m_orchestration == RepositoryType.STANDARD) {
+            m_services.addAll(cluster.getServices().keySet());
+          } else {
+            try {
+              VersionDefinitionXml vdf = m_repositoryVersion.getRepositoryXml();
+              m_services.addAll(vdf.getAvailableServiceNames());
+
+              // if this is every true, then just stop the upgrade attempt and
+              // throw an exception
+              if (m_services.isEmpty()) {
+                String message = String.format(
+                    "When using a VDF of type %s, the available services must be defined in the VDF",
+                    m_repositoryVersion.getType());
+
+                throw new AmbariException(message);
+              }
+
+            } catch (Exception e) {
+              String msg = String.format(
+                  "Could not parse version definition for %s.  Upgrade will not proceed.",
+                  m_repositoryVersion.getVersion());
+
+              throw new AmbariException(msg);
+            }
+          }
+
+          Set<String> installedServices = new HashSet<>();
+          // populate the target repository map for all services in the upgrade
+          for (String serviceName : m_services) {
+            try {
+              Service service = cluster.getService(serviceName);
+              m_sourceRepositoryMap.put(serviceName, service.getDesiredRepositoryVersion());
+              m_targetRepositoryMap.put(serviceName, m_repositoryVersion);
+              installedServices.add(serviceName);
+            } catch (ServiceNotFoundException e) {
+              LOG.warn("Skipping orchestraction for service {}, as it was defined to upgrade, but is not installed in cluster {}",
+                  serviceName, cluster.getClusterName());
+            }
+          }
+
+          m_services = installedServices;
+
+          break;
+        }
+        case DOWNGRADE:{
+          UpgradeEntity upgrade = m_upgradeDAO.findLastUpgradeForCluster(
+              cluster.getClusterId(), Direction.UPGRADE);
+
+          m_repositoryVersion = upgrade.getRepositoryVersion();
+
+          // populate the repository maps for all services in the upgrade
+          for (UpgradeHistoryEntity history : upgrade.getHistory()) {
+            m_services.add(history.getServiceName());
+            m_sourceRepositoryMap.put(history.getServiceName(), m_repositoryVersion);
+            m_targetRepositoryMap.put(history.getServiceName(), history.getFromReposistoryVersion());
+          }
+
+          break;
+        }
+        default:
+          m_repositoryVersion = null;
+          break;
+      }
+    }
+
+
+    /**
+     * For the unit tests tests, there are multiple upgrade packs for the same
+     * type, so allow picking one of them. In prod, this is empty.
+     */
+    String preferredUpgradePackName = (String) upgradeRequestMap.get(UPGRADE_PACK);
+
+    @Experimental(feature = ExperimentalFeature.PATCH_UPGRADES, comment="This is wrong")
+    String upgradePackFromVersion = cluster.getService(
+        m_services.iterator().next()).getDesiredRepositoryVersion().getVersion();
+
+    m_upgradePack = m_upgradeHelper.suggestUpgradePack(m_cluster.getClusterName(),
+        upgradePackFromVersion, m_repositoryVersion.getVersion(), m_direction, m_type,
+        preferredUpgradePackName);
+
+    // the validator will throw an exception if the upgrade request is not valid
+    UpgradeRequestValidator upgradeRequestValidator = buildValidator(m_type);
+    upgradeRequestValidator.validate(cluster, m_direction, m_type, m_upgradePack,
+        upgradeRequestMap);
+
+    // optionally skip failures - this can be supplied on either the request or
+    // in the upgrade pack explicitely, however the request will always override
+    // the upgrade pack if explicitely specified
+    boolean skipComponentFailures = m_upgradePack.isComponentFailureAutoSkipped();
+    boolean skipServiceCheckFailures = m_upgradePack.isServiceCheckFailureAutoSkipped();
+
+    // only override the upgrade pack if set on the request
+    if (upgradeRequestMap.containsKey(UPGRADE_SKIP_FAILURES)) {
+      skipComponentFailures = Boolean.parseBoolean(
+          (String) upgradeRequestMap.get(UPGRADE_SKIP_FAILURES));
+    }
+
+    // only override the upgrade pack if set on the request
+    if (upgradeRequestMap.containsKey(UPGRADE_SKIP_SC_FAILURES)) {
+      skipServiceCheckFailures = Boolean.parseBoolean(
+          (String) upgradeRequestMap.get(UPGRADE_SKIP_SC_FAILURES));
+    }
+
+    boolean skipManualVerification = false;
+    if (upgradeRequestMap.containsKey(UPGRADE_SKIP_MANUAL_VERIFICATION)) {
+      skipManualVerification = Boolean.parseBoolean(
+          (String) upgradeRequestMap.get(UPGRADE_SKIP_MANUAL_VERIFICATION));
+    }
+
+    m_autoSkipComponentFailures = skipComponentFailures;
+    m_autoSkipServiceCheckFailures = skipServiceCheckFailures;
+    m_autoSkipManualVerification = skipManualVerification;
+
+    m_resolver = new MasterHostResolver(m_cluster, configHelper, this);
   }
 
   /**
@@ -199,42 +475,38 @@ public class UpgradeContext {
    */
   @AssistedInject
   public UpgradeContext(@Assisted Cluster cluster, @Assisted UpgradeEntity upgradeEntity,
-      AmbariMetaInfo ambariMetaInfo) {
-
+      AmbariMetaInfo ambariMetaInfo, ConfigHelper configHelper) {
     m_metaInfo = ambariMetaInfo;
+
     m_cluster = cluster;
     m_type = upgradeEntity.getUpgradeType();
     m_direction = upgradeEntity.getDirection();
-
-    m_fromRepositoryVersion = upgradeEntity.getFromRepositoryVersion();
-    m_toRepositoryVersion = upgradeEntity.getToRepositoryVersion();
-
-    String upgradePackage = upgradeEntity.getUpgradePackage();
-
-    StackId originalStackId = m_fromRepositoryVersion.getStackId();
-    if (m_direction == Direction.DOWNGRADE) {
-      originalStackId = m_toRepositoryVersion.getStackId();
+    m_repositoryVersion = upgradeEntity.getRepositoryVersion();
+
+    m_autoSkipComponentFailures = upgradeEntity.isComponentFailureAutoSkipped();
+    m_autoSkipServiceCheckFailures = upgradeEntity.isServiceCheckFailureAutoSkipped();
+
+    List<UpgradeHistoryEntity> allHistory = upgradeEntity.getHistory();
+    for (UpgradeHistoryEntity history : allHistory) {
+      String serviceName = history.getServiceName();
+      RepositoryVersionEntity sourceRepositoryVersion = history.getFromReposistoryVersion();
+      RepositoryVersionEntity targetRepositoryVersion = history.getTargetRepositoryVersion();
+      m_sourceRepositoryMap.put(serviceName, sourceRepositoryVersion);
+      m_targetRepositoryMap.put(serviceName, targetRepositoryVersion);
+      m_services.add(serviceName);
     }
 
-    Map<String, UpgradePack> packs = m_metaInfo.getUpgradePacks(originalStackId.getStackName(),
-        originalStackId.getStackVersion());
-
+    @Experimental(feature = ExperimentalFeature.PATCH_UPGRADES, comment = "This is wrong")
+    String upgradePackage = upgradeEntity.getUpgradePackage();
+    StackId stackId = m_repositoryVersion.getStackId();
+    Map<String, UpgradePack> packs = m_metaInfo.getUpgradePacks(stackId.getStackName(), stackId.getStackVersion());
     m_upgradePack = packs.get(upgradePackage);
 
-    // since this constructor is initialized from an entity, then this map is
-    // not present
-    m_upgradeRequestMap = Collections.emptyMap();
-  }
+    m_resolver = new MasterHostResolver(m_cluster, configHelper, this);
+    m_orchestration = upgradeEntity.getOrchestration();
 
-  /**
-   * Gets the original mapping of key/value pairs from the request which created
-   * the upgrade.
-   *
-   * @return the original mapping of key/value pairs from the request which
-   *         created the upgrade.
-   */
-  public Map<String, Object> getUpgradeRequest() {
-    return m_upgradeRequestMap;
+    m_isRevert = upgradeEntity.getOrchestration() == RepositoryType.PATCH &&
+        upgradeEntity.getDirection() == Direction.DOWNGRADE;
   }
 
   /**
@@ -266,104 +538,146 @@ public class UpgradeContext {
   }
 
   /**
-   * @return the target version for the upgrade
+   * Gets the version that components are being considered to be "coming from".
+   * <p/>
+   * With a {@link Direction#UPGRADE}, this value represent the services'
+   * desired repository. However, {@link Direction#DOWNGRADE} will use the same
+   * value for all services which is the version that the downgrade is coming
+   * from.
+   *
+   * @return the source version for the upgrade
    */
-  public RepositoryVersionEntity getTargetRepositoryVersion() {
-    return m_toRepositoryVersion;
+  public Map<String, RepositoryVersionEntity> getSourceVersions() {
+    return new HashMap<>(m_sourceRepositoryMap);
   }
 
   /**
-   * @return the source version for the upgrade
+   * Gets the version that service is being considered to be "coming from".
+   * <p/>
+   * With a {@link Direction#UPGRADE}, this value represent the services'
+   * desired repository. However, {@link Direction#DOWNGRADE} will use the same
+   * value for all services which is the version that the downgrade is coming
+   * from.
+   *
+   * @return the source repository for the upgrade
    */
-  public RepositoryVersionEntity getSourceRepositoryVersion() {
-    return m_fromRepositoryVersion;
+  public RepositoryVersionEntity getSourceRepositoryVersion(String serviceName) {
+    return m_sourceRepositoryMap.get(serviceName);
   }
 
   /**
-   * @return the direction of the upgrade
+   * Gets the version that service is being considered to be "coming from".
+   * <p/>
+   * With a {@link Direction#UPGRADE}, this value represent the services'
+   * desired repository. However, {@link Direction#DOWNGRADE} will use the same
+   * value for all services which is the version that the downgrade is coming
+   * from.
+   *
+   * @return the source repository for the upgrade
+   * @see #getSourceRepositoryVersion(String)
    */
-  public Direction getDirection() {
-    return m_direction;
+  public String getSourceVersion(String serviceName) {
+    RepositoryVersionEntity serviceSourceVersion = m_sourceRepositoryMap.get(serviceName);
+    return serviceSourceVersion.getVersion();
   }
 
   /**
-   * @return the type of upgrade.
+   * Gets the version being upgraded to or downgraded to for all services
+   * participating. This is the version that the service will be on if the
+   * upgrade or downgrade succeeds.
+   * <p/>
+   * With a {@link Direction#UPGRADE}, all services should be targetting the
+   * same repository version. However, {@link Direction#DOWNGRADE} will target
+   * the original repository that the service was on.
+   *
+   * @return the target version for the upgrade
    */
-  public UpgradeType getType() {
-    return m_type;
+  public Map<String, RepositoryVersionEntity> getTargetVersions() {
+    return new HashMap<>(m_targetRepositoryMap);
   }
 
   /**
-   * Sets the host resolver.
+   * Gets the repository being upgraded to or downgraded to for the given
+   * service. This is the version that the service will be on if the upgrade or
+   * downgrade succeeds.
+   * <p/>
+   * With a {@link Direction#UPGRADE}, all services should be targeting the
+   * same repository version. However, {@link Direction#DOWNGRADE} will target
+   * the original repository that the service was on.
    *
-   * @param resolver
-   *          the resolver that also references the required cluster
+   * @return the target repository for the upgrade
    */
-  public void setResolver(MasterHostResolver resolver) {
-    m_resolver = resolver;
+  public RepositoryVersionEntity getTargetRepositoryVersion(String serviceName) {
+    return m_targetRepositoryMap.get(serviceName);
   }
 
   /**
-   * @return the resolver
+   * Gets the version being upgraded to or downgraded to for the given service.
+   * This is the version that the service will be on if the upgrade or downgrade
+   * succeeds.
+   * <p/>
+   * With a {@link Direction#UPGRADE}, all services should be targetting the
+   * same repository version. However, {@link Direction#DOWNGRADE} will target
+   * the original repository that the service was on.
+   *
+   * @return the target version for the upgrade
+   * @see #getTargetRepositoryVersion(String)
    */
-  public MasterHostResolver getResolver() {
-    return m_resolver;
+  public String getTargetVersion(String serviceName) {
+    RepositoryVersionEntity serviceTargetVersion = m_targetRepositoryMap.get(serviceName);
+    return serviceTargetVersion.getVersion();
   }
 
   /**
-   * @return the metainfo for access to service definitions
+   * @return the direction of the upgrade
    */
-  public AmbariMetaInfo getAmbariMetaInfo() {
-    return m_metaInfo;
+  public Direction getDirection() {
+    return m_direction;
   }
 
   /**
-   * @param metaInfo the metainfo for access to service definitions
+   * @return the type of upgrade.
    */
-  public void setAmbariMetaInfo(AmbariMetaInfo metaInfo) {
-    m_metaInfo = metaInfo;
+  public UpgradeType getType() {
+    return m_type;
   }
 
   /**
-   * @param unhealthy a list of unhealthy host components
+   * @return the resolver
    */
-  public void addUnhealthy(List<ServiceComponentHost> unhealthy) {
-    m_unhealthy.addAll(unhealthy);
+  public MasterHostResolver getResolver() {
+    return m_resolver;
   }
 
   /**
-   * Gets the target stack of the upgrade based on the target repository which
-   * finalization will set.
-   *
-   * @return the target stack (never {@code null}).
+   * @return the metainfo for access to service definitions
    */
-  public StackId getTargetStackId() {
-    return m_toRepositoryVersion.getStackId();
+  public AmbariMetaInfo getAmbariMetaInfo() {
+    return m_metaInfo;
   }
 
   /**
-   * Gets the current stack of the components participating in the upgrade.
-   *
-   * @return the source stack (never {@code null}).
+   * @param unhealthy a list of unhealthy host components
    */
-  public StackId getSourceStackId() {
-    return m_fromRepositoryVersion.getStackId();
+  public void addUnhealthy(List<ServiceComponentHost> unhealthy) {
+    m_unhealthy.addAll(unhealthy);
   }
 
   /**
-   * Gets the original stack ID that the cluster was on. For an upgrade, this
-   * returns the source stack ID. For a downgrade, this will return the target
-   * stack ID.
+   * Gets the single repository version for the upgrade depending on the
+   * direction.
+   * <p/>
+   * If the direction is {@link Direction#UPGRADE} then this will return the
+   * target repository which every service will be on if the upgrade is
+   * finalized. <br/>
+   * If the direction is {@link Direction#DOWNGRADE} then this will return the
+   * repository from which the downgrade is coming from.
    *
-   * @return the original stack ID.
+   * @return the target repository version for this upgrade (never
+   *         {@code null}).
    */
-  public StackId getOriginalStackId() {
-    StackId originalStackId = getSourceStackId();
-    if (m_direction == Direction.DOWNGRADE) {
-      originalStackId = getTargetStackId();
-    }
-
-    return originalStackId;
+  public RepositoryVersionEntity getRepositoryVersion() {
+    return m_repositoryVersion;
   }
 
   /**
@@ -417,17 +731,6 @@ public class UpgradeContext {
   }
 
   /**
-   * Sets whether skippable components that failed are automatically skipped.
-   *
-   * @param autoSkipComponentFailures
-   *          {@code true} to automatically skip component failures which are
-   *          marked as skippable.
-   */
-  public void setAutoSkipComponentFailures(boolean autoSkipComponentFailures) {
-    m_autoSkipComponentFailures = autoSkipComponentFailures;
-  }
-
-  /**
    * Gets whether skippable service checks that failed are automatically
    * skipped.
    *
@@ -438,18 +741,6 @@ public class UpgradeContext {
   }
 
   /**
-   * Sets whether skippable service checks that failed are automatically
-   * skipped.
-   *
-   * @param autoSkipServiceCheckFailures
-   *          {@code true} to automatically skip service check failures which
-   *          are marked as being skippable.
-   */
-  public void setAutoSkipServiceCheckFailures(boolean autoSkipServiceCheckFailures) {
-    m_autoSkipServiceCheckFailures = autoSkipServiceCheckFailures;
-  }
-
-  /**
    * Gets whether manual verification tasks can be automatically skipped.
    *
    * @return the skipManualVerification
@@ -459,49 +750,43 @@ public class UpgradeContext {
   }
 
   /**
-   * Sets whether manual verification checks are automatically skipped.
+   * Gets the services participating in the upgrade.
    *
-   * @param autoSkipManualVerification
-   *          {@code true} to automatically skip manual verification tasks.
-   */
-  public void setAutoSkipManualVerification(boolean autoSkipManualVerification) {
-    m_autoSkipManualVerification = autoSkipManualVerification;
-  }
-
-  /**
-   * Sets the service names that are supported by an upgrade.  This is used for
-   * {@link RepositoryType#PATCH} and {@link RepositoryType#SERVICE}.
-   *
-   * @param services  the set of specific services
+   * @return the set of supported services. This collection should never be
+   *         empty.
    */
   @Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
-  public void setSupportedServices(Set<String> services) {
-    m_supported = services;
+  public Set<String> getSupportedServices() {
+    return Collections.unmodifiableSet(m_services);
   }
 
   /**
-   * Gets if a service is supported.  If there are no services marked for the context,
-   * then ALL services are supported
-   * @param serviceName the service name to check.
+   * Gets if a service is supported.
+   *
+   * @param serviceName
+   *          the service name to check.
    * @return {@code true} when the service is supported
    */
   @Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
   public boolean isServiceSupported(String serviceName) {
-    if (m_supported.isEmpty() || m_supported.contains(serviceName)) {
+    return m_services.contains(serviceName);
+  }
+
+  @Experimental(feature = ExperimentalFeature.PATCH_UPGRADES)
+  public boolean isScoped(UpgradeScope scope) {
+    if (scope == UpgradeScope.ANY) {
       return true;
     }
 
-    return false;
-  }
-
-  @Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
-  public void setScope(UpgradeScope scope) {
-    m_scope = scope;
-  }
+    switch (m_orchestration) {
+      case PATCH:
+      case SERVICE:
+        return scope == UpgradeScope.PARTIAL;
+      case STANDARD:
+        return scope == UpgradeScope.COMPLETE;
+    }
 
-  @Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
-  public boolean isScoped(UpgradeScope scope) {
-    return m_scope.isScoped(scope);
+    return false;
   }
 
   /**
@@ -523,6 +808,18 @@ public class UpgradeContext {
   }
 
   /**
+   * Gets the repository type to determine if this upgrade is a complete upgrade
+   * or a service/patch.  This value is not always the same as the repository version.  In
+   * the case of a revert of a patch, the target repository may be of type STANDARD, but orchestration
+   * must be "like a patch".
+   *
+   * @return the orchestration type.
+   */
+  public RepositoryType getOrchestrationType() {
+    return m_orchestration;
+  }
+
+  /**
    * Gets a map initialized with parameters required for upgrades to work. The
    * following properties are already set:
    * <ul>
@@ -557,4 +854,350 @@ public class UpgradeContext {
     parameters.put(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION, "true");
     return parameters;
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this)
+        .add("direction", m_direction)
+        .add("type", m_type)
+        .add("target", m_repositoryVersion).toString();
+  }
+
+  /**
+   * Gets whether a downgrade is allowed for this upgrade. If the direction is
+   * {@link Direction#DOWNGRADE}, then this method always returns false.
+   * Otherwise it will consule {@link UpgradePack#isDowngradeAllowed()}.
+   *
+   * @return {@code true} of a downgrade is allowed for this upgrade,
+   *         {@code false} otherwise.
+   */
+  public boolean isDowngradeAllowed() {
+    if (m_direction == Direction.DOWNGRADE) {
+      return false;
+    }
+
+    return m_upgradePack.isDowngradeAllowed();
+  }
+
+  /**
+   * @return
+   */
+  public boolean isPatchRevert() {
+    return m_isRevert;
+  }
+
+  /**
+   * Builds a chain of {@link UpgradeRequestValidator}s to ensure that the
+   * incoming request to create a new upgrade is valid.
+   *
+   * @param upgradeType
+   *          the type of upgrade to build the validator for.
+   * @return the validator which can check to ensure that the properties are
+   *         valid.
+   */
+  private UpgradeRequestValidator buildValidator(UpgradeType upgradeType){
+    UpgradeRequestValidator validator = new BasicUpgradePropertiesValidator();
+    UpgradeRequestValidator preReqValidator = new PreReqCheckValidator();
+    validator.setNextValidator(preReqValidator);
+
+    final UpgradeRequestValidator upgradeTypeValidator;
+    switch (upgradeType) {
+      case HOST_ORDERED:
+        upgradeTypeValidator = new HostOrderedUpgradeValidator();
+        break;
+      case NON_ROLLING:
+      case ROLLING:
+      default:
+        upgradeTypeValidator = null;
+        break;
+    }
+
+    preReqValidator.setNextValidator(upgradeTypeValidator);
+    return validator;
+  }
+
+  /**
+   * The {@link UpgradeRequestValidator} contains the logic to check for correct
+   * upgrade request properties and then pass the responsibility onto the next
+   * validator in the chain.
+   */
+  private abstract class UpgradeRequestValidator {
+    /**
+     * The next validator.
+     */
+    UpgradeRequestValidator m_nextValidator;
+
+    /**
+     * Sets the next validator in the chain.
+     *
+     * @param nextValidator
+     *          the next validator to run, or {@code null} for none.
+     */
+    void setNextValidator(UpgradeRequestValidator nextValidator) {
+      m_nextValidator = nextValidator;
+    }
+
+    /**
+     * Validates the upgrade request from this point in the chain.
+     *
+     * @param cluster
+     * @param direction
+     * @param type
+     * @param upgradePack
+     * @param requestMap
+     * @throws AmbariException
+     */
+    final void validate(Cluster cluster, Direction direction, UpgradeType type,
+        UpgradePack upgradePack, Map<String, Object> requestMap) throws AmbariException {
+
+      // run this instance's check
+      check(cluster, direction, type, upgradePack, requestMap);
+
+      // pass along to the next
+      if (null != m_nextValidator) {
+        m_nextValidator.validate(cluster, direction, type, upgradePack, requestMap);
+      }
+    }
+
+    /**
+     * Checks to ensure that upgrade request is valid given the specific
+     * arguments.
+     *
+     * @param cluster
+     * @param direction
+     * @param type
+     * @param upgradePack
+     * @param requestMap
+     * @throws AmbariException
+     */
+    abstract void check(Cluster cluster, Direction direction, UpgradeType type,
+        UpgradePack upgradePack, Map<String, Object> requestMap) throws AmbariException;
+  }
+
+  /**
+   * The {@link BasicUpgradePropertiesValidator} ensures that the basic required
+   * properties are present on the upgrade request.
+   */
+  private final class BasicUpgradePropertiesValidator extends UpgradeRequestValidator {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void check(Cluster cluster, Direction direction, UpgradeType type,
+        UpgradePack upgradePack, Map<String, Object> requestMap) throws AmbariException {
+
+      if (direction == Direction.UPGRADE) {
+        String repositoryVersionId = (String) requestMap.get(UPGRADE_REPO_VERSION_ID);
+        if (StringUtils.isBlank(repositoryVersionId)) {
+          throw new AmbariException(
+              String.format("%s is required for upgrades", UPGRADE_REPO_VERSION_ID));
+        }
+      }
+    }
+  }
+
+  /**
+   * The {@link PreReqCheckValidator} ensures that the upgrade pre-requisite
+   * checks have passed.
+   */
+  private final class PreReqCheckValidator extends UpgradeRequestValidator {
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void check(Cluster cluster, Direction direction, UpgradeType type, UpgradePack upgradePack,
+        Map<String, Object> requestMap) throws AmbariException {
+
+      String repositoryVersionId = (String) requestMap.get(UPGRADE_REPO_VERSION_ID);
+      boolean skipPrereqChecks = Boolean.parseBoolean((String) requestMap.get(UPGRADE_SKIP_PREREQUISITE_CHECKS));
+      boolean failOnCheckWarnings = Boolean.parseBoolean((String) requestMap.get(UPGRADE_FAIL_ON_CHECK_WARNINGS));
+      String preferredUpgradePack = requestMap.containsKey(UPGRADE_PACK) ? (String) requestMap.get(UPGRADE_PACK) : null;
+
+      // verify that there is not an upgrade or downgrade that is in progress or suspended
+      UpgradeEntity existingUpgrade = cluster.getUpgradeInProgress();
+      if (null != existingUpgrade) {
+        throw new AmbariException(
+            String.format("Unable to perform %s as another %s (request ID %s) is in progress.",
+                direction.getText(false), existingUpgrade.getDirection().getText(false),
+                existingUpgrade.getRequestId()));
+      }
+
+      // skip this check if it's a downgrade or we are instructed to skip it
+      if (direction.isDowngrade() || skipPrereqChecks) {
+        return;
+      }
+
+      RepositoryVersionEntity repositoryVersion = m_repoVersionDAO.findByPK(
+          Long.valueOf(repositoryVersionId));
+
+      // Validate pre-req checks pass
+      PreUpgradeCheckResourceProvider provider = (PreUpgradeCheckResourceProvider) AbstractControllerResourceProvider.getResourceProvider(
+          Resource.Type.PreUpgradeCheck);
+
+      Predicate preUpgradeCheckPredicate = new PredicateBuilder().property(
+          PreUpgradeCheckResourceProvider.UPGRADE_CHECK_CLUSTER_NAME_PROPERTY_ID).equals(cluster.getClusterName()).and().property(
+          PreUpgradeCheckResourceProvider.UPGRADE_CHECK_REPOSITORY_VERSION_PROPERTY_ID).equals(repositoryVersion.getVersion()).and().property(
+          PreUpgradeCheckResourceProvider.UPGRADE_CHECK_FOR_REVERT_PROPERTY_ID).equals(m_isRevert).and().property(
+          PreUpgradeCheckResourceProvider.UPGRADE_CHECK_UPGRADE_TYPE_PROPERTY_ID).equals(type).and().property(
+          PreUpgradeCheckResourceProvider.UPGRADE_CHECK_UPGRADE_PACK_PROPERTY_ID).equals(preferredUpgradePack).toPredicate();
+
+      Request preUpgradeCheckRequest = PropertyHelper.getReadRequest();
+
+      Set<Resource> preUpgradeCheckResources;
+      try {
+        preUpgradeCheckResources = provider.getResources(
+            preUpgradeCheckRequest, preUpgradeCheckPredicate);
+      } catch (NoSuchResourceException|SystemException|UnsupportedPropertyException|NoSuchParentResourceException e) {
+        throw new AmbariException(
+            String.format("Unable to perform %s. Prerequisite checks could not be run",
+                direction.getText(false), e));
+      }
+
+      List<Resource> failedResources = new LinkedList<>();
+      if (preUpgradeCheckResources != null) {
+        for (Resource res : preUpgradeCheckResources) {
+          PrereqCheckStatus prereqCheckStatus = (PrereqCheckStatus) res.getPropertyValue(
+              PreUpgradeCheckResourceProvider.UPGRADE_CHECK_STATUS_PROPERTY_ID);
+
+          if (prereqCheckStatus == PrereqCheckStatus.FAIL
+              || (failOnCheckWarnings && prereqCheckStatus == PrereqCheckStatus.WARNING)) {
+            failedResources.add(res);
+          }
+        }
+      }
+
+      if (!failedResources.isEmpty()) {
+        throw new AmbariException(
+            String.format("Unable to perform %s. Prerequisite checks failed %s",
+                direction.getText(false), m_gson.toJson(failedResources)));
+      }
+    }
+  }
+
+  /**
+   * Ensures that for {@link UpgradeType#HOST_ORDERED}, the properties supplied
+   * are valid.
+   */
+  @SuppressWarnings("unchecked")
+  private final class HostOrderedUpgradeValidator extends UpgradeRequestValidator {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void check(Cluster cluster, Direction direction, UpgradeType type, UpgradePack upgradePack,
+        Map<String, Object> requestMap) throws AmbariException {
+
+      String skipFailuresRequestProperty = (String) requestMap.get(UPGRADE_SKIP_FAILURES);
+      if (Boolean.parseBoolean(skipFailuresRequestProperty)) {
+        throw new AmbariException(
+            String.format("The %s property is not valid when creating a %s upgrade.",
+                UPGRADE_SKIP_FAILURES, UpgradeType.HOST_ORDERED));
+      }
+
+      String skipManualVerification = (String) requestMap.get(UPGRADE_SKIP_MANUAL_VERIFICATION);
+      if (Boolean.parseBoolean(skipManualVerification)) {
+        throw new AmbariException(
+            String.format("The %s property is not valid when creating a %s upgrade.",
+                UPGRADE_SKIP_MANUAL_VERIFICATION, UpgradeType.HOST_ORDERED));
+      }
+
+      if (!requestMap.containsKey(UPGRADE_HOST_ORDERED_HOSTS)) {
+        throw new AmbariException(
+            String.format("The %s property is required when creating a %s upgrade.",
+                UPGRADE_HOST_ORDERED_HOSTS, UpgradeType.HOST_ORDERED));
+      }
+
+      List<HostOrderItem> hostOrderItems = extractHostOrderItemsFromRequest(requestMap);
+      List<String> hostsFromRequest = new ArrayList<>(hostOrderItems.size());
+      for (HostOrderItem hostOrderItem : hostOrderItems) {
+        if (hostOrderItem.getType() == HostOrderActionType.HOST_UPGRADE) {
+          hostsFromRequest.addAll(hostOrderItem.getActionItems());
+        }
+      }
+
+      // ensure that all hosts for this cluster are accounted for
+      Collection<Host> hosts = cluster.getHosts();
+      Set<String> clusterHostNames = new HashSet<>(hosts.size());
+      for (Host host : hosts) {
+        clusterHostNames.add(host.getHostName());
+      }
+
+      Collection<String> disjunction = CollectionUtils.disjunction(hostsFromRequest,
+          clusterHostNames);
+
+      if (CollectionUtils.isNotEmpty(disjunction)) {
+        throw new AmbariException(String.format(
+            "The supplied list of hosts must match the cluster hosts in an upgrade of type %s. The following hosts are either missing or invalid: %s",
+            UpgradeType.HOST_ORDERED, StringUtils.join(disjunction, ", ")));
+      }
+
+      // verify that the upgradepack has the required grouping and set the
+      // action items on it
+      HostOrderGrouping hostOrderGrouping = null;
+      List<Grouping> groupings = upgradePack.getGroups(direction);
+      for (Grouping grouping : groupings) {
+        if (grouping instanceof HostOrderGrouping) {
+          hostOrderGrouping = (HostOrderGrouping) grouping;
+          hostOrderGrouping.setHostOrderItems(hostOrderItems);
+        }
+      }
+    }
+
+    /**
+     * Builds the list of {@link HostOrderItem}s from the upgrade request. If
+     * the upgrade request does not contain the hosts
+     *
+     * @param requestMap
+     *          the map of properties from the request (not {@code null}).
+     * @return the ordered list of actions to orchestrate for the
+     *         {@link UpgradeType#HOST_ORDERED} upgrade.
+     * @throws AmbariException
+     *           if the request properties are not valid.
+     */
+    private List<HostOrderItem> extractHostOrderItemsFromRequest(Map<String, Object> requestMap)
+        throws AmbariException {
+      // ewwww
+      Set<Map<String, List<String>>> hostsOrder = (Set<Map<String, List<String>>>) requestMap.get(
+          UPGRADE_HOST_ORDERED_HOSTS);
+
+      if (CollectionUtils.isEmpty(hostsOrder)) {
+        throw new AmbariException(
+            String.format("The %s property must be specified when using a %s upgrade type.",
+                UPGRADE_HOST_ORDERED_HOSTS, UpgradeType.HOST_ORDERED));
+      }
+
+      List<HostOrderItem> hostOrderItems = new ArrayList<>();
+
+      // extract all of the hosts so that we can ensure they are all accounted
+      // for
+      Iterator<Map<String, List<String>>> iterator = hostsOrder.iterator();
+      while (iterator.hasNext()) {
+        Map<String, List<String>> grouping = iterator.next();
+        List<String> hosts = grouping.get("hosts");
+        List<String> serviceChecks = grouping.get("service_checks");
+
+        if (CollectionUtils.isEmpty(hosts) && CollectionUtils.isEmpty(serviceChecks)) {
+          throw new AmbariException(String.format(
+              "The %s property must contain at least one object with either a %s or %s key",
+              UPGRADE_HOST_ORDERED_HOSTS, "hosts", "service_checks"));
+        }
+
+        if (CollectionUtils.isNotEmpty(hosts)) {
+          hostOrderItems.add(new HostOrderItem(HostOrderActionType.HOST_UPGRADE, hosts));
+        }
+
+        if (CollectionUtils.isNotEmpty(serviceChecks)) {
+          hostOrderItems.add(new HostOrderItem(HostOrderActionType.SERVICE_CHECK, serviceChecks));
+        }
+      }
+
+      return hostOrderItems;
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/48f7fb22/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContextFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContextFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContextFactory.java
index d482b8a..833c0b5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContextFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeContextFactory.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -19,12 +19,8 @@ package org.apache.ambari.server.state;
 
 import java.util.Map;
 
-import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
-import org.apache.ambari.server.state.stack.upgrade.Direction;
-import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
-
-import com.google.inject.assistedinject.Assisted;
 
 /**
  * The {@link UpgradeContextFactory} is used to create dependency-injected
@@ -37,27 +33,14 @@ public interface UpgradeContextFactory {
    *
    * @param cluster
    *          the cluster that the upgrade is for (not {@code null}).
-   * @param type
-   *          the type of upgrade, either rolling or non_rolling (not
-   *          {@code null}).
-   * @param direction
-   *          the direction for the upgrade
-   * @param fromRepositoryVersion
-   *          the repository where any existing services are coming from
-   *          {@code null}).
-   * @param toRepositoryVersion
-   *          the repository which is the target of the finalized
-   *          upgrade/downgrade {@code null}).
    * @param upgradeRequestMap
    *          the original map of parameters used to create the upgrade (not
    *          {@code null}).
    *
    * @return an initialized {@link UpgradeContext}.
    */
-  UpgradeContext create(Cluster cluster, UpgradeType type, Direction direction,
-      @Assisted("fromRepositoryVersion") RepositoryVersionEntity fromRepositoryVersion,
-      @Assisted("toRepositoryVersion") RepositoryVersionEntity toRepositoryVersion,
-      Map<String, Object> upgradeRequestMap);
+  UpgradeContext create(Cluster cluster, Map<String, Object> upgradeRequestMap)
+      throws AmbariException;
 
   /**
    * Creates an {@link UpgradeContext} which is injected with dependencies.

http://git-wip-us.apache.org/repos/asf/ambari/blob/48f7fb22/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
index fe6532d..5fdcd66 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -19,8 +19,10 @@ package org.apache.ambari.server.state;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -28,8 +30,11 @@ import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.ambari.annotations.Experimental;
+import org.apache.ambari.annotations.ExperimentalFeature;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.internal.TaskResourceProvider;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.spi.ClusterController;
@@ -46,7 +51,10 @@ import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.events.listeners.upgrade.StackVersionListener;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.ServiceConfigDAO;
+import org.apache.ambari.server.orm.entities.ClusterConfigEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
+import org.apache.ambari.server.orm.entities.ServiceConfigEntity;
 import org.apache.ambari.server.stack.HostsType;
 import org.apache.ambari.server.stack.MasterHostResolver;
 import org.apache.ambari.server.state.stack.UpgradePack;
@@ -98,7 +106,7 @@ public class UpgradeHelper {
   /**
    * Enum used to define placeholder text for replacement
    */
-  private static enum Placeholder {
+  private enum Placeholder {
     /**
      * No placeholder defined
      */
@@ -154,7 +162,7 @@ public class UpgradeHelper {
     DIRECTION_VERB_PROPER("direction.verb.proper");
 
     private String pattern;
-    private Placeholder(String key) {
+    Placeholder(String key) {
       pattern = "{{" + key + "}}";
     }
 
@@ -174,35 +182,55 @@ public class UpgradeHelper {
    * {@link StageWrapperBuilder} has finished building out all of the stages.
    */
   @Inject
-  private Provider<ConfigHelper> m_configHelper;
+  Provider<ConfigHelper> m_configHelperProvider;
+
+  @Inject
+  private Provider<AmbariMetaInfo> m_ambariMetaInfoProvider;
+
+  @Inject
+  private Provider<Clusters> m_clusters;
 
   @Inject
-  private Provider<AmbariMetaInfo> m_ambariMetaInfo;
+  private Provider<RepositoryVersionDAO> m_repoVersionProvider;
 
+  /**
+   * Used to update the configuration properties.
+   */
   @Inject
-  private Provider<Clusters> clusters;
+  private Provider<AmbariManagementController> m_controllerProvider;
 
+  /**
+   * Used to get configurations by service name.
+   */
   @Inject
-  private Provider<RepositoryVersionDAO> s_repoVersionDAO;
+  ServiceConfigDAO m_serviceConfigDAO;
 
   /**
-   * Get right Upgrade Pack, depends on stack, direction and upgrade type information
-   * @param clusterName The name of the cluster
-   * @param upgradeFromVersion Current stack version
-   * @param upgradeToVersion Target stack version
-   * @param direction {@code Direction} of the upgrade
-   * @param upgradeType The {@code UpgradeType}
-   * @param targetStackName The destination target stack name.
-   * @param preferredUpgradePackName For unit test, need to prefer an upgrade pack since multiple matches can be found.
+   * Get right Upgrade Pack, depends on stack, direction and upgrade type
+   * information
+   *
+   * @param clusterName
+   *          The name of the cluster
+   * @param upgradeFromVersion
+   *          Current stack version
+   * @param upgradeToVersion
+   *          Target stack version
+   * @param direction
+   *          {@code Direction} of the upgrade
+   * @param upgradeType
+   *          The {@code UpgradeType}
+   * @param preferredUpgradePackName
+   *          For unit test, need to prefer an upgrade pack since multiple
+   *          matches can be found.
    * @return {@code UpgradeType} object
    * @throws AmbariException
    */
   public UpgradePack suggestUpgradePack(String clusterName, String upgradeFromVersion, String upgradeToVersion,
-    Direction direction, UpgradeType upgradeType, String targetStackName, String preferredUpgradePackName) throws AmbariException {
+    Direction direction, UpgradeType upgradeType, String preferredUpgradePackName) throws AmbariException {
 
     // Find upgrade packs based on current stack. This is where to upgrade from
-    Cluster cluster = clusters.get().getCluster(clusterName);
-    StackId currentStack =  cluster.getCurrentStackVersion();
+    Cluster cluster = m_clusters.get().getCluster(clusterName);
+    StackId stack =  cluster.getCurrentStackVersion();
 
     String repoVersion = upgradeToVersion;
 
@@ -211,25 +239,19 @@ public class UpgradeHelper {
       repoVersion = upgradeFromVersion;
     }
 
-    Map<String, UpgradePack> packs = m_ambariMetaInfo.get().getUpgradePacks(currentStack.getStackName(), currentStack.getStackVersion());
-    UpgradePack pack = null;
-
-    if (StringUtils.isNotEmpty(preferredUpgradePackName) && packs.containsKey(preferredUpgradePackName)) {
-      pack = packs.get(preferredUpgradePackName);
-    }
-
-    RepositoryVersionEntity versionEntity = s_repoVersionDAO.get().findByStackNameAndVersion(targetStackName, repoVersion);
-    if (null == versionEntity) {
-      versionEntity = s_repoVersionDAO.get().findByVersion(repoVersion);
-    }
+    RepositoryVersionEntity versionEntity = m_repoVersionProvider.get().findByStackNameAndVersion(
+        stack.getStackName(), repoVersion);
 
     if (versionEntity == null) {
-      throw new AmbariException(String.format("Repository version %s was not found for target stack %s", repoVersion, targetStackName));
+      throw new AmbariException(String.format("Repository version %s was not found", repoVersion));
     }
 
-    // Best-attempt at picking an upgrade pack assuming within the same stack whose target stack version matches.
-    // If multiple candidates are found, raise an exception.
-    if (null == pack) {
+    Map<String, UpgradePack> packs = m_ambariMetaInfoProvider.get().getUpgradePacks(stack.getStackName(), stack.getStackVersion());
+    UpgradePack pack = null;
+
+    if (StringUtils.isNotEmpty(preferredUpgradePackName) && packs.containsKey(preferredUpgradePackName)) {
+      pack = packs.get(preferredUpgradePackName);
+    } else {
       String repoStackId = versionEntity.getStackId().getStackId();
       for (UpgradePack upgradePack : packs.values()) {
         if (null != upgradePack.getTargetStack() && upgradePack.getTargetStack().equals(repoStackId) &&
@@ -247,8 +269,8 @@ public class UpgradeHelper {
     }
 
     if (null == pack) {
-      throw new AmbariException(String.format("Unable to perform %s. Could not locate %s upgrade pack for stack %s and version %s",
-          direction.getText(false), upgradeType.toString(), targetStackName, repoVersion));
+      throw new AmbariException(String.format("Unable to perform %s. Could not locate %s upgrade pack for version %s",
+          direction.getText(false), upgradeType.toString(), repoVersion));
     }
 
    return pack;
@@ -268,7 +290,6 @@ public class UpgradeHelper {
   public List<UpgradeGroupHolder> createSequence(UpgradePack upgradePack,
       UpgradeContext context) throws AmbariException {
 
-    context.setAmbariMetaInfo(m_ambariMetaInfo.get());
     Cluster cluster = context.getCluster();
     MasterHostResolver mhr = context.getResolver();
 
@@ -546,7 +567,6 @@ public class UpgradeHelper {
   private String tokenReplace(UpgradeContext ctx, String source, String service, String component) {
     Cluster cluster = ctx.getCluster();
     MasterHostResolver mhr = ctx.getResolver();
-    String version = ctx.getTargetRepositoryVersion().getVersion();
 
     String result = source;
 
@@ -583,7 +603,7 @@ public class UpgradeHelper {
           break;
         }
         case VERSION:
-          value = version;
+          value = ctx.getRepositoryVersion().getVersion();
           break;
         case DIRECTION_VERB:
         case DIRECTION_VERB_PROPER:
@@ -602,7 +622,7 @@ public class UpgradeHelper {
           value = ctx.getDirection().getText(p == Placeholder.DIRECTION_TEXT_PROPER);
           break;
         default:
-          value = m_configHelper.get().getPlaceholderValueFromDesiredConfigurations(
+          value = m_configHelperProvider.get().getPlaceholderValueFromDesiredConfigurations(
               cluster, token);
           break;
       }
@@ -708,7 +728,7 @@ public class UpgradeHelper {
   private void setDisplayNames(UpgradeContext context, String service, String component) {
     StackId stackId = context.getCluster().getDesiredStackVersion();
     try {
-      ServiceInfo serviceInfo = m_ambariMetaInfo.get().getService(stackId.getStackName(),
+      ServiceInfo serviceInfo = m_ambariMetaInfoProvider.get().getService(stackId.getStackName(),
           stackId.getStackVersion(), service);
       context.setServiceDisplay(service, serviceInfo.getDisplayName());
 
@@ -721,6 +741,32 @@ public class UpgradeHelper {
   }
 
   /**
+   * Updates the various repositories and configurations for services
+   * participating in the upgrade or downgrade. The following actions are
+   * performed in order:
+   * <ul>
+   * <li>The desired repository for every service and component is changed<
+   * <li>The {@link UpgradeState} of every component host is moved to either
+   * {@link UpgradeState#IN_PROGRESS} or {@link UpgradeState#NONE}.
+   * <li>In the case of an upgrade, new configurations and service
+   * configurations are created if necessary. In the case of a downgrade, any
+   * configurations created by the upgrade are reverted.
+   * </ul>
+   *
+   * @param upgradeContext
+   *          the upgrade context holding all relevent upgrade information (not
+   *          {@code null}).
+   * @throws AmbariException
+   */
+  @Transactional
+  @Experimental(feature = ExperimentalFeature.PATCH_UPGRADES)
+  public void updateDesiredRepositoriesAndConfigs(UpgradeContext upgradeContext)
+      throws AmbariException {
+    setDesiredRepositories(upgradeContext);
+    processConfigurationsIfRequired(upgradeContext);
+  }
+
+  /**
    * Transitions all affected components to {@link UpgradeState#IN_PROGRESS}.
    * Transition is performed only for components that advertise their version.
    * Additionally sets the service component desired version to the specified
@@ -730,27 +776,25 @@ public class UpgradeHelper {
    * the upgrade state individually, we wrap this method inside of a transaction
    * to prevent 1000's of transactions from being opened and committed.
    *
-   * @param stackId
-   *          the desired stack ID for the upgrade
-   * @param version
-   *          desired version (like 2.2.1.0-1234) for upgrade
-   * @param targetServices
-   *          targets for upgrade
-   * @param targetStack
-   *          the target stack for the components. Express and Rolling upgrades
-   *          determine the "correct" stack differently, so the component's
-   *          desired stack id is not a reliable indicator.
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
    */
-  @Transactional
-  public void putComponentsToUpgradingState(StackId stackId, String version,
-      Map<Service, Set<ServiceComponent>> targetServices, StackId targetStack) throws AmbariException {
-
-    for (Map.Entry<Service, Set<ServiceComponent>> entry: targetServices.entrySet()) {
-      for (ServiceComponent serviceComponent: entry.getValue()) {
-
+  @Experimental(feature = ExperimentalFeature.PATCH_UPGRADES)
+  private void setDesiredRepositories(UpgradeContext upgradeContext) throws AmbariException {
+    Cluster cluster = upgradeContext.getCluster();
+    Set<String> services = upgradeContext.getSupportedServices();
+
+    for (String serviceName : services) {
+      Service service = cluster.getService(serviceName);
+      RepositoryVersionEntity targetRepositoryVersion = upgradeContext.getTargetRepositoryVersion(serviceName);
+      StackId targetStack = targetRepositoryVersion.getStackId();
+      service.setDesiredRepositoryVersion(targetRepositoryVersion);
+
+      Collection<ServiceComponent> components = service.getServiceComponents().values();
+      for (ServiceComponent serviceComponent : components) {
         boolean versionAdvertised = false;
         try {
-          ComponentInfo ci = m_ambariMetaInfo.get().getComponent(targetStack.getStackName(),
+          ComponentInfo ci = m_ambariMetaInfoProvider.get().getComponent(targetStack.getStackName(),
               targetStack.getStackVersion(), serviceComponent.getServiceName(),
               serviceComponent.getName());
 
@@ -761,27 +805,224 @@ public class UpgradeHelper {
               StackVersionListener.UNKNOWN_VERSION);
         }
 
-        UpgradeState upgradeState = UpgradeState.IN_PROGRESS;
-        String desiredVersion = version;
-
+        UpgradeState upgradeStateToSet = UpgradeState.IN_PROGRESS;
         if (!versionAdvertised) {
-          upgradeState = UpgradeState.NONE;
-          desiredVersion = StackVersionListener.UNKNOWN_VERSION;
+          upgradeStateToSet = UpgradeState.NONE;
         }
 
-        for (ServiceComponentHost serviceComponentHost: serviceComponent.getServiceComponentHosts().values()) {
-          serviceComponentHost.setUpgradeState(upgradeState);
+        for (ServiceComponentHost serviceComponentHost : serviceComponent.getServiceComponentHosts().values()) {
+          if (serviceComponentHost.getUpgradeState() != upgradeStateToSet) {
+            serviceComponentHost.setUpgradeState(upgradeStateToSet);
+          }
 
-          // !!! if we aren't version advertised, but there IS a version, set it.
-          if (!versionAdvertised &&
-              !serviceComponentHost.getVersion().equals(StackVersionListener.UNKNOWN_VERSION)) {
+          // !!! if we aren't version advertised, but there IS a version, set
+          // it.
+          if (!versionAdvertised && !StringUtils.equals(StackVersionListener.UNKNOWN_VERSION,
+              serviceComponentHost.getVersion())) {
             serviceComponentHost.setVersion(StackVersionListener.UNKNOWN_VERSION);
           }
         }
 
-        serviceComponent.setDesiredStackVersion(stackId);
-        serviceComponent.setDesiredVersion(desiredVersion);
+        // set component desired repo
+        serviceComponent.setDesiredRepositoryVersion(targetRepositoryVersion);
+      }
+    }
+  }
+
+  /**
+   * Handles the creation or resetting of configurations based on whether an
+   * upgrade or downgrade is occurring. This method will not do anything when
+   * the service is not crossing major stack versions, since, by definition, no
+   * new configurations are automatically created when upgrading with the same
+   * stack (ie HDP 2.2.0.0 -> HDP 2.2.1.0).
+   * <p/>
+   * When upgrading or downgrade between stacks (HDP 2.2.0.0 -> HDP 2.3.0.0)
+   * then this will perform the following:
+   * <ul>
+   * <li>Upgrade: Create new configurations that are a merge between the source
+   * stack and the target stack. If a value has changed between stacks, then the
+   * target stack value should be taken unless the cluster's value differs from
+   * the old stack. This can occur if a property has been customized after
+   * installation.</li>
+   * <li>Downgrade: Reset the latest configurations from the service's original
+   * stack. The new configurations that were created on upgrade must be left
+   * intact until all components have been reverted, otherwise heartbeats will
+   * fail due to missing configurations.</li>
+   * </ul>
+   *
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
+   * @throws AmbariException
+   */
+  private void processConfigurationsIfRequired(UpgradeContext upgradeContext)
+      throws AmbariException {
+
+    AmbariManagementController controller = m_controllerProvider.get();
+
+    Cluster cluster = upgradeContext.getCluster();
+    Direction direction = upgradeContext.getDirection();
+    String userName = controller.getAuthName();
+    Set<String> servicesInUpgrade = upgradeContext.getSupportedServices();
+
+    // merge or revert configurations for any service that needs it
+    for( String serviceName : servicesInUpgrade ){
+      RepositoryVersionEntity sourceRepositoryVersion = upgradeContext.getSourceRepositoryVersion(serviceName);
+      RepositoryVersionEntity targetRepositoryVersion = upgradeContext.getTargetRepositoryVersion(serviceName);
+      StackId sourceStackId = sourceRepositoryVersion.getStackId();
+      StackId targetStackId = targetRepositoryVersion.getStackId();
+
+      // only work with configurations when crossing stacks
+      if (sourceStackId.equals(targetStackId)) {
+        RepositoryVersionEntity associatedRepositoryVersion = upgradeContext.getRepositoryVersion();
+        LOG.info(
+            "The {} {} {} will not change stack configurations for {} since the source and target are both {}",
+            direction.getText(false), direction.getPreposition(),
+            associatedRepositoryVersion.getVersion(), serviceName, targetStackId);
+
+        continue;
+      }
+
+      ConfigHelper configHelper = m_configHelperProvider.get();
+
+      // downgrade is easy - just remove the new and make the old current
+      if (direction == Direction.DOWNGRADE) {
+        cluster.applyLatestConfigurations(targetStackId, serviceName);
+        return;
+      }
+
+      // upgrade is a bit harder - we have to merge new stack configurations in
+
+      // populate a map of default configurations for the service on the old
+      // stack (this is used when determining if a property has been
+      // customized and should be overriden with the new stack value)
+      Map<String, Map<String, String>> oldServiceDefaultConfigsByType = configHelper.getDefaultProperties(
+          sourceStackId, serviceName);
+
+      // populate a map with default configurations from the new stack
+      Map<String, Map<String, String>> newServiceDefaultConfigsByType = configHelper.getDefaultProperties(
+          targetStackId, serviceName);
+
+      // find the current, existing configurations for the service
+      List<Config> existingServiceConfigs = new ArrayList<>();
+      List<ServiceConfigEntity> latestServiceConfigs = m_serviceConfigDAO.getLastServiceConfigsForService(
+          cluster.getClusterId(), serviceName);
+
+      for (ServiceConfigEntity serviceConfig : latestServiceConfigs) {
+        List<ClusterConfigEntity> existingConfigurations = serviceConfig.getClusterConfigEntities();
+        for (ClusterConfigEntity currentServiceConfig : existingConfigurations) {
+          String configurationType = currentServiceConfig.getType();
+          Config currentClusterConfigForService = cluster.getDesiredConfigByType(configurationType);
+          existingServiceConfigs.add(currentClusterConfigForService);
+        }
+      }
+
+      // now that we have found, old, new, and existing confgs, overlay the
+      // existing on top of the new
+      for (Config existingServiceConfig : existingServiceConfigs) {
+        String configurationType = existingServiceConfig.getType();
+
+        // get current stack default configurations on install
+        Map<String, String> oldServiceDefaultConfigs = oldServiceDefaultConfigsByType.get(
+            configurationType);
+
+        // NPE sanity for current stack defaults
+        if (null == oldServiceDefaultConfigs) {
+          oldServiceDefaultConfigs = Collections.emptyMap();
+        }
+
+        // get the existing configurations
+        Map<String, String> existingConfigurations = existingServiceConfig.getProperties();
+
+        // get the new configurations
+        Map<String, String> newDefaultConfigurations = newServiceDefaultConfigsByType.get(
+            configurationType);
+
+        // if the new stack configurations don't have the type, then simply add
+        // all of the existing in
+        if (null == newDefaultConfigurations) {
+          newServiceDefaultConfigsByType.put(configurationType, existingConfigurations);
+          continue;
+        } else {
+          // Remove any configs in the new stack whose value is NULL, unless
+          // they currently exist and the value is not NULL.
+          Iterator<Map.Entry<String, String>> iter = newDefaultConfigurations.entrySet().iterator();
+          while (iter.hasNext()) {
+            Map.Entry<String, String> entry = iter.next();
+            if (entry.getValue() == null) {
+              iter.remove();
+            }
+          }
+        }
+
+        // process every existing configuration property for this configuration
+        // type
+        for (Map.Entry<String, String> existingConfigurationEntry : existingConfigurations.entrySet()) {
+          String existingConfigurationKey = existingConfigurationEntry.getKey();
+          String existingConfigurationValue = existingConfigurationEntry.getValue();
+
+          // if there is already an entry, we now have to try to determine if
+          // the value was customized after stack installation
+          if (newDefaultConfigurations.containsKey(existingConfigurationKey)) {
+            String newDefaultConfigurationValue = newDefaultConfigurations.get(
+                existingConfigurationKey);
+
+            if (!StringUtils.equals(existingConfigurationValue, newDefaultConfigurationValue)) {
+              // the new default is different from the existing cluster value;
+              // only override the default value if the existing value differs
+              // from the original stack
+              String oldDefaultValue = oldServiceDefaultConfigs.get(existingConfigurationKey);
+
+              if (!StringUtils.equals(existingConfigurationValue, oldDefaultValue)) {
+                // at this point, we've determined that there is a
+                // difference
+                // between default values between stacks, but the value was
+                // also customized, so keep the customized value
+                newDefaultConfigurations.put(existingConfigurationKey, existingConfigurationValue);
+              }
+            }
+          } else {
+            // there is no entry in the map, so add the existing key/value
+            // pair
+            newDefaultConfigurations.put(existingConfigurationKey, existingConfigurationValue);
+          }
+        }
+
+        /*
+        for every new configuration which does not exist in the existing
+        configurations, see if it was present in the current stack
+
+        stack 2.x has foo-site/property (on-ambari-upgrade is false)
+        stack 2.y has foo-site/property
+        the current cluster (on 2.x) does not have it
+
+        In this case, we should NOT add it back as clearly stack advisor has removed it
+        */
+        Iterator<Map.Entry<String, String>> newDefaultConfigurationsIterator = newDefaultConfigurations.entrySet().iterator();
+        while (newDefaultConfigurationsIterator.hasNext()) {
+          Map.Entry<String, String> newConfigurationEntry = newDefaultConfigurationsIterator.next();
+          String newConfigurationPropertyName = newConfigurationEntry.getKey();
+          if (oldServiceDefaultConfigs.containsKey(newConfigurationPropertyName)
+              && !existingConfigurations.containsKey(newConfigurationPropertyName)) {
+            LOG.info(
+                "The property {}/{} exists in both {} and {} but is not part of the current set of configurations and will therefore not be included in the configuration merge",
+                configurationType, newConfigurationPropertyName, sourceStackId, targetStackId);
+
+            // remove the property so it doesn't get merged in
+            newDefaultConfigurationsIterator.remove();
+          }
+        }
+      }
+
+      if (null != newServiceDefaultConfigsByType) {
+        Set<String> configTypes = newServiceDefaultConfigsByType.keySet();
+        LOG.info("The upgrade will create the following configurations for stack {}: {}",
+            targetStackId, StringUtils.join(configTypes, ','));
+
+        String serviceVersionNote = String.format("%s %s %s", direction.getText(true),
+            direction.getPreposition(), upgradeContext.getRepositoryVersion().getVersion());
 
+        configHelper.createConfigTypes(cluster, targetStackId, controller,
+            newServiceDefaultConfigsByType, userName, serviceVersionNote);
       }
     }
   }


Mime
View raw message