You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2015/04/25 01:01:18 UTC

[2/2] ambari git commit: AMBARI-10736 - Downgrade must manage cross-stack configuration sets (jonathanhurley)

AMBARI-10736 - Downgrade must manage cross-stack configuration sets (jonathanhurley)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ac6fd63f
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ac6fd63f
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ac6fd63f

Branch: refs/heads/trunk
Commit: ac6fd63f95a38130ef707a5b872c0c51ec23573d
Parents: c06571f
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Fri Apr 24 16:02:11 2015 -0400
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Fri Apr 24 19:01:10 2015 -0400

----------------------------------------------------------------------
 .../internal/UpgradeResourceProvider.java       | 127 +++++++--
 .../ambari/server/orm/dao/ClusterDAO.java       |  75 +++++-
 .../ambari/server/orm/dao/ServiceConfigDAO.java |  59 ++++
 .../orm/entities/ClusterConfigEntity.java       |   5 +-
 .../entities/ClusterConfigMappingEntity.java    |  88 +++++-
 .../orm/entities/ServiceConfigEntity.java       |  52 +++-
 .../upgrades/FinalizeUpgradeAction.java         |  42 ++-
 .../org/apache/ambari/server/state/Cluster.java | 133 +++++----
 .../ambari/server/state/UpgradeContext.java     |  70 ++++-
 .../server/state/cluster/ClusterImpl.java       | 147 +++++++++-
 .../server/orm/dao/ServiceConfigDAOTest.java    |  54 +++-
 .../upgrades/UpgradeActionTest.java             | 267 ++++++++++++++-----
 .../ambari/server/state/UpgradeHelperTest.java  |  38 +--
 13 files changed, 956 insertions(+), 201 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
index 7de7348..b973d98 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
@@ -142,6 +142,20 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   private static final String COMMAND_PARAM_TASKS = "tasks";
   private static final String COMMAND_PARAM_STRUCT_OUT = "structured_out";
 
+  /**
+   * 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}.
+   */
+  private 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}.
+   */
+  private static final String COMMAND_PARAM_TARGET_STACK = "target_stack";
+
   private static final String DEFAULT_REASON_TEMPLATE = "Aborting upgrade %s";
 
   private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = new HashMap<Resource.Type, String>();
@@ -514,10 +528,27 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     // the version being upgraded or downgraded to (ie hdp-2.2.1.0-1234)
     final String version = (String) requestMap.get(UPGRADE_VERSION);
 
-    MasterHostResolver resolver = direction.isUpgrade() ?
-        new MasterHostResolver(configHelper, cluster) : new MasterHostResolver(configHelper, cluster, version);
+    MasterHostResolver resolver = direction.isUpgrade() ? new MasterHostResolver(
+        configHelper, cluster) : new MasterHostResolver(configHelper, cluster,
+        version);
+
+    StackId sourceStackId = null;
+    StackId targetStackId = null;
 
-    UpgradeContext ctx = new UpgradeContext(resolver, version, direction);
+    switch( direction ){
+      case UPGRADE:
+        sourceStackId = cluster.getCurrentStackVersion();
+
+        RepositoryVersionEntity targetRepositoryVersion = s_repoVersionDAO.findMaxByVersion(version);
+        targetStackId = targetRepositoryVersion.getStackId();
+        break;
+      case DOWNGRADE:
+        sourceStackId = cluster.getCurrentStackVersion();
+        targetStackId = cluster.getDesiredStackVersion();
+        break;
+    }
+
+    UpgradeContext ctx = new UpgradeContext(resolver, sourceStackId, targetStackId, version, direction);
 
     List<UpgradeGroupHolder> groups = s_upgradeHelper.createSequence(pack, ctx);
 
@@ -571,9 +602,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
       }
 
       groupEntity.setItems(itemEntities);
-
       groupEntities.add(groupEntity);
-
     }
 
     UpgradeEntity entity = new UpgradeEntity();
@@ -588,7 +617,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     entity.setRequestId(req.getId());
 
     // !!! in case persist() starts creating tasks right away, square away the configs
-    createConfigs(cluster, version, direction);
+    processConfigurations(cluster, version, direction);
 
     req.persist();
 
@@ -598,13 +627,31 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   }
 
   /**
-   * Merges and creates configs for the new stack.  No-op when the target stack version
-   * is the same as the cluster's current stack version.
-   * @param cluster the cluster
-   * @param version the version
+   * Handles the creation or resetting of configurations based on whether an
+   * upgrade or downgrade is occurring. This method will not do anything when
+   * the target stack version is the same as the cluster's current stack version
+   * 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 current
+   * stack and the desired stack.</li>
+   * <li>Downgrade: Reset the latest configurations from the cluster'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 cluster
+   *          the cluster
+   * @param version
+   *          the version
    * @throws AmbariException
    */
-  private void createConfigs(Cluster cluster, String version, Direction direction) throws AmbariException {
+  private void processConfigurations(Cluster cluster, String version, Direction direction) throws AmbariException {
     RepositoryVersionEntity targetRve = s_repoVersionDAO.findMaxByVersion(version);
     if (null == targetRve) {
       LOG.info("Could not find version entity for {}; not setting new configs",
@@ -612,24 +659,37 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
       return;
     }
 
-    StackEntity oldStack = cluster.getCurrentClusterVersion().getRepositoryVersion().getStack();
-    StackEntity newStack = targetRve.getStack();
-
-    if (oldStack.equals(newStack)) {
-      return;
+    // if the current and target stacks are the same (ie HDP 2.2.0.0 -> 2.2.1.0)
+    // then we should never do anything with configs on either upgrade or
+    // downgrade; however if we are going across stacks, we have to do the stack
+    // checks differently depending on whether this is an upgrade or downgrade
+    StackEntity targetStack = targetRve.getStack();
+    StackId currentStackId = cluster.getCurrentStackVersion();
+    StackId desiredStackId = cluster.getDesiredStackVersion();
+    StackId targetStackId = new StackId(targetStack);
+    switch (direction) {
+      case UPGRADE:
+        if (currentStackId.equals(targetStackId)) {
+          return;
+        }
+        break;
+      case DOWNGRADE:
+        if (desiredStackId.equals(targetStackId)) {
+          return;
+        }
+        break;
     }
 
-    ConfigHelper configHelper = getManagementController().getConfigHelper();
 
     Map<String, Map<String, String>> clusterConfigs = null;
+    ConfigHelper configHelper = getManagementController().getConfigHelper();
 
     if (direction == Direction.UPGRADE) {
-
       clusterConfigs = new HashMap<String, Map<String, String>>();
 
       // !!! stack
-      Set<org.apache.ambari.server.state.PropertyInfo> pi = s_metaProvider.get().getStackProperties(newStack.getStackName(),
-          newStack.getStackVersion());
+      Set<org.apache.ambari.server.state.PropertyInfo> pi = s_metaProvider.get().getStackProperties(
+          targetStack.getStackName(), targetStack.getStackVersion());
 
       for (PropertyInfo stackProperty : pi) {
         String type = ConfigHelper.fileNameToConfigType(stackProperty.getFilename());
@@ -644,8 +704,9 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
 
       // !!! by service
       for (String serviceName : cluster.getServices().keySet()) {
-        pi = s_metaProvider.get().getServiceProperties(newStack.getStackName(),
-            newStack.getStackVersion(), serviceName);
+        pi = s_metaProvider.get().getServiceProperties(
+            targetStack.getStackName(), targetStack.getStackVersion(),
+            serviceName);
 
         // !!! use new stack as the basis
         for (PropertyInfo stackProperty : pi) {
@@ -668,12 +729,13 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
         }
       }
     } else {
-      // !!! remove configs
+      // downgrade
+      cluster.applyLatestConfigurations(cluster.getCurrentStackVersion());
     }
 
     // !!! update the stack
-    cluster.setDesiredStackVersion(
-        new StackId(newStack.getStackName(), newStack.getStackVersion()), true);
+    cluster.setDesiredStackVersion(new StackId(targetStack.getStackName(),
+        targetStack.getStackVersion()), true);
 
     // !!! configs must be created after setting the stack version
     if (null != clusterConfigs) {
@@ -681,7 +743,6 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
           clusterConfigs, getManagementController().getAuthName(),
           "Configuration created for Upgrade");
     }
-
   }
 
 
@@ -734,6 +795,8 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     params.put(COMMAND_PARAM_TASKS, entity.getTasks());
     params.put(COMMAND_PARAM_VERSION, context.getVersion());
     params.put(COMMAND_PARAM_DIRECTION, context.getDirection().name().toLowerCase());
+    params.put(COMMAND_PARAM_ORIGINAL_STACK, context.getOriginalStackId().getStackId());
+    params.put(COMMAND_PARAM_TARGET_STACK, context.getTargetStackId().getStackId());
 
     // Because custom task may end up calling a script/function inside a service, it is necessary to set the
     // service_package_folder and hooks_folder params.
@@ -806,6 +869,10 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     restartCommandParams.put(COMMAND_PARAM_RESTART_TYPE, "rolling_upgrade");
     restartCommandParams.put(COMMAND_PARAM_VERSION, context.getVersion());
     restartCommandParams.put(COMMAND_PARAM_DIRECTION, context.getDirection().name().toLowerCase());
+    restartCommandParams.put(COMMAND_PARAM_ORIGINAL_STACK,
+        context.getOriginalStackId().getStackId());
+    restartCommandParams.put(COMMAND_PARAM_TARGET_STACK,
+        context.getTargetStackId().getStackId());
 
     ActionExecutionContext actionContext = new ActionExecutionContext(
         cluster.getClusterName(), "RESTART",
@@ -858,6 +925,10 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     Map<String, String> commandParams = new HashMap<String, String>();
     commandParams.put(COMMAND_PARAM_VERSION, context.getVersion());
     commandParams.put(COMMAND_PARAM_DIRECTION, context.getDirection().name().toLowerCase());
+    commandParams.put(COMMAND_PARAM_ORIGINAL_STACK,
+        context.getOriginalStackId().getStackId());
+    commandParams.put(COMMAND_PARAM_TARGET_STACK,
+        context.getTargetStackId().getStackId());
 
     ActionExecutionContext actionContext = new ActionExecutionContext(
         cluster.getClusterName(), "SERVICE_CHECK",
@@ -904,6 +975,10 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     commandParams.put(COMMAND_PARAM_CLUSTER_NAME, cluster.getClusterName());
     commandParams.put(COMMAND_PARAM_VERSION, context.getVersion());
     commandParams.put(COMMAND_PARAM_DIRECTION, context.getDirection().name().toLowerCase());
+    commandParams.put(COMMAND_PARAM_ORIGINAL_STACK,
+        context.getOriginalStackId().getStackId());
+    commandParams.put(COMMAND_PARAM_TARGET_STACK,
+        context.getTargetStackId().getStackId());
 
     String itemDetail = entity.getText();
     String stageText = StringUtils.abbreviate(entity.getText(), 255);

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
index 7df1e2b..d3c4bd4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
@@ -30,7 +30,10 @@ import javax.persistence.criteria.Root;
 
 import org.apache.ambari.server.orm.RequiresSession;
 import org.apache.ambari.server.orm.entities.ClusterConfigEntity;
+import org.apache.ambari.server.orm.entities.ClusterConfigMappingEntity;
 import org.apache.ambari.server.orm.entities.ClusterEntity;
+import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.state.StackId;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -41,9 +44,13 @@ import com.google.inject.persist.Transactional;
 public class ClusterDAO {
 
   @Inject
-  Provider<EntityManager> entityManagerProvider;
+  private Provider<EntityManager> entityManagerProvider;
+
+  @Inject
+  private DaoUtils daoUtils;
+
   @Inject
-  DaoUtils daoUtils;
+  private StackDAO stackDAO;
 
   /**
    * Looks for Cluster by ID
@@ -143,6 +150,58 @@ public class ClusterDAO {
   }
 
   /**
+   * Get all configurations for the specified cluster and stack. This will
+   * return different versions of the same configuration type (cluster-env v1
+   * and cluster-env v2) if they exist.
+   *
+   * @param clusterId
+   *          the cluster (not {@code null}).
+   * @param stackId
+   *          the stack (not {@code null}).
+   * @return all service configurations for the cluster and stack.
+   */
+  @RequiresSession
+  public List<ClusterConfigEntity> getAllConfigurations(Long clusterId,
+      StackId stackId) {
+
+    StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
+        stackId.getStackVersion());
+
+    TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
+        "ClusterConfigEntity.findAllConfigsByStack", ClusterConfigEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("stack", stackEntity);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets the latest configurations for a given stack for all of the
+   * configurations of the specified cluster.
+   *
+   * @param clusterId
+   *          the cluster that the service is a part of.
+   * @param stackId
+   *          the stack to get the latest configurations for (not {@code null}).
+   * @return the latest configurations for the specified cluster and stack.
+   */
+  public List<ClusterConfigEntity> getLatestConfigurations(long clusterId,
+      StackId stackId) {
+    StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
+        stackId.getStackVersion());
+
+    TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
+        "ClusterConfigEntity.findLatestConfigsByStack",
+        ClusterConfigEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("stack", stackEntity);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
    * Create Cluster entity in Database
    * @param clusterEntity entity to create
    */
@@ -168,8 +227,18 @@ public class ClusterDAO {
   }
 
   /**
+   * Remove a cluster configuration mapping from the DB.
+   */
+  @Transactional
+  public void removeConfigMapping(ClusterConfigMappingEntity entity) {
+    entityManagerProvider.get().remove(entity);
+  }
+
+  /**
    * Retrieve entity data from DB
-   * @param clusterEntity entity to refresh
+   * 
+   * @param clusterEntity
+   *          entity to refresh
    */
   @Transactional
   public void refresh(ClusterEntity clusterEntity) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceConfigDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceConfigDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceConfigDAO.java
index fbaec3e..8f8e196 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceConfigDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceConfigDAO.java
@@ -32,6 +32,8 @@ import javax.persistence.criteria.Root;
 
 import org.apache.ambari.server.orm.RequiresSession;
 import org.apache.ambari.server.orm.entities.ServiceConfigEntity;
+import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.state.StackId;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -44,6 +46,9 @@ public class ServiceConfigDAO {
   private Provider<EntityManager> entityManagerProvider;
 
   @Inject
+  private StackDAO stackDAO;
+
+  @Inject
   private DaoUtils daoUtils;
 
   @RequiresSession
@@ -110,6 +115,60 @@ public class ServiceConfigDAO {
     return daoUtils.selectList(query, clusterId);
   }
 
+  /**
+   * Get all service configurations for the specified cluster and stack. This
+   * will return different versions of the same configuration (HDFS v1 and v2)
+   * if they exist.
+   *
+   * @param clusterId
+   *          the cluster (not {@code null}).
+   * @param stackId
+   *          the stack (not {@code null}).
+   * @return all service configurations for the cluster and stack.
+   */
+  @RequiresSession
+  public List<ServiceConfigEntity> getAllServiceConfigs(Long clusterId,
+      StackId stackId) {
+
+    StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
+        stackId.getStackVersion());
+
+    TypedQuery<ServiceConfigEntity> query = entityManagerProvider.get().createNamedQuery(
+        "ServiceConfigEntity.findAllServiceConfigsByStack",
+        ServiceConfigEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("stack", stackEntity);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets the latest service configurations for the specified cluster and stack.
+   *
+   * @param clusterId
+   *          the cluster (not {@code null}).
+   * @param stackId
+   *          the stack (not {@code null}).
+   * @return the latest service configurations for the cluster and stack.
+   */
+  @RequiresSession
+  public List<ServiceConfigEntity> getLatestServiceConfigs(Long clusterId,
+      StackId stackId) {
+
+    StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
+        stackId.getStackVersion());
+
+    TypedQuery<ServiceConfigEntity> query = entityManagerProvider.get().createNamedQuery(
+        "ServiceConfigEntity.findLatestServiceConfigsByStack",
+        ServiceConfigEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("stack", stackEntity);
+
+    return daoUtils.selectList(query);
+  }
+
   @RequiresSession
   public ServiceConfigEntity getLastServiceConfig(Long clusterId, String serviceName) {
     TypedQuery<ServiceConfigEntity> query = entityManagerProvider.get().

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
index 8d304c0..67f804c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
@@ -48,7 +48,10 @@ import javax.persistence.UniqueConstraint;
   , pkColumnValue = "config_id_seq"
   , initialValue = 1
 )
-@NamedQueries({ @NamedQuery(name = "ClusterConfigEntity.findNextConfigVersion", query = "SELECT COALESCE(MAX(clusterConfig.version),0) + 1 as nextVersion FROM ClusterConfigEntity clusterConfig WHERE clusterConfig.type=:configType AND clusterConfig.clusterId=:clusterId") })
+@NamedQueries({
+    @NamedQuery(name = "ClusterConfigEntity.findNextConfigVersion", query = "SELECT COALESCE(MAX(clusterConfig.version),0) + 1 as nextVersion FROM ClusterConfigEntity clusterConfig WHERE clusterConfig.type=:configType AND clusterConfig.clusterId=:clusterId"),
+    @NamedQuery(name = "ClusterConfigEntity.findAllConfigsByStack", query = "SELECT clusterConfig FROM ClusterConfigEntity clusterConfig WHERE clusterConfig.clusterId=:clusterId AND clusterConfig.stack=:stack"),
+    @NamedQuery(name = "ClusterConfigEntity.findLatestConfigsByStack", query = "SELECT clusterConfig FROM ClusterConfigEntity clusterConfig WHERE clusterConfig.clusterId=:clusterId AND clusterConfig.timestamp = (SELECT MAX(clusterConfig2.timestamp) FROM ClusterConfigEntity clusterConfig2 WHERE clusterConfig2.clusterId=:clusterId AND clusterConfig2.stack=:stack AND clusterConfig2.type = clusterConfig.type)") })
 public class ClusterConfigEntity {
 
   @Id

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigMappingEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigMappingEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigMappingEntity.java
index fa48399..5e18014 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigMappingEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigMappingEntity.java
@@ -19,14 +19,11 @@ package org.apache.ambari.server.orm.entities;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.IdClass;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import javax.persistence.TableGenerator;
 
 /**
  * Entity that maps to a cluster config mapping.
@@ -53,22 +50,22 @@ public class ClusterConfigMappingEntity {
 
   @Column(name = "selected", insertable = true, updatable = true, nullable = false)
   private int selectedInd = 0;
-  
+
   @Column(name = "user_name", insertable = true, updatable = true, nullable = false)
   private String user;
 
   @ManyToOne
   @JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", nullable = false)
   private ClusterEntity clusterEntity;
-  
+
   public Long getClusterId() {
     return clusterId;
   }
-  
+
   public void setClusterId(Long id) {
     clusterId = id;
   }
-  
+
   public String getType() {
     return typeName;
   }
@@ -83,15 +80,15 @@ public class ClusterConfigMappingEntity {
   public void setCreateTimestamp(Long timestamp) {
     createTimestamp = timestamp;
   }
-  
+
   public String getTag() {
     return tag;
   }
-  
+
   public void setTag(String version) {
     tag = version;
   }
- 
+
   public int isSelected() {
     return selectedInd;
   }
@@ -99,21 +96,21 @@ public class ClusterConfigMappingEntity {
   public void setSelected(int selected) {
     selectedInd = selected;
   }
-  
+
   /**
    * @return the user
    */
   public String getUser() {
     return user;
   }
-  
+
   /**
    * @param userName the user
    */
   public void setUser(String userName) {
     user = userName;
   }
-  
+
   public ClusterEntity getClusterEntity() {
     return clusterEntity;
   }
@@ -122,5 +119,70 @@ public class ClusterConfigMappingEntity {
     clusterEntity = entity;
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((clusterId == null) ? 0 : clusterId.hashCode());
+    result = prime * result + ((createTimestamp == null) ? 0 : createTimestamp.hashCode());
+    result = prime * result + ((tag == null) ? 0 : tag.hashCode());
+    result = prime * result + ((typeName == null) ? 0 : typeName.hashCode());
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (obj == null) {
+      return false;
+    }
+
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
 
+    ClusterConfigMappingEntity other = (ClusterConfigMappingEntity) obj;
+    if (clusterId == null) {
+      if (other.clusterId != null) {
+        return false;
+      }
+    } else if (!clusterId.equals(other.clusterId)) {
+      return false;
+    }
+
+    if (createTimestamp == null) {
+      if (other.createTimestamp != null) {
+        return false;
+      }
+    } else if (!createTimestamp.equals(other.createTimestamp)) {
+      return false;
+    }
+
+    if (tag == null) {
+      if (other.tag != null) {
+        return false;
+      }
+    } else if (!tag.equals(other.tag)) {
+      return false;
+    }
+
+    if (typeName == null) {
+      if (other.typeName != null) {
+        return false;
+      }
+    } else if (!typeName.equals(other.typeName)) {
+      return false;
+    }
+
+    return true;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceConfigEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceConfigEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceConfigEntity.java
index c8a74a6..f5bdbf9 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceConfigEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceConfigEntity.java
@@ -21,6 +21,7 @@ package org.apache.ambari.server.orm.entities;
 import java.util.List;
 
 import javax.persistence.Basic;
+import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -45,7 +46,10 @@ import javax.persistence.TableGenerator;
   , pkColumnValue = "service_config_id_seq"
   , initialValue = 1
 )
-@NamedQueries({ @NamedQuery(name = "ServiceConfigEntity.findNextServiceConfigVersion", query = "SELECT COALESCE(MAX(serviceConfig.version), 0) + 1 AS nextVersion FROM ServiceConfigEntity serviceConfig WHERE serviceConfig.serviceName=:serviceName AND serviceConfig.clusterId=:clusterId") })
+@NamedQueries({
+    @NamedQuery(name = "ServiceConfigEntity.findNextServiceConfigVersion", query = "SELECT COALESCE(MAX(serviceConfig.version), 0) + 1 AS nextVersion FROM ServiceConfigEntity serviceConfig WHERE serviceConfig.serviceName=:serviceName AND serviceConfig.clusterId=:clusterId"),
+    @NamedQuery(name = "ServiceConfigEntity.findAllServiceConfigsByStack", query = "SELECT serviceConfig FROM ServiceConfigEntity serviceConfig WHERE serviceConfig.clusterId=:clusterId AND serviceConfig.stack=:stack"),
+    @NamedQuery(name = "ServiceConfigEntity.findLatestServiceConfigsByStack", query = "SELECT serviceConfig FROM ServiceConfigEntity serviceConfig WHERE serviceConfig.clusterId = :clusterId AND serviceConfig.createTimestamp = (SELECT MAX(serviceConfig2.createTimestamp) FROM ServiceConfigEntity serviceConfig2 WHERE serviceConfig2.clusterId=:clusterId AND serviceConfig2.stack=:stack AND serviceConfig2.serviceName = serviceConfig.serviceName)") })
 public class ServiceConfigEntity {
   @Id
   @Column(name = "service_config_id")
@@ -85,12 +89,12 @@ public class ServiceConfigEntity {
   @Column(name = "host_id")
   private List<Long> hostIds;
 
-  @ManyToMany
   @JoinTable(
     name = "serviceconfigmapping",
     joinColumns = {@JoinColumn(name = "service_config_id", referencedColumnName = "service_config_id")},
     inverseJoinColumns = {@JoinColumn(name = "config_id", referencedColumnName = "config_id")}
   )
+  @ManyToMany(cascade = { CascadeType.REMOVE })
   private List<ClusterConfigEntity> clusterConfigEntities;
 
   @ManyToOne
@@ -210,4 +214,46 @@ public class ServiceConfigEntity {
   public void setStack(StackEntity stack) {
     this.stack = stack;
   }
-}
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+
+    result = prime * result
+        + ((serviceConfigId == null) ? 0 : serviceConfigId.hashCode());
+
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (obj == null) {
+      return false;
+    }
+
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+
+    ServiceConfigEntity other = (ServiceConfigEntity) obj;
+    if (serviceConfigId == null) {
+      if (other.serviceConfigId != null) {
+        return false;
+      }
+    } else if (!serviceConfigId.equals(other.serviceConfigId)) {
+      return false;
+    }
+    return true;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/FinalizeUpgradeAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/FinalizeUpgradeAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/FinalizeUpgradeAction.java
index 49e241f..f1af7c4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/FinalizeUpgradeAction.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/FinalizeUpgradeAction.java
@@ -49,6 +49,7 @@ import org.apache.ambari.server.state.ServiceComponent;
 import org.apache.ambari.server.state.ServiceComponentHost;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.UpgradeState;
+import org.apache.ambari.server.state.stack.upgrade.Direction;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostSummary;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.text.StrBuilder;
@@ -65,6 +66,20 @@ public class FinalizeUpgradeAction extends AbstractServerAction {
   public static final String VERSION_KEY = "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 ORIGINAL_STACK_KEY = "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 TARGET_STACK_KEY = "target_stack";
+
+  /**
    * The Cluster that this ServerAction implementation is executing on
    */
   @Inject
@@ -92,11 +107,14 @@ public class FinalizeUpgradeAction extends AbstractServerAction {
         "downgrade".equals(commandParams.get(UPGRADE_DIRECTION_KEY).toLowerCase());
 
     String version = commandParams.get(VERSION_KEY);
+    StackId originalStackId = new StackId(commandParams.get(ORIGINAL_STACK_KEY));
+    StackId targetStackId = new StackId(commandParams.get(TARGET_STACK_KEY));
 
     String clusterName = getExecutionCommand().getClusterName();
 
     if (isDowngrade) {
-      return executeDowngrade(clusterName, version);
+      return executeDowngrade(clusterName, originalStackId, targetStackId,
+          version);
     } else {
       return executeUpgrade(clusterName, version);
     }
@@ -230,11 +248,17 @@ public class FinalizeUpgradeAction extends AbstractServerAction {
 
   /**
    * Execution path for downgrade.
-   * @param clusterName the name of the cluster the downgrade is for
-   * @param version     the target version of the downgrade
+   *
+   * @param clusterName
+   *          the name of the cluster the downgrade is for
+   * @paran originalStackId the stack ID of the cluster before the upgrade.
+   * @paran targetStackId the stack ID that was desired for this upgrade.
+   * @param version
+   *          the target version of the downgrade
    * @return the command report
    */
-  private CommandReport executeDowngrade(String clusterName, String version)
+  private CommandReport executeDowngrade(String clusterName,
+      StackId originalStackId, StackId targetStackId, String version)
       throws AmbariException, InterruptedException {
 
     StringBuilder out = new StringBuilder();
@@ -245,8 +269,16 @@ public class FinalizeUpgradeAction extends AbstractServerAction {
       StackId desiredClusterStackId = cluster.getDesiredStackVersion();
       StackId currentClusterStackId = cluster.getCurrentStackVersion();
 
+      // this was a cross-stack upgrade, meaning that configurations were
+      // created that now need to be removed
+      if (!originalStackId.equals(targetStackId)) {
+        cluster.removeConfigurations(targetStackId);
+      }
+
       // !!! find and make sure the cluster_version EXCEPT current are set back
-      out.append(String.format("Searching for current version for %s\n", clusterName));
+      out.append(String.format("Searching for current version for %s\n",
+          clusterName));
+
       ClusterVersionEntity clusterVersion = clusterVersionDAO.findByClusterAndStateCurrent(clusterName);
       if (null == clusterVersion) {
         throw new AmbariException("Could not find current cluster version");

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
index 209293f..19fe2dd 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
@@ -42,43 +42,43 @@ public interface Cluster {
   /**
    * Get the cluster ID
    */
-  public long getClusterId();
+  long getClusterId();
 
   /**
    * Get the Cluster Name
    */
-  public String getClusterName();
+  String getClusterName();
 
   /**
    * Set the Cluster Name
    */
-  public void setClusterName(String clusterName);
+  void setClusterName(String clusterName);
 
   /**
    * Add a service to a cluster
    * @param service
    */
-  public void addService(Service service) throws AmbariException;
+  void addService(Service service) throws AmbariException;
 
   /**
    * Get a service
    * @param serviceName
    * @return
    */
-  public Service getService(String serviceName) throws AmbariException;
+  Service getService(String serviceName) throws AmbariException;
 
   /**
    * Get all services
    * @return
    */
-  public Map<String, Service> getServices();
+  Map<String, Service> getServices();
 
   /**
    * Get all ServiceComponentHosts on a given host
    * @param hostname
    * @return
    */
-  public List<ServiceComponentHost> getServiceComponentHosts(String hostname);
+  List<ServiceComponentHost> getServiceComponentHosts(String hostname);
 
 
   /**
@@ -87,39 +87,40 @@ public interface Cluster {
    * @param componentName
    * @return
    */
-  public Set<String> getHosts(String serviceName, String componentName);
+  Set<String> getHosts(String serviceName, String componentName);
 
 
   /**
    * Remove ServiceComponentHost from cluster
    * @param svcCompHost
    */
-  public void removeServiceComponentHost(ServiceComponentHost svcCompHost) throws AmbariException;
+  void removeServiceComponentHost(ServiceComponentHost svcCompHost)
+      throws AmbariException;
 
 
   /**
    * Get the ClusterVersionEntity object whose state is CURRENT.
    * @return
    */
-  public ClusterVersionEntity getCurrentClusterVersion();
+  ClusterVersionEntity getCurrentClusterVersion();
 
   /**
    * Get all of the ClusterVersionEntity objects for the cluster.
    * @return
    */
-  public Collection<ClusterVersionEntity> getAllClusterVersions();
+  Collection<ClusterVersionEntity> getAllClusterVersions();
 
   /**
    * Get desired stack version
    * @return
    */
-  public StackId getDesiredStackVersion();
+  StackId getDesiredStackVersion();
 
   /**
    * Set desired stack version
    * @param stackVersion
    */
-  public void setDesiredStackVersion(StackId stackVersion) throws AmbariException;
+  void setDesiredStackVersion(StackId stackVersion) throws AmbariException;
 
   /**
    * Sets the desired stack version, optionally setting all owned services,
@@ -134,13 +135,13 @@ public interface Cluster {
    * Get current stack version
    * @return
    */
-  public StackId getCurrentStackVersion();
+  StackId getCurrentStackVersion();
 
   /**
    * Set current stack version
    * @param stackVersion
    */
-  public void setCurrentStackVersion(StackId stackVersion) throws AmbariException;
+  void setCurrentStackVersion(StackId stackVersion) throws AmbariException;
 
   /**
    * Create host versions for all of the hosts that don't already have the stack version.
@@ -149,7 +150,9 @@ public interface Cluster {
    * @param desiredState Desired state must be {@link RepositoryVersionState#CURRENT} or {@link RepositoryVersionState#UPGRADING}
    * @throws AmbariException
    */
-  public void mapHostVersions(Set<String> hostNames, ClusterVersionEntity currentClusterVersion, RepositoryVersionState desiredState) throws AmbariException;
+  void mapHostVersions(Set<String> hostNames,
+      ClusterVersionEntity currentClusterVersion,
+      RepositoryVersionState desiredState) throws AmbariException;
 
   /**
    * Create/update host versions for all of the hosts within a cluster based on state of cluster stack version.
@@ -160,7 +163,8 @@ public interface Cluster {
    * of a cluster version is {@link RepositoryVersionState#INSTALLING}
    * @throws AmbariException
    */
-  public void inferHostVersions(ClusterVersionEntity sourceClusterVersion) throws AmbariException;
+  void inferHostVersions(ClusterVersionEntity sourceClusterVersion)
+      throws AmbariException;
 
   /**
    * For a given host, will either either update an existing Host Version Entity for the given version, or create
@@ -172,7 +176,9 @@ public interface Cluster {
    * @return Returns either the newly created or the updated Host Version Entity.
    * @throws AmbariException
    */
-  public HostVersionEntity transitionHostVersionState(HostEntity host, final RepositoryVersionEntity repositoryVersion, final StackId stack) throws AmbariException;
+  HostVersionEntity transitionHostVersionState(HostEntity host,
+      final RepositoryVersionEntity repositoryVersion, final StackId stack)
+      throws AmbariException;
 
   /**
    * Update state of a cluster stack version for cluster based on states of host versions and stackids.
@@ -186,7 +192,7 @@ public interface Cluster {
    * Update state of all cluster stack versions for cluster based on states of host versions.
    * @throws AmbariException
    */
-  public void recalculateAllClusterVersionStates() throws AmbariException;
+  void recalculateAllClusterVersionStates() throws AmbariException;
 
   /**
    * Create a cluster version for the given stack and version, whose initial
@@ -204,7 +210,7 @@ public interface Cluster {
    *          Initial state
    * @throws AmbariException
    */
-  public void createClusterVersion(StackId stackId, String version,
+  void createClusterVersion(StackId stackId, String version,
       String userName, RepositoryVersionState state) throws AmbariException;
 
   /**
@@ -218,7 +224,7 @@ public interface Cluster {
    *          Desired state
    * @throws AmbariException
    */
-  public void transitionClusterVersion(StackId stackId, String version,
+  void transitionClusterVersion(StackId stackId, String version,
       RepositoryVersionState state) throws AmbariException;
 
   /**
@@ -228,7 +234,7 @@ public interface Cluster {
    * @return either {@link State#INIT} or {@link State#INSTALLED}, never
    *         {@code null}.
    */
-  public State getProvisioningState();
+  State getProvisioningState();
 
   /**
    * Sets the provisioning state of the cluster.
@@ -236,21 +242,21 @@ public interface Cluster {
    * @param provisioningState
    *          the provisioning state, not {@code null}.
    */
-  public void setProvisioningState(State provisioningState);
+  void setProvisioningState(State provisioningState);
 
   /**
    * Gets the cluster's security type.
    *
    * @return this Cluster's security type
    */
-  public SecurityType getSecurityType();
+  SecurityType getSecurityType();
 
   /**
    * Sets this Cluster's security type.
    *
    * @param securityType a SecurityType to set
    */
-  public void setSecurityType(SecurityType securityType);
+  void setSecurityType(SecurityType securityType);
 
   /**
    * Gets all configs that match the specified type.  Result is not the
@@ -258,7 +264,7 @@ public interface Cluster {
    * @param configType  the config type to return
    * @return  a map of configuration objects that have been set for the given type
    */
-  public Map<String, Config> getConfigsByType(String configType);
+  Map<String, Config> getConfigsByType(String configType);
 
   /**
    * Gets the specific config that matches the specified type and tag.  This not
@@ -268,20 +274,20 @@ public interface Cluster {
    * @return  a {@link Config} object, or <code>null</code> if the specific type
    *          and version have not been set.
    */
-  public Config getConfig(String configType, String versionTag);
+  Config getConfig(String configType, String versionTag);
 
   /**
    * Sets a specific config.  NOTE:  This is not a DESIRED configuration that
    * applies to a cluster.
    * @param config  the config instance to add
    */
-  public void addConfig(Config config);
+  void addConfig(Config config);
 
   /**
    * Gets all configurations defined for a cluster.
    * @return  the collection of all configs that have been defined.
    */
-  public Collection<Config> getAllConfigs();
+  Collection<Config> getAllConfigs();
 
   /**
    * Adds and sets a DESIRED configuration to be applied to a cluster.  There
@@ -291,7 +297,7 @@ public interface Cluster {
    * @return <code>true</code> if the config was added, or <code>false</code>
    * if the config is already set as the current
    */
-  public ServiceConfigVersionResponse addDesiredConfig(String user, Set<Config> configs);
+  ServiceConfigVersionResponse addDesiredConfig(String user, Set<Config> configs);
 
   /**
    * Adds and sets a DESIRED configuration to be applied to a cluster.  There
@@ -338,13 +344,13 @@ public interface Cluster {
    * @return  the {@link Config} instance, or <code>null</code> if the type has
    * not been set.
    */
-  public Config getDesiredConfigByType(String configType);
+  Config getDesiredConfigByType(String configType);
 
   /**
    * Gets the desired configurations for the cluster.
    * @return a map of type-to-configuration information.
    */
-  public Map<String, DesiredConfig> getDesiredConfigs();
+  Map<String, DesiredConfig> getDesiredConfigs();
 
 
   /**
@@ -352,43 +358,43 @@ public interface Cluster {
    * @return
    * @throws AmbariException
    */
-  public ClusterResponse convertToResponse() throws AmbariException;
+  ClusterResponse convertToResponse() throws AmbariException;
 
   /**
    * Refreshes the cluster details
    */
-  public void refresh();
+  void refresh();
 
   /**
    * Creates a debug dump based on the current cluster state
    * @param sb
    */
-  public void debugDump(StringBuilder sb);
+  void debugDump(StringBuilder sb);
 
   /**
    * Delete all the services associated with this cluster
    * @throws AmbariException
    */
-  public void deleteAllServices() throws AmbariException;
+  void deleteAllServices() throws AmbariException;
 
   /**
    * Delete the named service associated with this cluster
    * @param serviceName
    * @throws AmbariException
    */
-  public void deleteService(String serviceName) throws AmbariException;
+  void deleteService(String serviceName) throws AmbariException;
 
   /**
    * Gets if the cluster can be deleted
    * @return
    */
-  public boolean canBeRemoved();
+  boolean canBeRemoved();
 
   /**
    * Delete the cluster
    * @throws AmbariException
    */
-  public void delete() throws AmbariException;
+  void delete() throws AmbariException;
 
   /**
    * Add service to the cluster
@@ -423,47 +429,49 @@ public interface Cluster {
    * @param configGroup
    * @throws AmbariException
    */
-  public void addConfigGroup(ConfigGroup configGroup) throws AmbariException;
+  void addConfigGroup(ConfigGroup configGroup) throws AmbariException;
 
   /**
    * Get config groups associated with this cluster
    * @return unmodifiable map of config group id to config group.  Will not return null.
    */
-  public Map<Long, ConfigGroup> getConfigGroups();
+  Map<Long, ConfigGroup> getConfigGroups();
 
   /**
    * Delete this config group identified by the config group id
    * @param id
    * @throws AmbariException
    */
-  public void deleteConfigGroup(Long id) throws AmbariException;
+  void deleteConfigGroup(Long id) throws AmbariException;
 
   /**
    * Find all config groups associated with the give hostname
    * @param hostname
    * @return Map of config group id to config group
    */
-  public Map<Long, ConfigGroup> getConfigGroupsByHostname(String hostname) throws AmbariException;
+  Map<Long, ConfigGroup> getConfigGroupsByHostname(String hostname)
+      throws AmbariException;
 
   /**
    * Add a @RequestExecution to the cluster
    * @param requestExecution
    * @throws AmbariException
    */
-  public void addRequestExecution(RequestExecution requestExecution) throws AmbariException;
+  void addRequestExecution(RequestExecution requestExecution)
+      throws AmbariException;
 
   /**
    * Get all @RequestExecution objects associated with the cluster
    * @return
    */
-  public Map<Long, RequestExecution> getAllRequestExecutions();
+  Map<Long, RequestExecution> getAllRequestExecutions();
 
   /**
    * Delete a @RequestExecution associated with the cluster
    * @param id
    * @throws AmbariException
    */
-  public void deleteRequestExecution(Long id) throws AmbariException;
+  void deleteRequestExecution(Long id) throws AmbariException;
 
   /**
    * Get next version of specified config type
@@ -489,14 +497,14 @@ public interface Cluster {
    *
    * @return true if the access to this cluster is allowed
    */
-  public boolean checkPermission(PrivilegeEntity privilegeEntity, boolean readOnly);
+  boolean checkPermission(PrivilegeEntity privilegeEntity, boolean readOnly);
 
   /**
    * Add the given map of attributes to the session for this cluster.
    *
    * @param attributes  the session attributes
    */
-  public void addSessionAttributes(Map<String, Object> attributes);
+  void addSessionAttributes(Map<String, Object> attributes);
 
   /**
    * Sets or adds an attribute in the session for this cluster
@@ -504,20 +512,43 @@ public interface Cluster {
    * @param key   the name of the key which identifies the attribute in the map
    * @param value the value to set
    */
-  public void setSessionAttribute(String key, Object value);
+  void setSessionAttribute(String key, Object value);
 
   /**
    * Removes an attribute from the session for this cluster
    *
    * @param key the name of the key which identifies the attribute in the map
    */
-  public void removeSessionAttribute(String key);
+  void removeSessionAttribute(String key);
 
   /**
    * Get the map of session attributes for this cluster.
    *
    * @return the map of session attributes for this cluster; never null
    */
-  public Map<String, Object> getSessionAttributes();
+  Map<String, Object> getSessionAttributes();
 
+  /**
+   * Makes the most recent configurations in the specified stack the current set
+   * of configurations. This method will first ensure that the cluster's current
+   * stack matches that of the configuration stack specified.
+   * <p/>
+   * When completed, all other configurations for any other stack will remain,
+   * but will not be marked as selected.
+   *
+   * @param stackId
+   *          the stack to use when finding the latest configurations (not
+   *          {@code null}).
+   */
+  void applyLatestConfigurations(StackId stackId);
+
+  /**
+   * Removes all cluster configurations and service configurations that belong
+   * to the specified stack.
+   *
+   * @param stackId
+   *          the stack to use when finding the configurations to remove (not
+   *          {@code null}).
+   */
+  void removeConfigurations(StackId stackId);
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/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 6436e22..6dbde09 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
@@ -32,6 +32,21 @@ import org.apache.ambari.server.state.stack.upgrade.Direction;
 public class UpgradeContext {
 
   private String m_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}.
+   */
+  private StackId m_originalStackId;
+
+  /**
+   * 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}.
+   */
+  private StackId m_targetStackId;
+
   private Direction m_direction;
   private MasterHostResolver m_resolver;
   private AmbariMetaInfo m_metaInfo;
@@ -41,13 +56,30 @@ public class UpgradeContext {
 
   /**
    * Constructor.
-   * @param resolver  the resolver that also references the required cluster
-   * @param version   the target version to upgrade to
-   * @param direction the direction for the upgrade
+   *
+   * @param resolver
+   *          the resolver that also references the required cluster
+   * @param sourceStackId
+   *          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} (not {@code null}).
+   * @param targetStackId
+   *          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} (not
+   *          {@code null}).
+   * @param version
+   *          the target version to upgrade to
+   * @param direction
+   *          the direction for the upgrade
    */
-  public UpgradeContext(MasterHostResolver resolver, String version,
+  public UpgradeContext(MasterHostResolver resolver, StackId sourceStackId,
+      StackId targetStackId, String version,
       Direction direction) {
     m_version = version;
+    m_originalStackId = sourceStackId;
+    m_targetStackId = targetStackId;
     m_direction = direction;
     m_resolver = resolver;
   }
@@ -102,6 +134,36 @@ public class UpgradeContext {
   }
 
   /**
+   * @return the originalStackId
+   */
+  public StackId getOriginalStackId() {
+    return m_originalStackId;
+  }
+
+  /**
+   * @param originalStackId
+   *          the originalStackId to set
+   */
+  public void setOriginalStackId(StackId originalStackId) {
+    m_originalStackId = originalStackId;
+  }
+
+  /**
+   * @return the targetStackId
+   */
+  public StackId getTargetStackId() {
+    return m_targetStackId;
+  }
+
+  /**
+   * @param targetStackId
+   *          the targetStackId to set
+   */
+  public void setTargetStackId(StackId targetStackId) {
+    m_targetStackId = targetStackId;
+  }
+
+  /**
    * @return a map of host to list of components.
    */
   public Map<String, List<String>> getUnhealthy() {

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
index bb6f6c1..39219a3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
@@ -259,24 +259,13 @@ public class ClusterImpl implements Cluster {
 
     serviceComponentHosts = new HashMap<String,
       Map<String, Map<String, ServiceComponentHost>>>();
+
     serviceComponentHostsByHost = new HashMap<String,
       List<ServiceComponentHost>>();
 
     desiredStackVersion = new StackId(clusterEntity.getDesiredStack());
 
-    allConfigs = new HashMap<String, Map<String, Config>>();
-    if (!clusterEntity.getClusterConfigEntities().isEmpty()) {
-      for (ClusterConfigEntity entity : clusterEntity.getClusterConfigEntities()) {
-
-        if (!allConfigs.containsKey(entity.getType())) {
-          allConfigs.put(entity.getType(), new HashMap<String, Config>());
-        }
-
-        Config config = configFactory.createExisting(this, entity);
-
-        allConfigs.get(entity.getType()).put(entity.getTag(), config);
-      }
-    }
+    cacheConfigurations();
 
     if (desiredStackVersion != null && !StringUtils.isEmpty(desiredStackVersion.getStackName()) && !
       StringUtils.isEmpty(desiredStackVersion.getStackVersion())) {
@@ -2677,4 +2666,136 @@ public class ClusterImpl implements Cluster {
   private String getClusterSessionAttributeName() {
     return CLUSTER_SESSION_ATTRIBUTES_PREFIX + getClusterName();
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  @Transactional
+  public void applyLatestConfigurations(StackId stackId) {
+    clusterGlobalLock.writeLock().lock();
+    try {
+      Collection<ClusterConfigMappingEntity> configMappingEntities = clusterEntity.getConfigMappingEntities();
+
+      // disable previous config
+      for (ClusterConfigMappingEntity e : configMappingEntities) {
+        e.setSelected(0);
+      }
+
+      List<ClusterConfigEntity> clusterConfigsToMakeSelected = clusterDAO.getLatestConfigurations(
+          clusterEntity.getClusterId(), stackId);
+
+      for( ClusterConfigEntity clusterConfigToMakeSelected : clusterConfigsToMakeSelected ){
+        for (ClusterConfigMappingEntity configMappingEntity : configMappingEntities) {
+          String tag = configMappingEntity.getTag();
+          String type = configMappingEntity.getType();
+
+          if (clusterConfigToMakeSelected.getTag().equals(tag)
+              && clusterConfigToMakeSelected.getType().equals(type)) {
+            configMappingEntity.setSelected(1);
+          }
+        }
+      }
+
+      clusterDAO.merge(clusterEntity);
+
+      cacheConfigurations();
+    } finally {
+      clusterGlobalLock.writeLock().unlock();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  @Transactional
+  public void removeConfigurations(StackId stackId) {
+    clusterGlobalLock.writeLock().lock();
+    try {
+      long clusterId = clusterEntity.getClusterId();
+
+      // this will keep track of cluster config mappings that need removal
+      // since there is no relationship between configs and their mappings, we
+      // have to do it manually
+      List<ClusterConfigEntity> removedClusterConfigs = new ArrayList<ClusterConfigEntity>(50);
+      Collection<ClusterConfigEntity> clusterConfigEntities = clusterEntity.getClusterConfigEntities();
+
+      List<ClusterConfigEntity> clusterConfigs = clusterDAO.getAllConfigurations(
+          clusterId, stackId);
+
+      // remove any lefover cluster configurations that don't have a service
+      // configuration (like cluster-env)
+      for (ClusterConfigEntity clusterConfig : clusterConfigs) {
+        clusterDAO.removeConfig(clusterConfig);
+        clusterConfigEntities.remove(clusterConfig);
+
+        removedClusterConfigs.add(clusterConfig);
+      }
+
+      clusterEntity = clusterDAO.merge(clusterEntity);
+
+      List<ServiceConfigEntity> serviceConfigs = serviceConfigDAO.getAllServiceConfigs(
+          clusterId, stackId);
+
+      // remove all service configurations
+      Collection<ServiceConfigEntity> serviceConfigEntities = clusterEntity.getServiceConfigEntities();
+      for (ServiceConfigEntity serviceConfig : serviceConfigs) {
+        serviceConfigDAO.remove(serviceConfig);
+        serviceConfigEntities.remove(serviceConfig);
+      }
+
+      clusterEntity = clusterDAO.merge(clusterEntity);
+
+      // remove config mappings
+      Collection<ClusterConfigMappingEntity> configMappingEntities = clusterEntity.getConfigMappingEntities();
+      for (ClusterConfigEntity removedClusterConfig : removedClusterConfigs) {
+        String removedClusterConfigType = removedClusterConfig.getType();
+        String removedClusterConfigTag = removedClusterConfig.getTag();
+
+        Iterator<ClusterConfigMappingEntity> clusterConfigMappingIterator = configMappingEntities.iterator();
+        while (clusterConfigMappingIterator.hasNext()) {
+          ClusterConfigMappingEntity clusterConfigMapping = clusterConfigMappingIterator.next();
+          String mappingType = clusterConfigMapping.getType();
+          String mappingTag = clusterConfigMapping.getTag();
+
+          if (removedClusterConfigTag.equals(mappingTag)
+              && removedClusterConfigType.equals(mappingType)) {
+            clusterConfigMappingIterator.remove();
+            clusterDAO.removeConfigMapping(clusterConfigMapping);
+          }
+        }
+      }
+
+      clusterEntity = clusterDAO.merge(clusterEntity);
+
+      cacheConfigurations();
+    } finally {
+      clusterGlobalLock.writeLock().unlock();
+    }
+  }
+
+  /**
+   * Caches all of the {@link ClusterConfigEntity}s in {@link #allConfigs}.
+   */
+  private void cacheConfigurations() {
+    if (null == allConfigs) {
+      allConfigs = new HashMap<String, Map<String, Config>>();
+    }
+
+    allConfigs.clear();
+
+    if (!clusterEntity.getClusterConfigEntities().isEmpty()) {
+      for (ClusterConfigEntity entity : clusterEntity.getClusterConfigEntities()) {
+
+        if (!allConfigs.containsKey(entity.getType())) {
+          allConfigs.put(entity.getType(), new HashMap<String, Config>());
+        }
+
+        Config config = configFactory.createExisting(this, entity);
+
+        allConfigs.get(entity.getType()).put(entity.getTag(), config);
+      }
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
index e7a5185..3d93e4d 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
@@ -31,6 +31,7 @@ import org.apache.ambari.server.orm.entities.ResourceEntity;
 import org.apache.ambari.server.orm.entities.ResourceTypeEntity;
 import org.apache.ambari.server.orm.entities.ServiceConfigEntity;
 import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.state.StackId;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,6 +41,9 @@ import com.google.inject.Injector;
 import com.google.inject.persist.PersistService;
 
 public class ServiceConfigDAOTest {
+  private static final StackId HDP_01 = new StackId("HDP", "0.1");
+  private static final StackId HDP_02 = new StackId("HDP", "0.2");
+
   private Injector injector;
   private ServiceConfigDAO serviceConfigDAO;
   private ClusterDAO clusterDAO;
@@ -79,13 +83,14 @@ public class ServiceConfigDAOTest {
       resourceTypeEntity = resourceTypeDAO.merge(resourceTypeEntity);
     }
 
-    StackEntity stackEntity = stackDAO.find("HDP", "0.1");
-
     ResourceEntity resourceEntity = new ResourceEntity();
     resourceEntity.setResourceType(resourceTypeEntity);
 
     ClusterEntity  clusterEntity = clusterDAO.findByName("c1");
     if (clusterEntity == null) {
+      StackEntity stackEntity = stackDAO.find(HDP_01.getStackName(),
+          HDP_01.getStackVersion());
+
       clusterEntity = new ClusterEntity();
       clusterEntity.setClusterName("c1");
       clusterEntity.setResource(resourceEntity);
@@ -283,4 +288,49 @@ public class ServiceConfigDAOTest {
     }
   }
 
+  @Test
+  public void testGetAllServiceConfigs() throws Exception {
+    ServiceConfigEntity serviceConfigEntity = null;
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 1L, 1L, 10L, null);
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 2L, 2L, 20L, null);
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 3L, 3L, 30L, null);
+    serviceConfigEntity = createServiceConfig("YARN", "admin", 1L, 4L, 40L, null);
+
+    long clusterId = serviceConfigEntity.getClusterId();
+
+    List<ServiceConfigEntity> serviceConfigs = serviceConfigDAO.getAllServiceConfigs(clusterId, HDP_01);
+    Assert.assertEquals(4, serviceConfigs.size());
+
+    serviceConfigs = serviceConfigDAO.getAllServiceConfigs(clusterId, HDP_02);
+    Assert.assertEquals(0, serviceConfigs.size());
+  }
+
+  @Test
+  public void testGetLatestServiceConfigs() throws Exception {
+    ServiceConfigEntity serviceConfigEntity = null;
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 1L, 1L, 10L, null);
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 2L, 2L, 20L, null);
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 3L, 3L, 30L, null);
+    serviceConfigEntity = createServiceConfig("YARN", "admin", 1L, 4L, 40L, null);
+
+    StackEntity stackEntity = stackDAO.find(HDP_02.getStackName(),
+        HDP_02.getStackVersion());
+
+    ClusterEntity clusterEntity = serviceConfigEntity.getClusterEntity();
+    clusterEntity.setDesiredStack(stackEntity);
+    clusterDAO.merge(clusterEntity);
+
+    // create some for HDP 0.2
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 4L, 5L, 50L, null);
+    serviceConfigEntity = createServiceConfig("HDFS", "admin", 5L, 6L, 60L, null);
+    serviceConfigEntity = createServiceConfig("YARN", "admin", 2L, 7L, 70L, null);
+
+    long clusterId = serviceConfigEntity.getClusterId();
+
+    List<ServiceConfigEntity> serviceConfigs = serviceConfigDAO.getLatestServiceConfigs(clusterId, HDP_01);
+    Assert.assertEquals(2, serviceConfigs.size());
+
+    serviceConfigs = serviceConfigDAO.getLatestServiceConfigs(clusterId, HDP_02);
+    Assert.assertEquals(2, serviceConfigs.size());
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/ac6fd63f/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/UpgradeActionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/UpgradeActionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/UpgradeActionTest.java
index fc86c7a..e03c2f5 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/UpgradeActionTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/UpgradeActionTest.java
@@ -21,10 +21,14 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.ServiceComponentNotFoundException;
+import org.apache.ambari.server.ServiceNotFoundException;
 import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
@@ -46,11 +50,21 @@ import org.apache.ambari.server.orm.entities.HostVersionEntity;
 import org.apache.ambari.server.orm.entities.StackEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Config;
+import org.apache.ambari.server.state.ConfigImpl;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.RepositoryInfo;
 import org.apache.ambari.server.state.RepositoryVersionState;
+import org.apache.ambari.server.state.Service;
+import org.apache.ambari.server.state.ServiceComponent;
+import org.apache.ambari.server.state.ServiceComponentFactory;
+import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.ServiceComponentHostFactory;
+import org.apache.ambari.server.state.ServiceFactory;
 import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.State;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -76,7 +90,7 @@ public class UpgradeActionTest {
   private Injector m_injector;
 
   @Inject
-  private OrmTestHelper helper;
+  private OrmTestHelper m_helper;
 
   @Inject
   private RepositoryVersionDAO repoVersionDAO;
@@ -93,18 +107,20 @@ public class UpgradeActionTest {
   @Inject
   private HostRoleCommandFactory hostRoleCommandFactory;
 
+  @Inject
+  private ServiceFactory serviceFactory;
+
+  @Inject
+  private ServiceComponentFactory serviceComponentFactory;
+
+  @Inject
+  private ServiceComponentHostFactory serviceComponentHostFactory;
+
   @Before
   public void setup() throws Exception {
     m_injector = Guice.createInjector(new InMemoryDefaultTestModule());
     m_injector.getInstance(GuiceJpaInitializer.class);
-
-    helper = m_injector.getInstance(OrmTestHelper.class);
-
-    repoVersionDAO = m_injector.getInstance(RepositoryVersionDAO.class);
-    clusterVersionDAO = m_injector.getInstance(ClusterVersionDAO.class);
-    hostVersionDAO = m_injector.getInstance(HostVersionDAO.class);
-    hostDAO = m_injector.getInstance(HostDAO.class);
-    hostRoleCommandFactory = m_injector.getInstance(HostRoleCommandFactory.class);
+    m_injector.injectMembers(this);
   }
 
   @After
@@ -132,8 +148,8 @@ public class UpgradeActionTest {
     host.setHostAttributes(hostAttributes);
     host.persist();
 
-    helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_0_0);
-    helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_1_0);
+    m_helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_0_0);
+    m_helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_1_0);
 
     c.createClusterVersion(HDP_21_STACK, HDP_2_2_0_0, "admin", RepositoryVersionState.UPGRADING);
     c.createClusterVersion(HDP_21_STACK, HDP_2_2_1_0, "admin", RepositoryVersionState.INSTALLING);
@@ -147,8 +163,7 @@ public class UpgradeActionTest {
 
     HostVersionEntity entity = new HostVersionEntity();
     entity.setHostEntity(hostDAO.findByName(hostName));
-    entity.setRepositoryVersion(
-        repoVersionDAO.findByStackAndVersion(HDP_21_STACK, HDP_2_2_1_0));
+    entity.setRepositoryVersion(repoVersionDAO.findByStackAndVersion(HDP_21_STACK, HDP_2_2_1_0));
     entity.setState(RepositoryVersionState.UPGRADING);
     hostVersionDAO.create(entity);
   }
@@ -180,14 +195,13 @@ public class UpgradeActionTest {
     host.setHostAttributes(hostAttributes);
     host.persist();
 
-    String urlInfo = "[{'repositories':[" +
-        "{'Repositories/base_url':'http://foo1','Repositories/repo_name':'HDP','Repositories/repo_id':'HDP-2.1.1'}" +
-        "], 'OperatingSystems/os_type':'redhat6'}]";
+    String urlInfo = "[{'repositories':["
+        + "{'Repositories/base_url':'http://foo1','Repositories/repo_name':'HDP','Repositories/repo_id':'HDP-2.1.1'}"
+        + "], 'OperatingSystems/os_type':'redhat6'}]";
 
-    helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_0_0);
-    repoVersionDAO.create(stackEntity, HDP_2_2_1_0,
-        String.valueOf(System.currentTimeMillis()), "pack",
-          urlInfo);
+    m_helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_2_0_0);
+    repoVersionDAO.create(stackEntity, HDP_2_2_1_0, String.valueOf(System.currentTimeMillis()),
+        "pack", urlInfo);
 
     c.createClusterVersion(HDP_21_STACK, HDP_2_2_0_0, "admin", RepositoryVersionState.UPGRADING);
     c.createClusterVersion(HDP_21_STACK, HDP_2_2_1_0, "admin", RepositoryVersionState.INSTALLING);
@@ -205,8 +219,7 @@ public class UpgradeActionTest {
 
     HostVersionEntity entity = new HostVersionEntity();
     entity.setHostEntity(hostDAO.findByName(hostName));
-    entity.setRepositoryVersion(
-        repoVersionDAO.findByStackAndVersion(HDP_21_STACK, HDP_2_2_1_0));
+    entity.setRepositoryVersion(repoVersionDAO.findByStackAndVersion(HDP_21_STACK, HDP_2_2_1_0));
     entity.setState(RepositoryVersionState.UPGRADED);
     hostVersionDAO.create(entity);
   }
@@ -230,7 +243,6 @@ public class UpgradeActionTest {
 
     // add a host component
     clusters.addHost(hostName);
-
     Host host = clusters.getHost(hostName);
 
     Map<String, String> hostAttributes = new HashMap<String, String>();
@@ -239,16 +251,17 @@ public class UpgradeActionTest {
     host.setHostAttributes(hostAttributes);
     host.persist();
 
-    String urlInfo = "[{'repositories':[" +
-        "{'Repositories/base_url':'http://foo1','Repositories/repo_name':'HDP','Repositories/repo_id':'HDP-2.1.1'}" +
-        "], 'OperatingSystems/os_type':'redhat6'}]";
+    clusters.mapHostToCluster(hostName, clusterName);
+
+    String urlInfo = "[{'repositories':["
+        + "{'Repositories/base_url':'http://foo1','Repositories/repo_name':'HDP','Repositories/repo_id':'HDP-2.1.1'}"
+        + "], 'OperatingSystems/os_type':'redhat6'}]";
 
-    helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_1_1_0);
-    helper.getOrCreateRepositoryVersion(HDP_22_STACK, HDP_2_2_1_0);
+    m_helper.getOrCreateRepositoryVersion(HDP_21_STACK, HDP_2_1_1_0);
+    m_helper.getOrCreateRepositoryVersion(HDP_22_STACK, HDP_2_2_1_0);
 
-    repoVersionDAO.create(stackEntity, HDP_2_2_1_0,
-        String.valueOf(System.currentTimeMillis()), "pack",
-          urlInfo);
+    repoVersionDAO.create(stackEntity, HDP_2_2_1_0, String.valueOf(System.currentTimeMillis()),
+        "pack", urlInfo);
 
     c.createClusterVersion(HDP_21_STACK, HDP_2_1_1_0, "admin", RepositoryVersionState.UPGRADING);
     c.createClusterVersion(HDP_22_STACK, HDP_2_2_1_0, "admin", RepositoryVersionState.INSTALLING);
@@ -265,29 +278,25 @@ public class UpgradeActionTest {
 
     HostVersionEntity entity = new HostVersionEntity();
     entity.setHostEntity(hostDAO.findByName(hostName));
-    entity.setRepositoryVersion(
-        repoVersionDAO.findByStackAndVersion(HDP_22_STACK, HDP_2_2_1_0));
+    entity.setRepositoryVersion(repoVersionDAO.findByStackAndVersion(HDP_22_STACK, HDP_2_2_1_0));
     entity.setState(RepositoryVersionState.UPGRADED);
     hostVersionDAO.create(entity);
   }
 
-
   @Test
   public void testFinalizeDowngrade() throws Exception {
     makeDowngradeCluster();
 
     Map<String, String> commandParams = new HashMap<String, String>();
-    commandParams.put("upgrade_direction", "downgrade");
-    commandParams.put("version", HDP_2_2_0_0);
+    commandParams.put(FinalizeUpgradeAction.UPGRADE_DIRECTION_KEY, "downgrade");
+    commandParams.put(FinalizeUpgradeAction.VERSION_KEY, HDP_2_2_0_0);
 
     ExecutionCommand executionCommand = new ExecutionCommand();
     executionCommand.setCommandParams(commandParams);
     executionCommand.setClusterName("c1");
 
-    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null,
-        null, null);
-    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(
-        executionCommand));
+    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null, null, null);
+    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(executionCommand));
 
     FinalizeUpgradeAction action = m_injector.getInstance(FinalizeUpgradeAction.class);
     action.setExecutionCommand(executionCommand);
@@ -297,12 +306,10 @@ public class UpgradeActionTest {
     assertNotNull(report);
     assertEquals(HostRoleStatus.COMPLETED.name(), report.getStatus());
 
-    for (HostVersionEntity entity : hostVersionDAO.findByClusterAndHost("c1",
-        "h1")) {
+    for (HostVersionEntity entity : hostVersionDAO.findByClusterAndHost("c1", "h1")) {
       if (entity.getRepositoryVersion().getVersion().equals(HDP_2_2_0_0)) {
         assertEquals(RepositoryVersionState.CURRENT, entity.getState());
-      } else if (entity.getRepositoryVersion().getVersion().equals(
-HDP_2_2_1_0)) {
+      } else if (entity.getRepositoryVersion().getVersion().equals(HDP_2_2_1_0)) {
         assertEquals(RepositoryVersionState.INSTALLED, entity.getState());
       }
     }
@@ -310,8 +317,7 @@ HDP_2_2_1_0)) {
     for (ClusterVersionEntity entity : clusterVersionDAO.findByCluster("c1")) {
       if (entity.getRepositoryVersion().getVersion().equals(HDP_2_2_0_0)) {
         assertEquals(RepositoryVersionState.CURRENT, entity.getState());
-      } else if (entity.getRepositoryVersion().getVersion().equals(
-HDP_2_2_1_0)) {
+      } else if (entity.getRepositoryVersion().getVersion().equals(HDP_2_2_1_0)) {
         assertEquals(RepositoryVersionState.INSTALLED, entity.getState());
       }
     }
@@ -322,17 +328,15 @@ HDP_2_2_1_0)) {
     makeUpgradeCluster();
 
     Map<String, String> commandParams = new HashMap<String, String>();
-    commandParams.put("upgrade_direction", "upgrade");
-    commandParams.put("version", HDP_2_2_1_0);
+    commandParams.put(FinalizeUpgradeAction.UPGRADE_DIRECTION_KEY, "upgrade");
+    commandParams.put(FinalizeUpgradeAction.VERSION_KEY, HDP_2_2_1_0);
 
     ExecutionCommand executionCommand = new ExecutionCommand();
     executionCommand.setCommandParams(commandParams);
     executionCommand.setClusterName("c1");
 
-    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null,
-        null, null);
-    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(
-        executionCommand));
+    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null, null, null);
+    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(executionCommand));
 
     FinalizeUpgradeAction action = m_injector.getInstance(FinalizeUpgradeAction.class);
     action.setExecutionCommand(executionCommand);
@@ -345,10 +349,8 @@ HDP_2_2_1_0)) {
     // !!! verify the metainfo url has not been updated, but an output command
     // has
     AmbariMetaInfo metaInfo = m_injector.getInstance(AmbariMetaInfo.class);
-    RepositoryInfo repo = metaInfo.getRepository("HDP", "2.1.1", "redhat6",
-        "HDP-2.1.1");
-    assertEquals(
-        "http://s3.amazonaws.com/dev.hortonworks.com/HDP/centos6/2.x/BUILDS/2.1.1.0-118",
+    RepositoryInfo repo = metaInfo.getRepository("HDP", "2.1.1", "redhat6", "HDP-2.1.1");
+    assertEquals("http://s3.amazonaws.com/dev.hortonworks.com/HDP/centos6/2.x/BUILDS/2.1.1.0-118",
         repo.getBaseUrl());
 
     // !!! verify that a command will return the correct host info
@@ -384,18 +386,18 @@ HDP_2_2_1_0)) {
     cluster.setDesiredStackVersion(HDP_22_STACK);
 
     Map<String, String> commandParams = new HashMap<String, String>();
-    commandParams.put("upgrade_direction", "upgrade");
-    commandParams.put("version", HDP_2_2_1_0);
+    commandParams.put(FinalizeUpgradeAction.UPGRADE_DIRECTION_KEY, "upgrade");
+    commandParams.put(FinalizeUpgradeAction.VERSION_KEY, HDP_2_2_1_0);
+    commandParams.put(FinalizeUpgradeAction.ORIGINAL_STACK_KEY, HDP_21_STACK.getStackId());
+    commandParams.put(FinalizeUpgradeAction.TARGET_STACK_KEY, HDP_22_STACK.getStackId());
 
     ExecutionCommand executionCommand = new ExecutionCommand();
     executionCommand.setCommandParams(commandParams);
     executionCommand.setClusterName("c1");
 
-    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null,
-        null, null);
+    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null, null, null);
 
-    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(
-        executionCommand));
+    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(executionCommand));
 
     FinalizeUpgradeAction action = m_injector.getInstance(FinalizeUpgradeAction.class);
     action.setExecutionCommand(executionCommand);
@@ -408,8 +410,149 @@ HDP_2_2_1_0)) {
     StackId currentStackId = cluster.getCurrentStackVersion();
     StackId desiredStackId = cluster.getDesiredStackVersion();
 
+    // verify current/desired stacks are updated to the new stack
     assertEquals(desiredStackId, currentStackId);
     assertEquals(HDP_22_STACK, currentStackId);
     assertEquals(HDP_22_STACK, desiredStackId);
   }
+
+  /**
+   * Tests some of the action items are completed when finalizing downgrade
+   * across stacks (HDP 2.2 -> HDP 2.3).
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testFinalizeDowngradeAcrossStacks() throws Exception {
+    makeCrossStackUpgradeCluster();
+
+    Clusters clusters = m_injector.getInstance(Clusters.class);
+    Cluster cluster = clusters.getCluster("c1");
+
+    // install HDFS with some components
+    Service service = installService(cluster, "HDFS");
+    addServiceComponent(cluster, service, "NAMENODE");
+    addServiceComponent(cluster, service, "DATANODE");
+    createNewServiceComponentHost(cluster, "HDFS", "NAMENODE", "h1");
+    createNewServiceComponentHost(cluster, "HDFS", "DATANODE", "h1");
+
+    // create some configs
+    createConfigs(cluster);
+
+    // setup the cluster for the upgrade across stacks
+    cluster.setCurrentStackVersion(HDP_21_STACK);
+    cluster.setDesiredStackVersion(HDP_22_STACK);
+
+    // now that the desired version is set, we can create some new configs in
+    // the new stack version
+    createConfigs(cluster);
+
+    // verify we have configs in both HDP stacks
+    cluster = clusters.getCluster("c1");
+    Collection<Config> configs = cluster.getAllConfigs();
+    assertEquals(6, configs.size());
+
+    Map<String, String> commandParams = new HashMap<String, String>();
+    commandParams.put(FinalizeUpgradeAction.UPGRADE_DIRECTION_KEY, "downgrade");
+    commandParams.put(FinalizeUpgradeAction.VERSION_KEY, HDP_2_1_1_0);
+    commandParams.put(FinalizeUpgradeAction.ORIGINAL_STACK_KEY, HDP_21_STACK.getStackId());
+    commandParams.put(FinalizeUpgradeAction.TARGET_STACK_KEY, HDP_22_STACK.getStackId());
+
+    ExecutionCommand executionCommand = new ExecutionCommand();
+    executionCommand.setCommandParams(commandParams);
+    executionCommand.setClusterName("c1");
+
+    HostRoleCommand hostRoleCommand = hostRoleCommandFactory.create(null, null, null, null);
+
+    hostRoleCommand.setExecutionCommandWrapper(new ExecutionCommandWrapper(executionCommand));
+
+    FinalizeUpgradeAction action = m_injector.getInstance(FinalizeUpgradeAction.class);
+    action.setExecutionCommand(executionCommand);
+    action.setHostRoleCommand(hostRoleCommand);
+
+    CommandReport report = action.execute(null);
+    assertNotNull(report);
+    assertEquals(HostRoleStatus.COMPLETED.name(), report.getStatus());
+
+    StackId currentStackId = cluster.getCurrentStackVersion();
+    StackId desiredStackId = cluster.getDesiredStackVersion();
+
+    // verify current/desired stacks are back to normal
+    assertEquals(desiredStackId, currentStackId);
+    assertEquals(HDP_21_STACK, currentStackId);
+    assertEquals(HDP_21_STACK, desiredStackId);
+
+    // verify we have configs in only 1 stack
+    cluster = clusters.getCluster("c1");
+    configs = cluster.getAllConfigs();
+    assertEquals(3, configs.size());
+  }
+
+  private ServiceComponentHost createNewServiceComponentHost(Cluster cluster, String svc,
+      String svcComponent, String hostName) throws AmbariException {
+    Assert.assertNotNull(cluster.getConfigGroups());
+    Service s = installService(cluster, svc);
+    ServiceComponent sc = addServiceComponent(cluster, s, svcComponent);
+
+    ServiceComponentHost sch = serviceComponentHostFactory.createNew(sc, hostName);
+
+    sc.addServiceComponentHost(sch);
+    sch.setDesiredState(State.INSTALLED);
+    sch.setState(State.INSTALLED);
+    sch.setDesiredStackVersion(cluster.getDesiredStackVersion());
+    sch.setStackVersion(cluster.getCurrentStackVersion());
+
+    sch.persist();
+    return sch;
+  }
+
+  private Service installService(Cluster cluster, String serviceName) throws AmbariException {
+    Service service = null;
+
+    try {
+      service = cluster.getService(serviceName);
+    } catch (ServiceNotFoundException e) {
+      service = serviceFactory.createNew(cluster, serviceName);
+      cluster.addService(service);
+      service.persist();
+    }
+
+    return service;
+  }
+
+  private ServiceComponent addServiceComponent(Cluster cluster, Service service,
+      String componentName) throws AmbariException {
+    ServiceComponent serviceComponent = null;
+    try {
+      serviceComponent = service.getServiceComponent(componentName);
+    } catch (ServiceComponentNotFoundException e) {
+      serviceComponent = serviceComponentFactory.createNew(service, componentName);
+      service.addServiceComponent(serviceComponent);
+      serviceComponent.setDesiredState(State.INSTALLED);
+      serviceComponent.persist();
+    }
+
+    return serviceComponent;
+  }
+
+  private void createConfigs(Cluster cluster) {
+    Map<String, String> properties = new HashMap<String, String>();
+    Map<String, Map<String, String>> propertiesAttributes = new HashMap<String, Map<String, String>>();
+    properties.put("a", "a1");
+    properties.put("b", "b1");
+
+    Config c1 = new ConfigImpl(cluster, "hdfs-site", properties, propertiesAttributes, m_injector);
+    properties.put("c", "c1");
+    properties.put("d", "d1");
+
+    Config c2 = new ConfigImpl(cluster, "core-site", properties, propertiesAttributes, m_injector);
+    Config c3 = new ConfigImpl(cluster, "foo-site", properties, propertiesAttributes, m_injector);
+
+    cluster.addConfig(c1);
+    cluster.addConfig(c2);
+    cluster.addConfig(c3);
+    c1.persist();
+    c2.persist();
+    c3.persist();
+  }
 }