You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2017/06/14 18:32:40 UTC

ambari git commit: AMBARI-21241. Support revert for patch upgrades (ncole)

Repository: ambari
Updated Branches:
  refs/heads/branch-feature-AMBARI-12556 382da9799 -> e2b8764cd


AMBARI-21241. Support revert for patch upgrades (ncole)


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

Branch: refs/heads/branch-feature-AMBARI-12556
Commit: e2b8764cda8e51e06654e51debe46e47354a61aa
Parents: 382da97
Author: Nate Cole <nc...@hortonworks.com>
Authored: Tue Jun 13 14:09:54 2017 -0400
Committer: Nate Cole <nc...@hortonworks.com>
Committed: Wed Jun 14 12:33:49 2017 -0400

----------------------------------------------------------------------
 .../checks/HostsRepositoryVersionCheck.java     |  13 +-
 .../server/controller/PrereqCheckRequest.java   |  27 ++-
 .../PreUpgradeCheckResourceProvider.java        |  11 +-
 .../internal/UpgradeResourceProvider.java       |  12 +-
 .../server/orm/entities/UpgradeEntity.java      |  22 ++
 .../upgrades/FinalizeUpgradeAction.java         |  59 ++---
 .../upgrades/UpdateDesiredStackAction.java      |   2 +-
 .../ambari/server/state/UpgradeContext.java     | 230 +++++++++++++------
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |   1 +
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |   1 +
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |   1 +
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |   1 +
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |   1 +
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |   1 +
 .../StackUpgradeConfigurationMergeTest.java     |   2 +-
 .../internal/UpgradeResourceProviderTest.java   |  99 +++++++-
 .../ambari/server/state/UpgradeHelperTest.java  |   4 +-
 .../src/test/resources/hbase_version_test.xml   |   2 +
 18 files changed, 374 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/java/org/apache/ambari/server/checks/HostsRepositoryVersionCheck.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/checks/HostsRepositoryVersionCheck.java b/ambari-server/src/main/java/org/apache/ambari/server/checks/HostsRepositoryVersionCheck.java
index a66db3c..79f3598 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/checks/HostsRepositoryVersionCheck.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/checks/HostsRepositoryVersionCheck.java
@@ -17,7 +17,9 @@
  */
 package org.apache.ambari.server.checks;
 
+import java.util.EnumSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.PrereqCheckRequest;
@@ -69,8 +71,6 @@ public class HostsRepositoryVersionCheck extends AbstractCheckDescriptor {
     final Map<String, Host> clusterHosts = clustersProvider.get().getHostsForCluster(clusterName);
     final StackId stackId = request.getSourceStackId();
 
-
-
     for (Host host : clusterHosts.values()) {
       // hosts in MM will produce a warning if they do not have the repo version
       MaintenanceState maintenanceState = host.getMaintenanceState(cluster.getClusterId());
@@ -80,10 +80,17 @@ public class HostsRepositoryVersionCheck extends AbstractCheckDescriptor {
 
       if (null != request.getRepositoryVersion()) {
         boolean found = false;
+
+        Set<RepositoryVersionState> allowed = EnumSet.of(RepositoryVersionState.INSTALLED,
+            RepositoryVersionState.NOT_REQUIRED);
+        if (request.isRevert()) {
+          allowed.add(RepositoryVersionState.CURRENT);
+        }
+
         for (HostVersionEntity hve : hostVersionDaoProvider.get().findByHost(host.getHostName())) {
 
           if (hve.getRepositoryVersion().getVersion().equals(request.getRepositoryVersion())
-              && (hve.getState() == RepositoryVersionState.INSTALLED || hve.getState() == RepositoryVersionState.NOT_REQUIRED)) {
+              && allowed.contains(hve.getState())) {
             found = true;
             break;
           }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/java/org/apache/ambari/server/controller/PrereqCheckRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/PrereqCheckRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/PrereqCheckRequest.java
index c8c9f9e..f80c16a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/PrereqCheckRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/PrereqCheckRequest.java
@@ -38,8 +38,8 @@ public class PrereqCheckRequest {
 
   private UpgradeType m_upgradeType;
 
-  private Map<CheckDescription, PrereqCheckStatus> m_results =
-    new HashMap<>();
+  private Map<CheckDescription, PrereqCheckStatus> m_results = new HashMap<>();
+  private boolean m_revert = false;
 
 
   public PrereqCheckRequest(String clusterName, UpgradeType upgradeType) {
@@ -132,11 +132,30 @@ public class PrereqCheckRequest {
    * Gets the prerequisite check config
    * @return the prereqCheckConfig
    */
-  public PrerequisiteCheckConfig getPrerequisiteCheckConfig() { return m_prereqCheckConfig; }
+  public PrerequisiteCheckConfig getPrerequisiteCheckConfig() {
+    return m_prereqCheckConfig;
+  }
 
   /**
    * Sets the prerequisite check config obtained from the upgrade pack
    * @param prereqCheckConfig The prereqCheckConfig
    */
-  public void setPrerequisiteCheckConfig(PrerequisiteCheckConfig prereqCheckConfig) { m_prereqCheckConfig = prereqCheckConfig;}
+  public void setPrerequisiteCheckConfig(PrerequisiteCheckConfig prereqCheckConfig) {
+    m_prereqCheckConfig = prereqCheckConfig;
+  }
+
+  /**
+   * @param revert
+   *          {@code true} if the check is for a patch reversion
+   */
+  public void setRevert(boolean revert) {
+    m_revert = revert;
+  }
+
+  /**
+   * @return if the check is for a patch reversion
+   */
+  public boolean isRevert() {
+    return m_revert;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PreUpgradeCheckResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PreUpgradeCheckResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PreUpgradeCheckResourceProvider.java
index ea8fb37..7b03912 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PreUpgradeCheckResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PreUpgradeCheckResourceProvider.java
@@ -52,6 +52,7 @@ import org.apache.ambari.server.state.stack.PrerequisiteCheck;
 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.UpgradeType;
+import org.apache.commons.lang.BooleanUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -82,6 +83,7 @@ public class PreUpgradeCheckResourceProvider extends ReadOnlyResourceProvider {
    */
   public static final String UPGRADE_CHECK_UPGRADE_PACK_PROPERTY_ID       = PropertyHelper.getPropertyId("UpgradeChecks", "upgrade_pack");
   public static final String UPGRADE_CHECK_REPOSITORY_VERSION_PROPERTY_ID = PropertyHelper.getPropertyId("UpgradeChecks", "repository_version");
+  public static final String UPGRADE_CHECK_FOR_REVERT_PROPERTY_ID = PropertyHelper.getPropertyId("UpgradeChecks", "for_revert");
 
   @Inject
   private static Provider<Clusters> clustersProvider;
@@ -114,7 +116,8 @@ public class PreUpgradeCheckResourceProvider extends ReadOnlyResourceProvider {
       UPGRADE_CHECK_CLUSTER_NAME_PROPERTY_ID,
       UPGRADE_CHECK_UPGRADE_TYPE_PROPERTY_ID,
       UPGRADE_CHECK_UPGRADE_PACK_PROPERTY_ID,
-      UPGRADE_CHECK_REPOSITORY_VERSION_PROPERTY_ID);
+      UPGRADE_CHECK_REPOSITORY_VERSION_PROPERTY_ID,
+      UPGRADE_CHECK_FOR_REVERT_PROPERTY_ID);
 
 
   @SuppressWarnings("serial")
@@ -134,6 +137,7 @@ public class PreUpgradeCheckResourceProvider extends ReadOnlyResourceProvider {
     super(propertyIds, keyPropertyIds, managementController);
   }
 
+  @Override
   public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException,
     NoSuchResourceException, NoSuchParentResourceException {
 
@@ -175,6 +179,11 @@ public class PreUpgradeCheckResourceProvider extends ReadOnlyResourceProvider {
         upgradeCheckRequest.setTargetStackId(repositoryVersionEntity.getStackId());
       }
 
+      if (propertyMap.containsKey(UPGRADE_CHECK_FOR_REVERT_PROPERTY_ID)) {
+        Boolean forRevert = BooleanUtils.toBooleanObject(propertyMap.get(UPGRADE_CHECK_FOR_REVERT_PROPERTY_ID).toString());
+        upgradeCheckRequest.setRevert(forRevert);
+      }
+
       //ambariMetaInfo.getStack(stackName, cluster.getCurrentStackVersion().getStackVersion()).getUpgradePacks()
       // TODO AMBARI-12698, filter the upgrade checks to run based on the stack and upgrade type, or the upgrade pack.
       UpgradePack upgradePack = null;

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/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 60665f7..269bc19 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
@@ -127,6 +127,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   public static final String UPGRADE_REPO_VERSION_ID = "Upgrade/repository_version_id";
   public static final String UPGRADE_TYPE = "Upgrade/upgrade_type";
   public static final String UPGRADE_PACK = "Upgrade/pack";
+  public static final String UPGRADE_ID = "Upgrade/upgrade_id";
   public static final String UPGRADE_REQUEST_ID = "Upgrade/request_id";
   public static final String UPGRADE_ASSOCIATED_VERSION = "Upgrade/associated_version";
   public static final String UPGRADE_VERSIONS = "Upgrade/versions";
@@ -175,6 +176,11 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   public static final String UPGRADE_HOST_ORDERED_HOSTS = "Upgrade/host_order";
 
   /**
+   * Allows reversion of a successful upgrade of a patch.
+   */
+  public static final String UPGRADE_REVERT_UPGRADE_ID = "Upgrade/revert_upgrade_id";
+
+  /**
    * The role that will be used when creating HRC's for the type
    * {@link StageWrapper.Type#RU_TASKS}.
    */
@@ -251,6 +257,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     PROPERTY_IDS.add(UPGRADE_REPO_VERSION_ID);
     PROPERTY_IDS.add(UPGRADE_TYPE);
     PROPERTY_IDS.add(UPGRADE_PACK);
+    PROPERTY_IDS.add(UPGRADE_ID);
     PROPERTY_IDS.add(UPGRADE_REQUEST_ID);
     PROPERTY_IDS.add(UPGRADE_ASSOCIATED_VERSION);
     PROPERTY_IDS.add(UPGRADE_VERSIONS);
@@ -263,6 +270,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     PROPERTY_IDS.add(UPGRADE_SKIP_PREREQUISITE_CHECKS);
     PROPERTY_IDS.add(UPGRADE_FAIL_ON_CHECK_WARNINGS);
     PROPERTY_IDS.add(UPGRADE_HOST_ORDERED_HOSTS);
+    PROPERTY_IDS.add(UPGRADE_REVERT_UPGRADE_ID);
 
     PROPERTY_IDS.add(REQUEST_CONTEXT_ID);
     PROPERTY_IDS.add(REQUEST_CREATE_TIME_ID);
@@ -538,6 +546,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   private Resource toResource(UpgradeEntity entity, String clusterName, Set<String> requestedIds) {
     ResourceImpl resource = new ResourceImpl(Resource.Type.Upgrade);
 
+    setResourceProperty(resource, UPGRADE_ID, entity.getId(), requestedIds);
     setResourceProperty(resource, UPGRADE_CLUSTER_NAME, clusterName, requestedIds);
     setResourceProperty(resource, UPGRADE_TYPE, entity.getUpgradeType(), requestedIds);
     setResourceProperty(resource, UPGRADE_PACK, entity.getUpgradePackage(), requestedIds);
@@ -667,6 +676,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     upgrade.setAutoSkipComponentFailures(upgradeContext.isComponentFailureAutoSkipped());
     upgrade.setAutoSkipServiceCheckFailures(upgradeContext.isServiceCheckFailureAutoSkipped());
     upgrade.setDowngradeAllowed(upgradeContext.isDowngradeAllowed());
+    upgrade.setOrchestration(upgradeContext.getOrchestrationType());
 
     // create to/from history for this upgrade - this should be done before any
     // possible changes to the desired version for components
@@ -1355,7 +1365,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
 
         // depending on whether this is an upgrade or a downgrade, the history
         // will be different
-        if (upgradeContext.getDirection() == Direction.UPGRADE) {
+        if (upgradeContext.getDirection() == Direction.UPGRADE || upgradeContext.isPatchRevert()) {
           history.setFromRepositoryVersion(component.getDesiredRepositoryVersion());
           history.setTargetRepositoryVersion(upgradeContext.getRepositoryVersion());
         } else {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeEntity.java
index 43b2e08..7f4824f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeEntity.java
@@ -38,6 +38,7 @@ import javax.persistence.Table;
 import javax.persistence.TableGenerator;
 
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.state.RepositoryType;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
 import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
 import org.apache.commons.lang.builder.EqualsBuilder;
@@ -118,6 +119,10 @@ public class UpgradeEntity {
   @Column(name="downgrade_allowed", nullable = false)
   private Short downgrade_allowed = 1;
 
+  @Column(name="orchestration", nullable = false)
+  @Enumerated(value = EnumType.STRING)
+  private RepositoryType orchestration = RepositoryType.STANDARD;
+
   /**
    * {@code true} if the upgrade has been marked as suspended.
    */
@@ -372,6 +377,23 @@ public class UpgradeEntity {
   }
 
   /**
+   * Sets the orchestration for the upgrade.  Only different when an upgrade is a revert of a patch.
+   * In that case, the orchestration is set to PATCH even if the target repository is type STANDARD.
+   *
+   * @param type  the orchestration
+   */
+  public void setOrchestration(RepositoryType type) {
+    orchestration = type;
+  }
+
+  /**
+   * @return  the orchestration type
+   */
+  public RepositoryType getOrchestration() {
+    return orchestration;
+  }
+
+  /**
    * {@inheritDoc}
    */
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/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 e70f1ce..9d70546 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
@@ -109,7 +109,7 @@ public class FinalizeUpgradeAction extends AbstractUpgradeServerAction {
       String version = repositoryVersion.getVersion();
 
       String message;
-      if (upgradeContext.getRepositoryType() == RepositoryType.STANDARD) {
+      if (upgradeContext.getOrchestrationType() == RepositoryType.STANDARD) {
         message = MessageFormat.format("Finalizing the upgrade to {0} for all cluster services.", version);
       } else {
         Set<String> servicesInUpgrade = upgradeContext.getSupportedServices();
@@ -150,7 +150,7 @@ public class FinalizeUpgradeAction extends AbstractUpgradeServerAction {
       // transition correctly
       for (HostVersionEntity hostVersion : hostVersions) {
         RepositoryVersionState hostVersionState = hostVersion.getState();
-        switch( hostVersionState ){
+        switch (hostVersionState) {
           case CURRENT:
           case NOT_REQUIRED: {
             hostVersionsAllowed.add(hostVersion);
@@ -230,7 +230,7 @@ public class FinalizeUpgradeAction extends AbstractUpgradeServerAction {
 
       String message;
 
-      if (downgradeFromRepositoryVersion.getType() == RepositoryType.STANDARD) {
+      if (upgradeContext.getOrchestrationType() == RepositoryType.STANDARD) {
         message = MessageFormat.format(
             "Finalizing the downgrade from {0} for all cluster services.",
             downgradeFromVersion);
@@ -260,39 +260,44 @@ public class FinalizeUpgradeAction extends AbstractUpgradeServerAction {
         throw new AmbariException(messageBuff.toString());
       }
 
-      // for every repository being downgraded to, ensure the host versions are correct
-      Map<String, RepositoryVersionEntity> targetVersionsByService = upgradeContext.getTargetVersions();
-      Set<RepositoryVersionEntity> targetRepositoryVersions = new HashSet<>();
-      for (String service : targetVersionsByService.keySet()) {
-        targetRepositoryVersions.add(targetVersionsByService.get(service));
-      }
 
-      for (RepositoryVersionEntity targetRepositoryVersion : targetRepositoryVersions) {
-        // find host versions
-        List<HostVersionEntity> hostVersions = hostVersionDAO.findHostVersionByClusterAndRepository(
-            cluster.getClusterId(), targetRepositoryVersion);
+      if (upgradeContext.isPatchRevert()) {
+        finalizeHostRepositoryVersions(cluster);
+      } else {
+        // for every repository being downgraded to, ensure the host versions are correct
+        Map<String, RepositoryVersionEntity> targetVersionsByService = upgradeContext.getTargetVersions();
+        Set<RepositoryVersionEntity> targetRepositoryVersions = new HashSet<>();
+        for (String service : targetVersionsByService.keySet()) {
+          targetRepositoryVersions.add(targetVersionsByService.get(service));
+        }
 
-        outSB.append(String.format("Finalizing %d host(s) back to %s", hostVersions.size(),
-            targetRepositoryVersion.getVersion())).append(System.lineSeparator());
+        for (RepositoryVersionEntity targetRepositoryVersion : targetRepositoryVersions) {
+          // find host versions
+          List<HostVersionEntity> hostVersions = hostVersionDAO.findHostVersionByClusterAndRepository(
+              cluster.getClusterId(), targetRepositoryVersion);
 
-        for (HostVersionEntity hostVersion : hostVersions) {
-          if (hostVersion.getState() != RepositoryVersionState.CURRENT) {
-            hostVersion.setState(RepositoryVersionState.CURRENT);
-            hostVersionDAO.merge(hostVersion);
-          }
+          outSB.append(String.format("Finalizing %d host(s) back to %s", hostVersions.size(),
+              targetRepositoryVersion.getVersion())).append(System.lineSeparator());
+
+          for (HostVersionEntity hostVersion : hostVersions) {
+            if (hostVersion.getState() != RepositoryVersionState.CURRENT) {
+              hostVersion.setState(RepositoryVersionState.CURRENT);
+              hostVersionDAO.merge(hostVersion);
+            }
 
-          List<HostComponentStateEntity> hostComponentStates = hostComponentStateDAO.findByHost(
-              hostVersion.getHostName());
+            List<HostComponentStateEntity> hostComponentStates = hostComponentStateDAO.findByHost(
+                hostVersion.getHostName());
 
-          for (HostComponentStateEntity hostComponentState : hostComponentStates) {
-            hostComponentState.setUpgradeState(UpgradeState.NONE);
-            hostComponentStateDAO.merge(hostComponentState);
+            for (HostComponentStateEntity hostComponentState : hostComponentStates) {
+              hostComponentState.setUpgradeState(UpgradeState.NONE);
+              hostComponentStateDAO.merge(hostComponentState);
+            }
           }
         }
       }
 
       // remove any configurations for services which crossed a stack boundary
-      for( String serviceName : servicesInUpgrade ){
+      for (String serviceName : servicesInUpgrade) {
         RepositoryVersionEntity sourceRepositoryVersion = upgradeContext.getSourceRepositoryVersion(serviceName);
         RepositoryVersionEntity targetRepositoryVersion = upgradeContext.getTargetRepositoryVersion(serviceName);
         StackId sourceStackId = sourceRepositoryVersion.getStackId();
@@ -346,7 +351,7 @@ public class FinalizeUpgradeAction extends AbstractUpgradeServerAction {
     StackId targetStackId = repositoryVersionEntity.getStackId();
 
     Set<String> servicesParticipating = upgradeContext.getSupportedServices();
-    for( String serviceName : servicesParticipating ){
+    for (String serviceName : servicesParticipating) {
       Service service = cluster.getService(serviceName);
       String targetVersion = upgradeContext.getTargetVersion(serviceName);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/UpdateDesiredStackAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/UpdateDesiredStackAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/UpdateDesiredStackAction.java
index 8a4820d..ff0becc 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/UpdateDesiredStackAction.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/UpdateDesiredStackAction.java
@@ -130,7 +130,7 @@ public class UpdateDesiredStackAction extends AbstractUpgradeServerAction {
         final String message;
         RepositoryVersionEntity targetRepositoryVersion = upgradeContext.getRepositoryVersion();
 
-        if (upgradeContext.getRepositoryType() == RepositoryType.STANDARD) {
+        if (upgradeContext.getOrchestrationType() == RepositoryType.STANDARD) {
           message = MessageFormat.format(
               "Updating the desired repository version to {0} for all cluster services.",
               targetRepositoryVersion.getVersion());

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/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 3ecf64d..bca85ca 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
@@ -22,6 +22,7 @@ import static org.apache.ambari.server.controller.internal.UpgradeResourceProvid
 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;
@@ -42,6 +43,7 @@ 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;
@@ -75,6 +77,8 @@ 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;
@@ -90,6 +94,8 @@ import com.google.inject.assistedinject.AssistedInject;
  */
 public class UpgradeContext {
 
+  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";
@@ -237,6 +243,16 @@ public class UpgradeContext {
   @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 Map<String, Object> upgradeRequestMap, Gson gson, UpgradeHelper upgradeHelper,
@@ -250,14 +266,6 @@ public class UpgradeContext {
 
     m_cluster = cluster;
 
-    // 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);
-
     // determine upgrade type (default is ROLLING)
     String upgradeTypeProperty = (String) upgradeRequestMap.get(UPGRADE_TYPE);
     if (StringUtils.isNotBlank(upgradeTypeProperty)) {
@@ -272,71 +280,134 @@ public class UpgradeContext {
       m_type= UpgradeType.ROLLING;
     }
 
-    // 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));
-        }
+    m_isRevert = upgradeRequestMap.containsKey(UPGRADE_REVERT_UPGRADE_ID);
 
-        // depending on the repository, add services
-        m_repositoryVersion = m_repoVersionDAO.findByPK(Long.valueOf(repositoryVersionId));
-        if (m_repositoryVersion.getType() == 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);
-            }
+    if (m_isRevert) {
+      Long revertUpgradeId = Long.valueOf(upgradeRequestMap.get(UPGRADE_REVERT_UPGRADE_ID).toString());
+      UpgradeEntity revertUpgrade = m_upgradeDAO.findUpgrade(revertUpgradeId);
 
-          } catch (Exception e) {
-            String msg = String.format(
-                "Could not parse version definition for %s.  Upgrade will not proceed.",
-                m_repositoryVersion.getVersion());
+      if (revertUpgrade.getOrchestration() != RepositoryType.PATCH) {
+        throw new AmbariException("Can only revert upgrades that have been done as a patch.");
+      }
 
-            throw new AmbariException(msg);
-          }
-        }
+      if (revertUpgrade.getDirection() != Direction.UPGRADE) {
+        throw new AmbariException("Can only revert successful upgrades, not downgrades.");
+      }
 
-        // populate the target repository map for all services in the upgrade
-        for (String serviceName : m_services) {
-          Service service = cluster.getService(serviceName);
-          m_sourceRepositoryMap.put(serviceName, service.getDesiredRepositoryVersion());
-          m_targetRepositoryMap.put(serviceName, m_repositoryVersion);
-        }
+      Set<RepositoryVersionEntity> priors = new HashSet<>();
+      for (UpgradeHistoryEntity history : revertUpgrade.getHistory()) {
+        priors.add(history.getFromReposistoryVersion());
 
-        break;
+        // !!! build all service-specific
+        m_services.add(history.getServiceName());
+        m_sourceRepositoryMap.put(history.getServiceName(), history.getTargetRepositoryVersion());
+        m_targetRepositoryMap.put(history.getServiceName(), history.getFromReposistoryVersion());
       }
-      case DOWNGRADE:{
-        UpgradeEntity upgrade = m_upgradeDAO.findLastUpgradeForCluster(
-            cluster.getClusterId(), Direction.UPGRADE);
 
-        m_repositoryVersion = upgrade.getRepositoryVersion();
+      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());
 
-        // 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());
+        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);
 
-        break;
+          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;
       }
-      default:
-        m_repositoryVersion = null;
-        break;
     }
 
 
@@ -428,6 +499,10 @@ public class UpgradeContext {
     m_upgradePack = packs.get(upgradePackage);
 
     m_resolver = new MasterHostResolver(m_cluster, configHelper, this);
+    m_orchestration = upgradeEntity.getOrchestration();
+
+    m_isRevert = upgradeEntity.getOrchestration() == RepositoryType.PATCH &&
+        upgradeEntity.getDirection() == Direction.DOWNGRADE;
   }
 
   /**
@@ -522,7 +597,7 @@ public class UpgradeContext {
    * 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
+   * 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.
    *
@@ -699,7 +774,7 @@ public class UpgradeContext {
       return true;
     }
 
-    switch (m_repositoryVersion.getType()) {
+    switch (m_orchestration) {
       case PATCH:
       case SERVICE:
         return scope == UpgradeScope.PARTIAL;
@@ -730,12 +805,14 @@ public class UpgradeContext {
 
   /**
    * Gets the repository type to determine if this upgrade is a complete upgrade
-   * or a service/patch.
+   * 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 repository type.
+   * @return the orchestration type.
    */
-  public RepositoryType getRepositoryType() {
-    return m_repositoryVersion.getType();
+  public RepositoryType getOrchestrationType() {
+    return m_orchestration;
   }
 
   /**
@@ -802,6 +879,13 @@ public class UpgradeContext {
   }
 
   /**
+   * @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.
    *
@@ -816,7 +900,7 @@ public class UpgradeContext {
     validator.setNextValidator(preReqValidator);
 
     final UpgradeRequestValidator upgradeTypeValidator;
-    switch( upgradeType ){
+    switch (upgradeType) {
       case HOST_ORDERED:
         upgradeTypeValidator = new HostOrderedUpgradeValidator();
         break;
@@ -869,7 +953,7 @@ public class UpgradeContext {
       check(cluster, direction, type, upgradePack, requestMap);
 
       // pass along to the next
-      if( null != m_nextValidator ) {
+      if (null != m_nextValidator) {
         m_nextValidator.validate(cluster, direction, type, upgradePack, requestMap);
       }
     }
@@ -931,7 +1015,7 @@ public class UpgradeContext {
 
       // verify that there is not an upgrade or downgrade that is in progress or suspended
       UpgradeEntity existingUpgrade = cluster.getUpgradeInProgress();
-      if( null != existingUpgrade ){
+      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),
@@ -939,7 +1023,7 @@ public class UpgradeContext {
       }
 
       // skip this check if it's a downgrade or we are instructed to skip it
-      if( direction.isDowngrade() || skipPrereqChecks ){
+      if (direction.isDowngrade() || skipPrereqChecks) {
         return;
       }
 
@@ -953,6 +1037,7 @@ public class UpgradeContext {
       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();
 
@@ -1110,4 +1195,5 @@ public class UpgradeContext {
       return hostOrderItems;
     }
   }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
index e341405..bbb4af0 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -813,6 +813,7 @@ CREATE TABLE upgrade (
   cluster_id BIGINT NOT NULL,
   request_id BIGINT NOT NULL,
   direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_package VARCHAR(255) NOT NULL,
   upgrade_type VARCHAR(32) NOT NULL,
   repo_version_id BIGINT NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index ce8736e..5cdbd26 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -831,6 +831,7 @@ CREATE TABLE upgrade (
   cluster_id BIGINT NOT NULL,
   request_id BIGINT NOT NULL,
   direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_package VARCHAR(255) NOT NULL,
   upgrade_type VARCHAR(32) NOT NULL,
   repo_version_id BIGINT NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index ace3738..732e2c4 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -810,6 +810,7 @@ CREATE TABLE upgrade (
   cluster_id NUMBER(19) NOT NULL,
   request_id NUMBER(19) NOT NULL,
   direction VARCHAR2(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR2(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_package VARCHAR2(255) NOT NULL,
   upgrade_type VARCHAR2(32) NOT NULL,
   repo_version_id NUMBER(19) NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 5899179..62a3f00 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -812,6 +812,7 @@ CREATE TABLE upgrade (
   cluster_id BIGINT NOT NULL,
   request_id BIGINT NOT NULL,
   direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_package VARCHAR(255) NOT NULL,
   upgrade_type VARCHAR(32) NOT NULL,
   repo_version_id BIGINT NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
index 6164b2d..da46ec4 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -808,6 +808,7 @@ CREATE TABLE upgrade (
   cluster_id NUMERIC(19) NOT NULL,
   request_id NUMERIC(19) NOT NULL,
   direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_type VARCHAR(32) NOT NULL,
   repo_version_id NUMERIC(19) NOT NULL,
   upgrade_package VARCHAR(255) NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
index 52d2b87..c64355e 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -830,6 +830,7 @@ CREATE TABLE upgrade (
   cluster_id BIGINT NOT NULL,
   request_id BIGINT NOT NULL,
   direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  orchestration VARCHAR(255) DEFAULT 'STANDARD' NOT NULL,
   upgrade_package VARCHAR(255) NOT NULL,
   upgrade_type VARCHAR(32) NOT NULL,
   repo_version_id BIGINT NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackUpgradeConfigurationMergeTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackUpgradeConfigurationMergeTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackUpgradeConfigurationMergeTest.java
index a37e4f5..9f3f01f 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackUpgradeConfigurationMergeTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackUpgradeConfigurationMergeTest.java
@@ -264,7 +264,7 @@ public class StackUpgradeConfigurationMergeTest extends EasyMockSupport {
     expect(context.getSupportedServices()).andReturn(Sets.newHashSet("ZOOKEEPER")).atLeastOnce();
     expect(context.getSourceRepositoryVersion(EasyMock.anyString())).andReturn(repoVersion211).atLeastOnce();
     expect(context.getTargetRepositoryVersion(EasyMock.anyString())).andReturn(repoVersion220).atLeastOnce();
-    expect(context.getRepositoryType()).andReturn(RepositoryType.STANDARD).anyTimes();
+    expect(context.getOrchestrationType()).andReturn(RepositoryType.STANDARD).anyTimes();
     expect(context.getHostRoleCommandFactory()).andStubReturn(m_injector.getInstance(HostRoleCommandFactory.class));
     expect(context.getRoleGraphFactory()).andStubReturn(m_injector.getInstance(RoleGraphFactory.class));
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
index 014ab42..48d78f8 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
@@ -25,9 +25,12 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.lang.reflect.Field;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -88,6 +91,7 @@ import org.apache.ambari.server.state.ConfigFactory;
 import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostState;
+import org.apache.ambari.server.state.RepositoryType;
 import org.apache.ambari.server.state.Service;
 import org.apache.ambari.server.state.ServiceComponent;
 import org.apache.ambari.server.state.ServiceComponentHost;
@@ -100,6 +104,8 @@ import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
 import org.apache.ambari.server.topology.TopologyManager;
 import org.apache.ambari.server.utils.StageUtils;
 import org.apache.ambari.server.view.ViewRegistry;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.easymock.EasyMock;
 import org.easymock.EasyMockSupport;
@@ -109,6 +115,8 @@ import org.junit.Before;
 import org.junit.Test;
 import org.springframework.security.core.context.SecurityContextHolder;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.gson.JsonArray;
@@ -139,6 +147,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
 
   RepositoryVersionEntity repoVersionEntity2110;
   RepositoryVersionEntity repoVersionEntity2111;
+  RepositoryVersionEntity repoVersionEntity2112;
   RepositoryVersionEntity repoVersionEntity2200;
 
   /**
@@ -208,14 +217,23 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
     repoVersionDao.create(repoVersionEntity2110);
 
     repoVersionEntity2111 = new RepositoryVersionEntity();
-    repoVersionEntity2111.setDisplayName("My New Version 2 for patch upgrade");
+    repoVersionEntity2111.setDisplayName("My New Version 2 for minor upgrade");
     repoVersionEntity2111.setOperatingSystems("");
     repoVersionEntity2111.setStack(stackEntity211);
     repoVersionEntity2111.setVersion("2.1.1.1");
     repoVersionDao.create(repoVersionEntity2111);
 
+    repoVersionEntity2112 = new RepositoryVersionEntity();
+    repoVersionEntity2112.setDisplayName("My New Version 3 for patch upgrade");
+    repoVersionEntity2112.setOperatingSystems("");
+    repoVersionEntity2112.setStack(stackEntity211);
+    repoVersionEntity2112.setVersion("2.1.1.2");
+    repoVersionEntity2112.setType(RepositoryType.PATCH);
+    repoVersionEntity2112.setVersionXml("");
+    repoVersionDao.create(repoVersionEntity2112);
+
     repoVersionEntity2200 = new RepositoryVersionEntity();
-    repoVersionEntity2200.setDisplayName("My New Version 3 for major upgrade");
+    repoVersionEntity2200.setDisplayName("My New Version 4 for major upgrade");
     repoVersionEntity2200.setOperatingSystems("");
     repoVersionEntity2200.setStack(stackEntity220);
     repoVersionEntity2200.setVersion("2.2.0.0");
@@ -1572,6 +1590,81 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
     }
   }
 
+
+  /**
+   * Tests that from/to repository version history is created correctly on the
+   * upgrade.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testCreatePatchRevertUpgrade() throws Exception {
+    Cluster cluster = clusters.getCluster("c1");
+
+    File f = new File("src/test/resources/hbase_version_test.xml");
+    repoVersionEntity2112.setVersionXml(IOUtils.toString(new FileInputStream(f)));
+    repoVersionEntity2112.setVersionXsd("version_definition.xsd");
+    repoVersionDao.merge(repoVersionEntity2112);
+
+    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    assertEquals(0, upgrades.size());
+
+    Map<String, Object> requestProps = new HashMap<>();
+    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2112.getId()));
+    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, "true");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());
+
+    ResourceProvider upgradeResourceProvider = createProvider(amc);
+
+    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    upgradeResourceProvider.createResources(request);
+
+    upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    assertEquals(1, upgrades.size());
+    UpgradeEntity upgradeEntity = upgrades.get(0);
+    assertEquals(RepositoryType.PATCH, upgradeEntity.getOrchestration());
+
+    // !!! make it look like the cluster is done
+    cluster.setUpgradeEntity(null);
+
+    requestProps = new HashMap<>();
+    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_REVERT_UPGRADE_ID, upgradeEntity.getId());
+    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, Boolean.TRUE.toString());
+
+    request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    upgradeResourceProvider.createResources(request);
+
+    upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    assertEquals(2, upgrades.size());
+
+    boolean found = false;
+
+    Function<UpgradeHistoryEntity, String> function = new Function<UpgradeHistoryEntity, String>() {
+      @Override
+      public String apply(UpgradeHistoryEntity input) {
+        return input.getServiceName() + "/" + input.getComponentName();
+      };
+    };
+
+    for (UpgradeEntity upgrade : upgrades) {
+      if (upgrade.getId() != upgradeEntity.getId()) {
+        found = true;
+        assertEquals(upgradeEntity.getOrchestration(), upgrade.getOrchestration());
+
+        Collection<String> upgradeEntityStrings = Collections2.transform(upgradeEntity.getHistory(), function);
+        Collection<String> upgradeStrings = Collections2.transform(upgrade.getHistory(), function);
+
+        Collection<?> diff = CollectionUtils.disjunction(upgradeEntityStrings, upgradeStrings);
+        assertEquals("Verify the same set of components was orchestrated", 0, diff.size());
+      }
+    }
+
+    assertTrue(found);
+  }
+
   private String parseSingleMessage(String msgStr){
     JsonParser parser = new JsonParser();
     JsonArray msgArray = (JsonArray) parser.parse(msgStr);
@@ -1664,7 +1757,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
    * Without this, commands of this type would not be able to determine which
    * service/component repository they should use when the command is scheduled
    * to run.
-   * 
+   *
    * @throws Exception
    */
   @Test

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java
index 921322b..8cf7373 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java
@@ -2393,7 +2393,7 @@ public class UpgradeHelperTest extends EasyMockSupport {
     expect(context.getSupportedServices()).andReturn(Sets.newHashSet("ZOOKEEPER")).atLeastOnce();
     expect(context.getSourceRepositoryVersion(EasyMock.anyString())).andReturn(repoVersion211).atLeastOnce();
     expect(context.getTargetRepositoryVersion(EasyMock.anyString())).andReturn(repoVersion220).atLeastOnce();
-    expect(context.getRepositoryType()).andReturn(RepositoryType.STANDARD).anyTimes();
+    expect(context.getOrchestrationType()).andReturn(RepositoryType.STANDARD).anyTimes();
     expect(context.getAmbariMetaInfo()).andReturn(ambariMetaInfo).anyTimes();
     expect(context.getHostRoleCommandFactory()).andStubReturn(injector.getInstance(HostRoleCommandFactory.class));
     expect(context.getRoleGraphFactory()).andStubReturn(injector.getInstance(RoleGraphFactory.class));
@@ -2492,7 +2492,7 @@ public class UpgradeHelperTest extends EasyMockSupport {
     expect(context.getDirection()).andReturn(direction).anyTimes();
     expect(context.getRepositoryVersion()).andReturn(repositoryVersion).anyTimes();
     expect(context.getSupportedServices()).andReturn(services).anyTimes();
-    expect(context.getRepositoryType()).andReturn(repositoryType).anyTimes();
+    expect(context.getOrchestrationType()).andReturn(repositoryType).anyTimes();
     expect(context.getAmbariMetaInfo()).andReturn(ambariMetaInfo).anyTimes();
     expect(context.getHostRoleCommandFactory()).andStubReturn(
         injector.getInstance(HostRoleCommandFactory.class));

http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8764c/ambari-server/src/test/resources/hbase_version_test.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/hbase_version_test.xml b/ambari-server/src/test/resources/hbase_version_test.xml
index 58c8701..ea90f40 100644
--- a/ambari-server/src/test/resources/hbase_version_test.xml
+++ b/ambari-server/src/test/resources/hbase_version_test.xml
@@ -30,10 +30,12 @@
   
   <manifest>
     <service id="HBASE-112" name="HBASE" version="1.1.2" version-id="2_3_4_0-3396" />
+    <service id="ZOOKEEPER-346" name="ZOOKEEPER" version="3.4.6" version-id="2_3_4_0-3396" />
   </manifest>
   
   <available-services>
     <service idref="HBASE-112" />
+    <service idref="ZOOKEEPER-346" />
   </available-services>
   
   <repository-info>