Repository: ambari
Updated Branches:
  refs/heads/trunk 8cb942393 -> 8b5d697c0


AMBARI-21894 - PATCH and MAINT Repositories Should Indicate that they can be 
Reverted (jonathanhurley)


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

Branch: refs/heads/trunk
Commit: 8b5d697c0adb5ae92b16f1df2e2d6d75b066fce5
Parents: 8cb9423
Author: Jonathan Hurley <jhur...@hortonworks.com>
Authored: Wed Sep 6 14:32:29 2017 -0400
Committer: Jonathan Hurley <jhur...@hortonworks.com>
Committed: Thu Sep 7 00:06:08 2017 -0400

----------------------------------------------------------------------
 .../ClusterStackVersionResourceProvider.java    |  34 +++++-
 .../internal/UpgradeResourceProvider.java       |  16 ++-
 .../ambari/server/orm/dao/UpgradeDAO.java       |  46 +++++++-
 .../server/orm/entities/UpgradeEntity.java      | 109 +++++++++++++++----
 .../upgrades/AbstractUpgradeServerAction.java   |   7 ++
 .../upgrades/FinalizeUpgradeAction.java         |  11 ++
 .../ambari/server/state/RepositoryType.java     |   7 ++
 .../ambari/server/state/UpgradeContext.java     |  33 +++++-
 .../server/upgrade/UpgradeCatalog260.java       |   8 +-
 .../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 +
 .../internal/UpgradeResourceProviderTest.java   |  62 +++++++++++
 .../ambari/server/orm/dao/UpgradeDAOTest.java   | 105 +++++++++++++++++-
 .../ambari/server/state/UpgradeContextTest.java |  50 +++++++++
 .../server/upgrade/UpgradeCatalog260Test.java   |  30 ++++-
 19 files changed, 489 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterStackVersionResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterStackVersionResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterStackVersionResourceProvider.java
index 1766da3..85a7596 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterStackVersionResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterStackVersionResourceProvider.java
@@ -60,6 +60,7 @@ import 
org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.orm.dao.HostVersionDAO;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.UpgradeDAO;
 import org.apache.ambari.server.orm.entities.HostVersionEntity;
 import org.apache.ambari.server.orm.entities.OperatingSystemEntity;
 import org.apache.ambari.server.orm.entities.RepositoryEntity;
@@ -112,6 +113,8 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
   protected static final String CLUSTER_STACK_VERSION_STATE_PROPERTY_ID = 
PropertyHelper.getPropertyId("ClusterStackVersions", "state");
   protected static final String CLUSTER_STACK_VERSION_HOST_STATES_PROPERTY_ID 
= PropertyHelper.getPropertyId("ClusterStackVersions", "host_states");
   protected static final String CLUSTER_STACK_VERSION_REPO_SUMMARY_PROPERTY_ID 
= PropertyHelper.getPropertyId("ClusterStackVersions", "repository_summary");
+  protected static final String CLUSTER_STACK_VERSION_REPO_SUPPORTS_REVERT= 
PropertyHelper.getPropertyId("ClusterStackVersions", "supports_revert");
+  protected static final String CLUSTER_STACK_VERSION_REPO_REVERT_UPGRADE_ID = 
PropertyHelper.getPropertyId("ClusterStackVersions", "revert_upgrade_id");
 
   protected static final String 
CLUSTER_STACK_VERSION_REPOSITORY_VERSION_PROPERTY_ID  = 
PropertyHelper.getPropertyId("ClusterStackVersions", "repository_version");
   protected static final String CLUSTER_STACK_VERSION_STAGE_SUCCESS_FACTOR  = 
PropertyHelper.getPropertyId("ClusterStackVersions", "success_factor");
@@ -153,7 +156,8 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
       CLUSTER_STACK_VERSION_VERSION_PROPERTY_ID, 
CLUSTER_STACK_VERSION_HOST_STATES_PROPERTY_ID,
       CLUSTER_STACK_VERSION_STATE_PROPERTY_ID, 
CLUSTER_STACK_VERSION_REPOSITORY_VERSION_PROPERTY_ID,
       CLUSTER_STACK_VERSION_STAGE_SUCCESS_FACTOR,
-      CLUSTER_STACK_VERSION_FORCE, 
CLUSTER_STACK_VERSION_REPO_SUMMARY_PROPERTY_ID);
+      CLUSTER_STACK_VERSION_FORCE, 
CLUSTER_STACK_VERSION_REPO_SUMMARY_PROPERTY_ID,
+      CLUSTER_STACK_VERSION_REPO_SUPPORTS_REVERT, 
CLUSTER_STACK_VERSION_REPO_REVERT_UPGRADE_ID);
 
   private static Map<Type, String> keyPropertyIds = ImmutableMap.<Type, 
String> builder()
       .put(Type.Cluster, CLUSTER_STACK_VERSION_CLUSTER_NAME_PROPERTY_ID)
@@ -166,6 +170,12 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
   @Inject
   private static HostVersionDAO hostVersionDAO;
 
+  /**
+   * Used for looking up revertable upgrades.
+   */
+  @Inject
+  private static UpgradeDAO upgradeDAO;
+
   @Inject
   private static RepositoryVersionDAO repositoryVersionDAO;
 
@@ -260,6 +270,12 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
       throw new SystemException("Could not find any repositories to show");
     }
 
+    // find the 1 repository version which is revertable, if any
+    UpgradeEntity revertableUpgrade = null;
+    if (null == cluster.getUpgradeInProgress()) {
+      revertableUpgrade = upgradeDAO.findRevertable(cluster.getClusterId());
+    }
+
     for (Long repositoryVersionId : requestedEntities) {
       final Resource resource = new 
ResourceImpl(Resource.Type.ClusterStackVersion);
 
@@ -298,7 +314,6 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
       setResourceProperty(resource, 
CLUSTER_STACK_VERSION_HOST_STATES_PROPERTY_ID, hostStates, requestedIds);
       setResourceProperty(resource, 
CLUSTER_STACK_VERSION_REPO_SUMMARY_PROPERTY_ID, versionSummary, requestedIds);
 
-
       setResourceProperty(resource, CLUSTER_STACK_VERSION_ID_PROPERTY_ID, 
repositoryVersion.getId(), requestedIds);
       setResourceProperty(resource, CLUSTER_STACK_VERSION_STACK_PROPERTY_ID, 
repoVersionStackId.getStackName(), requestedIds);
       setResourceProperty(resource, CLUSTER_STACK_VERSION_VERSION_PROPERTY_ID, 
repoVersionStackId.getStackVersion(), requestedIds);
@@ -309,6 +324,21 @@ public class ClusterStackVersionResourceProvider extends 
AbstractControllerResou
       RepositoryVersionState aggregateState = 
RepositoryVersionState.getAggregateState(allStates);
       setResourceProperty(resource, CLUSTER_STACK_VERSION_STATE_PROPERTY_ID, 
aggregateState, requestedIds);
 
+      // mark whether this repo is revertable for this cluster
+      boolean revertable = false;
+      if (null != revertableUpgrade) {
+        RepositoryVersionEntity revertableRepositoryVersion = 
revertableUpgrade.getRepositoryVersion();
+        revertable = revertableRepositoryVersion.getId() == 
repositoryVersionId;
+      }
+
+      setResourceProperty(resource, 
CLUSTER_STACK_VERSION_REPO_SUPPORTS_REVERT, revertable, requestedIds);
+
+      // if the repo is revertable, indicate which upgrade to revert if 
necessary
+      if (revertable) {
+        setResourceProperty(resource, 
CLUSTER_STACK_VERSION_REPO_REVERT_UPGRADE_ID,
+            revertableUpgrade.getId(), requestedIds);
+      }
+
       if (predicate == null || predicate.evaluate(resource)) {
         resources.add(resource);
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 a35f380..0ff21a2 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
@@ -765,7 +765,7 @@ public class UpgradeResourceProvider extends 
AbstractControllerResourceProvider
     upgrade.setUpgradeGroups(groupEntities);
 
     req.getRequestStatusResponse();
-    return createUpgradeInsideTransaction(cluster, req, upgrade);
+    return createUpgradeInsideTransaction(cluster, req, upgrade, 
upgradeContext);
   }
 
   /**
@@ -782,6 +782,8 @@ public class UpgradeResourceProvider extends 
AbstractControllerResourceProvider
    * @param upgradeEntity
    *          the upgrade to create and associate with the newly created 
request
    *          (not {@code null}).
+   * @param upgradeContext
+   *          the upgrade context associated with the upgrade being created.
    * @return the persisted {@link UpgradeEntity} encapsulating all
    *         {@link UpgradeGroupEntity} and {@link UpgradeItemEntity}.
    * @throws AmbariException
@@ -789,7 +791,17 @@ public class UpgradeResourceProvider extends 
AbstractControllerResourceProvider
   @Transactional
   UpgradeEntity createUpgradeInsideTransaction(Cluster cluster,
       RequestStageContainer request,
-      UpgradeEntity upgradeEntity) throws AmbariException {
+      UpgradeEntity upgradeEntity, UpgradeContext upgradeContext) throws 
AmbariException {
+
+    // if this is a patch reversion, then we must unset the revertable flag of
+    // the upgrade being reverted
+    if (upgradeContext.isPatchRevert()) {
+      UpgradeEntity upgradeBeingReverted = s_upgradeDAO.findUpgrade(
+          upgradeContext.getPatchRevertUpgradeId());
+
+      upgradeBeingReverted.setRevertAllowed(false);
+      upgradeBeingReverted = s_upgradeDAO.merge(upgradeBeingReverted);
+    }
 
     request.persist();
     RequestEntity requestEntity = s_requestDAO.findByPK(request.getId());

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradeDAO.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradeDAO.java 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradeDAO.java
index 4e091fa..22a7505 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradeDAO.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradeDAO.java
@@ -26,6 +26,7 @@ import org.apache.ambari.server.orm.RequiresSession;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.orm.entities.UpgradeGroupEntity;
 import org.apache.ambari.server.orm.entities.UpgradeItemEntity;
+import org.apache.ambari.server.state.RepositoryType;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
 
 import com.google.inject.Inject;
@@ -168,7 +169,8 @@ public class UpgradeDAO {
   }
 
   /**
-   * @param clusterId the cluster id
+   * @param clusterId
+   *          the cluster id
    * @return the upgrade entity, or {@code null} if not found
    */
   @RequiresSession
@@ -181,6 +183,48 @@ public class UpgradeDAO {
     return daoUtils.selectSingle(query);
   }
 
+  /**
+   * Gets the only revertable upgrade if one exists. By definition, only the
+   * most recent {@code RepositoryType#PATCH} or {@code RepositoryType#MAINT}
+   * upgrade which doesn't have a downgrade already is revertable.
+   *
+   * @param clusterId
+   *          the cluster id
+   * @return the upgrade which can be reverted, or {@code null} if not found
+   */
+  @RequiresSession
+  public UpgradeEntity findRevertable(long clusterId) {
+    TypedQuery<UpgradeEntity> query = 
entityManagerProvider.get().createNamedQuery(
+        "UpgradeEntity.findRevertable", UpgradeEntity.class);
+    query.setMaxResults(1);
+    query.setParameter("clusterId", clusterId);
+
+    return daoUtils.selectSingle(query);
+  }
+
+  /**
+   * Gets the only revertable upgrade if one exists. By definition, only the
+   * most recent {@code RepositoryType#PATCH} or {@code RepositoryType#MAINT}
+   * upgrade which doesn't have a downgrade already is revertable.
+   * <p>
+   * This method tries to use some fancy SQL to do the work instead of relying
+   * on columns to be set correctly.
+   *
+   * @param clusterId
+   *          the cluster id
+   * @return the upgrade which can be reverted, or {@code null} if not found
+   */
+  @RequiresSession
+  public UpgradeEntity findRevertableUsingJPQL(long clusterId) {
+    TypedQuery<UpgradeEntity> query = 
entityManagerProvider.get().createNamedQuery(
+        "UpgradeEntity.findRevertableUsingJPQL", UpgradeEntity.class);
+    query.setMaxResults(1);
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("revertableTypes", RepositoryType.REVERTABLE);
+
+    return daoUtils.selectSingle(query);
+  }
+
   @Transactional
   public UpgradeEntity merge(UpgradeEntity upgradeEntity) {
     return entityManagerProvider.get().merge(upgradeEntity);

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 7f4824f..1361c94 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
@@ -34,6 +34,7 @@ import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
 import javax.persistence.OneToOne;
+import javax.persistence.QueryHint;
 import javax.persistence.Table;
 import javax.persistence.TableGenerator;
 
@@ -58,21 +59,42 @@ import com.google.common.base.Objects;
     pkColumnValue = "upgrade_id_seq",
     initialValue = 0)
 @NamedQueries({
-  @NamedQuery(name = "UpgradeEntity.findAll",
-      query = "SELECT u FROM UpgradeEntity u"),
-  @NamedQuery(name = "UpgradeEntity.findAllForCluster",
-      query = "SELECT u FROM UpgradeEntity u WHERE u.clusterId = :clusterId"),
-  @NamedQuery(name = "UpgradeEntity.findUpgrade",
-      query = "SELECT u FROM UpgradeEntity u WHERE u.upgradeId = :upgradeId"),
-  @NamedQuery(name = "UpgradeEntity.findUpgradeByRequestId",
-      query = "SELECT u FROM UpgradeEntity u WHERE u.requestId = :requestId"),
-  @NamedQuery(name = "UpgradeEntity.findLatestForClusterInDirection",
-      query = "SELECT u FROM UpgradeEntity u JOIN RequestEntity r ON 
u.requestId = r.requestId WHERE u.clusterId = :clusterId AND u.direction = 
:direction ORDER BY r.startTime DESC, u.upgradeId DESC"),
-  @NamedQuery(name = "UpgradeEntity.findLatestForCluster",
-      query = "SELECT u FROM UpgradeEntity u JOIN RequestEntity r ON 
u.requestId = r.requestId WHERE u.clusterId = :clusterId ORDER BY r.startTime 
DESC"),
-  @NamedQuery(name = "UpgradeEntity.findAllRequestIds",
-      query = "SELECT upgrade.requestId FROM UpgradeEntity upgrade")
-})
+    @NamedQuery(name = "UpgradeEntity.findAll", query = "SELECT u FROM 
UpgradeEntity u"),
+    @NamedQuery(
+        name = "UpgradeEntity.findAllForCluster",
+        query = "SELECT u FROM UpgradeEntity u WHERE u.clusterId = 
:clusterId"),
+    @NamedQuery(
+        name = "UpgradeEntity.findUpgrade",
+        query = "SELECT u FROM UpgradeEntity u WHERE u.upgradeId = 
:upgradeId"),
+    @NamedQuery(
+        name = "UpgradeEntity.findUpgradeByRequestId",
+        query = "SELECT u FROM UpgradeEntity u WHERE u.requestId = 
:requestId"),
+    @NamedQuery(
+        name = "UpgradeEntity.findLatestForClusterInDirection",
+        query = "SELECT u FROM UpgradeEntity u JOIN RequestEntity r ON 
u.requestId = r.requestId WHERE u.clusterId = :clusterId AND u.direction = 
:direction ORDER BY r.startTime DESC, u.upgradeId DESC"),
+    @NamedQuery(
+        name = "UpgradeEntity.findLatestForCluster",
+        query = "SELECT u FROM UpgradeEntity u JOIN RequestEntity r ON 
u.requestId = r.requestId WHERE u.clusterId = :clusterId ORDER BY r.startTime 
DESC"),
+    @NamedQuery(
+        name = "UpgradeEntity.findAllRequestIds",
+        query = "SELECT upgrade.requestId FROM UpgradeEntity upgrade"),
+    @NamedQuery(
+        name = "UpgradeEntity.findRevertable",
+        query = "SELECT upgrade FROM UpgradeEntity upgrade WHERE 
upgrade.revertAllowed = 1 AND upgrade.clusterId = :clusterId ORDER BY 
upgrade.upgradeId DESC",
+        hints = {
+            @QueryHint(name = "eclipselink.query-results-cache", value = 
"true"),
+            @QueryHint(name = "eclipselink.query-results-cache.ignore-null", 
value = "false"),
+            @QueryHint(name = "eclipselink.query-results-cache.size", value = 
"1")
+          }),
+    @NamedQuery(
+        name = "UpgradeEntity.findRevertableUsingJPQL",
+        query = "SELECT upgrade FROM UpgradeEntity upgrade WHERE 
upgrade.repoVersionId IN (SELECT upgrade.repoVersionId FROM UpgradeEntity 
upgrade WHERE upgrade.clusterId = :clusterId AND upgrade.orchestration IN 
:revertableTypes GROUP BY upgrade.repoVersionId HAVING 
MOD(COUNT(upgrade.repoVersionId), 2) != 0) ORDER BY upgrade.upgradeId DESC",
+        hints = {
+            @QueryHint(name = "eclipselink.query-results-cache", value = 
"true"),
+            @QueryHint(name = "eclipselink.query-results-cache.ignore-null", 
value = "false"),
+            @QueryHint(name = "eclipselink.query-results-cache.size", value = 
"1")
+          })
+        })
 public class UpgradeEntity {
 
   @Id
@@ -107,6 +129,9 @@ public class UpgradeEntity {
   @Enumerated(value = EnumType.STRING)
   private UpgradeType upgradeType;
 
+  @Column(name = "repo_version_id", insertable = false, updatable = false)
+  private Long repoVersionId;
+
   @JoinColumn(name = "repo_version_id", referencedColumnName = 
"repo_version_id", nullable = false)
   private RepositoryVersionEntity repositoryVersion;
 
@@ -117,7 +142,26 @@ public class UpgradeEntity {
   private Integer skipServiceCheckFailures = 0;
 
   @Column(name="downgrade_allowed", nullable = false)
-  private Short downgrade_allowed = 1;
+  private Short downgradeAllowed = 1;
+
+  /**
+   * Whether this upgrade is a candidate to be reverted. The current 
restriction
+   * on this behavior is that only the most recent
+   * {@link RepositoryType#PATCH}/{@link RepositoryType#MAINT} for a given
+   * cluster can be reverted at a time.
+   * <p/>
+   * All upgrades are created with this value defaulted to {@code false}. Upon
+   * successful finalization of the upgrade, if the upgrade was the correct 
type
+   * and direction, then it becomes a candidate for reversion and this value is
+   * set to {@code true}. If an upgrade is reverted after being finalized, then
+   * this value to should set to {@code false} explicitely.
+   * <p/>
+   * There can exist <i>n</i> number of upgrades with this value set to
+   * {@code true}. The idea is that only the most recent upgrade with this 
value
+   * set to {@code true} will be able to be reverted.
+   */
+  @Column(name = "revert_allowed", nullable = false)
+  private Short revertAllowed = 0;
 
   @Column(name="orchestration", nullable = false)
   @Enumerated(value = EnumType.STRING)
@@ -222,18 +266,45 @@ public class UpgradeEntity {
    * @return possibility to process downgrade
    */
   public Boolean isDowngradeAllowed() {
-    return downgrade_allowed != null ? (downgrade_allowed != 0) : null;
+    return downgradeAllowed != null ? (downgradeAllowed != 0) : null;
   }
 
   /**
    * @param canDowngrade {@code true} to allow downgrade, {@code false} to 
disallow downgrade
    */
   public void setDowngradeAllowed(boolean canDowngrade) {
-    downgrade_allowed = (!canDowngrade ? (short)0 : (short)1);
+    downgradeAllowed = (!canDowngrade ? (short) 0 : (short) 1);
+  }
+
+  /**
+   * Gets whether this upgrade supports being reverted. Upgrades can be 
reverted
+   * (downgraded after finalization) if they are either
+   * {@link RepositoryType#MAINT} or {@link RepositoryType#PATCH} and have 
never
+   * been previously downgraded.
+   *
+   * @return {@code true} if this upgrade can potentially be revereted.
+   */
+  public Boolean isRevertAllowed() {
+    return revertAllowed != null ? (revertAllowed != 0) : null;
+  }
+
+  /**
+   * Sets whether this upgrade supports being reverted. This should only ever 
be
+   * called from the finalization of an upgrade. {@link RepositoryType#MAINT} 
or
+   * {@link RepositoryType#PATCH} upgrades can be revereted only if they have
+   * not previously been downgraded.
+   *
+   * @param revertable
+   *          {@code true} to mark this as being revertable, {@code false}
+   *          otherwise.
+   */
+  public void setRevertAllowed(boolean revertable) {
+    revertAllowed = (!revertable ? (short) 0 : (short) 1);
   }
 
   /**
-   * @param upgradeType the upgrade type to set
+   * @param upgradeType
+   *          the upgrade type to set
    */
   public void setUpgradeType(UpgradeType upgradeType) {
     this.upgradeType = upgradeType;

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AbstractUpgradeServerAction.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AbstractUpgradeServerAction.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AbstractUpgradeServerAction.java
index e012dac..8ebb186 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AbstractUpgradeServerAction.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AbstractUpgradeServerAction.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ambari.server.serveraction.upgrades;
 
+import org.apache.ambari.server.orm.dao.UpgradeDAO;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.serveraction.AbstractServerAction;
 import org.apache.ambari.server.state.Cluster;
@@ -42,6 +43,12 @@ public abstract class AbstractUpgradeServerAction extends 
AbstractServerAction {
   protected UpgradeHelper m_upgradeHelper;
 
   /**
+   * Used to lookup or update {@link UpgradeEntity} instances.
+   */
+  @Inject
+  protected UpgradeDAO m_upgradeDAO;
+
+  /**
    * Used to create instances of {@link UpgradeContext} with injected
    * dependencies.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 26dcf27..5ec0692 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
@@ -41,6 +41,7 @@ import org.apache.ambari.server.orm.dao.HostVersionDAO;
 import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
 import org.apache.ambari.server.orm.entities.HostVersionEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
+import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.RepositoryType;
@@ -104,6 +105,9 @@ public class FinalizeUpgradeAction extends 
AbstractUpgradeServerAction {
   private CommandReport finalizeUpgrade(UpgradeContext upgradeContext)
     throws AmbariException, InterruptedException {
 
+    Direction direction = upgradeContext.getDirection();
+    RepositoryType repositoryType = upgradeContext.getOrchestrationType();
+
     StringBuilder outSB = new StringBuilder();
     StringBuilder errSB = new StringBuilder();
 
@@ -198,6 +202,13 @@ public class FinalizeUpgradeAction extends 
AbstractUpgradeServerAction {
       // longer used
       finalizeHostRepositoryVersions(cluster);
 
+      // mark revertable
+      if (repositoryType.isRevertable() && direction == Direction.UPGRADE) {
+        UpgradeEntity upgrade = cluster.getUpgradeInProgress();
+        upgrade.setRevertAllowed(true);
+        upgrade = m_upgradeDAO.merge(upgrade);
+      }
+
       // Reset upgrade state
       cluster.setUpgradeEntity(null);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/main/java/org/apache/ambari/server/state/RepositoryType.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/RepositoryType.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/RepositoryType.java
index 3f7d447..bf7eab0 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/RepositoryType.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/RepositoryType.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.state;
 
+import java.util.EnumSet;
+
 /**
  * Identifies the type of repository
  */
@@ -45,6 +47,11 @@ public enum RepositoryType {
    */
   SERVICE;
 
+  /**
+   * The types of repositories which are revertable.
+   */
+  public static final EnumSet<RepositoryType> REVERTABLE = 
EnumSet.of(RepositoryType.MAINT,
+      RepositoryType.PATCH);
 
   /**
    * Gets whether applications of this repository are revertable after they 
have

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 64da7c3..67a8950 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
@@ -252,6 +252,11 @@ public class UpgradeContext {
   private final boolean m_isRevert;
 
   /**
+   * The ID of the upgrade being reverted if this is a reversion.
+   */
+  private long m_revertUpgradeId;
+
+  /**
    * Defines orchestration type.  This is not the repository type when 
reverting a patch.
    */
   private RepositoryType m_orchestration = RepositoryType.STANDARD;
@@ -286,11 +291,19 @@ public class UpgradeContext {
     m_isRevert = upgradeRequestMap.containsKey(UPGRADE_REVERT_UPGRADE_ID);
 
     if (m_isRevert) {
-      Long revertUpgradeId = 
Long.valueOf(upgradeRequestMap.get(UPGRADE_REVERT_UPGRADE_ID).toString());
-      UpgradeEntity revertUpgrade = m_upgradeDAO.findUpgrade(revertUpgradeId);
+      m_revertUpgradeId = 
Long.valueOf(upgradeRequestMap.get(UPGRADE_REVERT_UPGRADE_ID).toString());
+      UpgradeEntity revertUpgrade = 
m_upgradeDAO.findUpgrade(m_revertUpgradeId);
+      UpgradeEntity revertableUpgrade = 
m_upgradeDAO.findRevertable(cluster.getClusterId());
 
       if (null == revertUpgrade) {
-          throw new AmbariException(String.format("Could not find Upgrade with 
id %s to revert.", revertUpgradeId));
+        throw new AmbariException(
+            String.format("Could not find Upgrade with id %s to revert.", 
m_revertUpgradeId));
+      }
+
+      if (null == revertableUpgrade) {
+        throw new AmbariException(
+            String.format("There are no upgrades for cluster %s which are 
marked as revertable",
+                cluster.getClusterName()));
       }      
       
       if (!revertUpgrade.getOrchestration().isRevertable()) {
@@ -299,7 +312,15 @@ public class UpgradeContext {
       }
 
       if (revertUpgrade.getDirection() != Direction.UPGRADE) {
-        throw new AmbariException("Can only revert successful upgrades, not 
downgrades.");
+        throw new AmbariException(
+            "Only successfully completed upgrades can be reverted. Downgrades 
cannot be reverted.");
+      }
+
+      if (revertableUpgrade.getId() != revertUpgrade.getId()) {
+        throw new AmbariException(String.format(
+            "The only upgrade which is currently allowed to be reverted for 
cluster %s is upgrade ID %s which was an upgrade to %s",
+            cluster.getClusterName(), revertableUpgrade.getId(),
+            revertableUpgrade.getRepositoryVersion().getVersion()));
       }
 
       Set<RepositoryVersionEntity> priors = new HashSet<>();
@@ -858,6 +879,10 @@ public class UpgradeContext {
     return m_isRevert;
   }
 
+  public long getPatchRevertUpgradeId() {
+    return m_revertUpgradeId;
+  }
+
   /**
    * Gets a POJO of the upgrade suitable to serialize.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog260.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog260.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog260.java
index 665b350..d1de998 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog260.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog260.java
@@ -93,6 +93,7 @@ public class UpgradeCatalog260 extends AbstractUpgradeCatalog 
{
   public static final String FROM_REPO_VERSION_ID_COLUMN = 
"from_repo_version_id";
   public static final String TO_REPO_VERSION_ID_COLUMN = "to_repo_version_id";
   public static final String ORCHESTRATION_COLUMN = "orchestration";
+  public static final String ALLOW_REVERT_COLUMN = "revert_allowed";
   public static final String FK_UPGRADE_FROM_REPO_ID = 
"FK_upgrade_from_repo_id";
   public static final String FK_UPGRADE_TO_REPO_ID = "FK_upgrade_to_repo_id";
   public static final String FK_UPGRADE_REPO_VERSION_ID = 
"FK_upgrade_repo_version_id";
@@ -146,6 +147,7 @@ public class UpgradeCatalog260 extends 
AbstractUpgradeCatalog {
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getTargetVersion() {
     return "2.6.0";
   }
@@ -187,7 +189,7 @@ public class UpgradeCatalog260 extends 
AbstractUpgradeCatalog {
         EntityManager entityManager = getEntityManagerProvider().get();
         Query query = 
entityManager.createNamedQuery("ClusterConfigEntity.findNotMappedClusterConfigsToService",ClusterConfigEntity.class);
 
-        List<ClusterConfigEntity> notMappedConfigs =  
(List<ClusterConfigEntity>) query.getResultList();
+        List<ClusterConfigEntity> notMappedConfigs =  query.getResultList();
         if (notMappedConfigs != null) {
           for (ClusterConfigEntity clusterConfigEntity : notMappedConfigs) {
             clusterConfigEntity.setUnmapped(true);
@@ -243,9 +245,13 @@ public class UpgradeCatalog260 extends 
AbstractUpgradeCatalog {
 
     dbAccessor.addColumn(UPGRADE_TABLE,
         new DBAccessor.DBColumnInfo(REPO_VERSION_ID_COLUMN, Long.class, null, 
null, false));
+
     dbAccessor.addColumn(UPGRADE_TABLE,
         new DBAccessor.DBColumnInfo(ORCHESTRATION_COLUMN, String.class, 255, 
STANDARD, false));
 
+    dbAccessor.addColumn(UPGRADE_TABLE,
+        new DBAccessor.DBColumnInfo(ALLOW_REVERT_COLUMN, Short.class, null, 0, 
false));
+
     dbAccessor.addFKConstraint(UPGRADE_TABLE, FK_UPGRADE_REPO_VERSION_ID, 
REPO_VERSION_ID_COLUMN, REPO_VERSION_TABLE, REPO_VERSION_ID_COLUMN, false);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 dc7f79a..e7359a7 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -819,6 +819,7 @@ CREATE TABLE upgrade (
   skip_failures SMALLINT DEFAULT 0 NOT NULL,
   skip_sc_failures SMALLINT DEFAULT 0 NOT NULL,
   downgrade_allowed SMALLINT DEFAULT 1 NOT NULL,
+  revert_allowed SMALLINT DEFAULT 0 NOT NULL,
   suspended SMALLINT DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 0c28012..c1e1953 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -836,6 +836,7 @@ CREATE TABLE upgrade (
   skip_failures TINYINT(1) NOT NULL DEFAULT 0,
   skip_sc_failures TINYINT(1) NOT NULL DEFAULT 0,
   downgrade_allowed TINYINT(1) NOT NULL DEFAULT 1,
+  revert_allowed TINYINT(1) NOT NULL DEFAULT 0,
   suspended TINYINT(1) DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 6cd330c..c0b2f0c 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -815,6 +815,7 @@ CREATE TABLE upgrade (
   skip_failures NUMBER(1) DEFAULT 0 NOT NULL,
   skip_sc_failures NUMBER(1) DEFAULT 0 NOT NULL,
   downgrade_allowed NUMBER(1) DEFAULT 1 NOT NULL,
+  revert_allowed NUMBER(1) DEFAULT 0 NOT NULL,
   suspended NUMBER(1) DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 8c8ed5c..90cdbfe 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -817,6 +817,7 @@ CREATE TABLE upgrade (
   skip_failures SMALLINT DEFAULT 0 NOT NULL,
   skip_sc_failures SMALLINT DEFAULT 0 NOT NULL,
   downgrade_allowed SMALLINT DEFAULT 1 NOT NULL,
+  revert_allowed SMALLINT DEFAULT 0 NOT NULL,
   suspended SMALLINT DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 59abd8b..7f39535 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -813,6 +813,7 @@ CREATE TABLE upgrade (
   skip_failures BIT NOT NULL DEFAULT 0,
   skip_sc_failures BIT NOT NULL DEFAULT 0,
   downgrade_allowed BIT NOT NULL DEFAULT 1,
+  revert_allowed BIT NOT NULL DEFAULT 0,
   suspended BIT DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 ea92256..aa06c4d 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -834,6 +834,7 @@ CREATE TABLE upgrade (
   skip_failures BIT NOT NULL DEFAULT 0,
   skip_sc_failures BIT NOT NULL DEFAULT 0,
   downgrade_allowed BIT NOT NULL DEFAULT 1,
+  revert_allowed BIT NOT NULL DEFAULT 0,
   suspended BIT DEFAULT 0 NOT NULL,
   CONSTRAINT PK_upgrade PRIMARY KEY CLUSTERED (upgrade_id),
   FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id),

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/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 5eca6b3..37a7b44 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
@@ -1608,6 +1608,7 @@ public class UpgradeResourceProviderTest extends 
EasyMockSupport {
     sch.setVersion("2.1.1.0");
 
     File f = new File("src/test/resources/hbase_version_test.xml");
+    repoVersionEntity2112.setType(RepositoryType.PATCH);
     repoVersionEntity2112.setVersionXml(IOUtils.toString(new 
FileInputStream(f)));
     repoVersionEntity2112.setVersionXsd("version_definition.xsd");
     repoVersionDao.merge(repoVersionEntity2112);
@@ -1629,9 +1630,17 @@ public class UpgradeResourceProviderTest extends 
EasyMockSupport {
 
     upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
     assertEquals(1, upgrades.size());
+
     UpgradeEntity upgradeEntity = upgrades.get(0);
     assertEquals(RepositoryType.PATCH, upgradeEntity.getOrchestration());
 
+    // should be false since only finalization actually sets this bit
+    assertEquals(false, upgradeEntity.isRevertAllowed());
+
+    // fake it now so the rest of the test passes
+    upgradeEntity.setRevertAllowed(true);
+    upgradeEntity = upgradeDao.merge(upgradeEntity);
+
     // !!! make it look like the cluster is done
     cluster.setUpgradeEntity(null);
 
@@ -1671,6 +1680,59 @@ public class UpgradeResourceProviderTest extends 
EasyMockSupport {
     assertTrue(found);
   }
 
+  /**
+   * Tests that when there is no revertable upgrade, a reversion of a specific
+   * ugprade ID is not allowed.
+   */
+  @Test(expected = SystemException.class)
+  public void testRevertFailsWhenNoRevertableUpgradeIsFound() throws Exception 
{
+    Cluster cluster = clusters.getCluster("c1");
+
+    // add a single ZK server and client on 2.1.1.0
+    Service service = cluster.addService("HBASE", repoVersionEntity2110);
+    ServiceComponent component = service.addServiceComponent("HBASE_MASTER");
+    ServiceComponentHost sch = component.addServiceComponentHost("h1");
+    sch.setVersion("2.1.1.0");
+
+    File f = new File("src/test/resources/hbase_version_test.xml");
+    repoVersionEntity2112.setType(RepositoryType.PATCH);
+    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);
+  }
+
   private String parseSingleMessage(String msgStr){
     JsonParser parser = new JsonParser();
     JsonArray msgArray = (JsonArray) parser.parse(msgStr);

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/UpgradeDAOTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/UpgradeDAOTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/UpgradeDAOTest.java
index f23e10d..47fde03 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/UpgradeDAOTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/UpgradeDAOTest.java
@@ -35,9 +35,11 @@ import 
org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.OrmTestHelper;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.RequestEntity;
+import org.apache.ambari.server.orm.entities.StageEntity;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.orm.entities.UpgradeGroupEntity;
 import org.apache.ambari.server.orm.entities.UpgradeItemEntity;
+import org.apache.ambari.server.state.RepositoryType;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.UpgradeState;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
@@ -240,4 +242,105 @@ public class UpgradeDAOTest {
     Assert.assertTrue(lastUpgradeForCluster.isComponentFailureAutoSkipped());
     
Assert.assertTrue(lastUpgradeForCluster.isServiceCheckFailureAutoSkipped());
   }
-}
+
+  /**
+   * Tests the logic that finds the one-and-only revertable upgrade.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testFindRevertableUpgrade() throws Exception {
+    // create upgrade entities
+    UpgradeEntity revertable = dao.findRevertable(1L);
+    UpgradeEntity revertableViaJPQL = dao.findRevertableUsingJPQL(1L);
+    assertEquals(null, revertable);
+    assertEquals(null, revertableViaJPQL);
+
+    RequestEntity requestEntity = new RequestEntity();
+    requestEntity.setRequestId(1L);
+    requestEntity.setClusterId(clusterId.longValue());
+    requestEntity.setStatus(HostRoleStatus.PENDING);
+    requestEntity.setStages(new ArrayList<StageEntity>());
+    requestDAO.create(requestEntity);
+
+    UpgradeEntity entity1 = new UpgradeEntity();
+    entity1.setId(11L);
+    entity1.setClusterId(clusterId.longValue());
+    entity1.setDirection(Direction.UPGRADE);
+    entity1.setRequestEntity(requestEntity);
+    entity1.setRepositoryVersion(repositoryVersion2500);
+    entity1.setUpgradeType(UpgradeType.ROLLING);
+    entity1.setUpgradePackage("test-upgrade");
+    entity1.setDowngradeAllowed(true);
+    entity1.setOrchestration(RepositoryType.PATCH);
+    entity1.setRevertAllowed(true);
+    dao.create(entity1);
+
+    revertable = dao.findRevertable(1L);
+    revertableViaJPQL = dao.findRevertableUsingJPQL(1L);
+    assertEquals(revertable.getId(), entity1.getId());
+    assertEquals(revertableViaJPQL.getId(), entity1.getId());
+
+    UpgradeEntity entity2 = new UpgradeEntity();
+    entity2.setId(22L);
+    entity2.setClusterId(clusterId.longValue());
+    entity2.setDirection(Direction.UPGRADE);
+    entity2.setRequestEntity(requestEntity);
+    entity2.setRepositoryVersion(repositoryVersion2511);
+    entity2.setUpgradeType(UpgradeType.ROLLING);
+    entity2.setUpgradePackage("test-upgrade");
+    entity2.setDowngradeAllowed(true);
+    entity2.setOrchestration(RepositoryType.MAINT);
+    entity2.setRevertAllowed(true);
+    dao.create(entity2);
+
+    revertable = dao.findRevertable(1L);
+    revertableViaJPQL = dao.findRevertableUsingJPQL(1L);
+    assertEquals(revertable.getId(), entity2.getId());
+    assertEquals(revertableViaJPQL.getId(), entity2.getId());
+
+    // now make it look like upgrade ID 22 was reverted
+    entity2.setRevertAllowed(false);
+    entity2 = dao.merge(entity2);
+
+    // create a downgrade for ID 22
+    UpgradeEntity entity3 = new UpgradeEntity();
+    entity3.setId(33L);
+    entity3.setClusterId(clusterId.longValue());
+    entity3.setDirection(Direction.DOWNGRADE);
+    entity3.setRequestEntity(requestEntity);
+    entity3.setRepositoryVersion(repositoryVersion2511);
+    entity3.setUpgradeType(UpgradeType.ROLLING);
+    entity3.setUpgradePackage("test-upgrade");
+    entity3.setOrchestration(RepositoryType.MAINT);
+    entity3.setDowngradeAllowed(false);
+    dao.create(entity3);
+
+    revertable = dao.findRevertable(1L);
+    revertableViaJPQL = dao.findRevertableUsingJPQL(1L);
+    assertEquals(revertable.getId(), entity1.getId());
+    assertEquals(revertableViaJPQL.getId(), entity1.getId());
+
+    // now make it look like upgrade ID 11 was reverted
+    entity1.setRevertAllowed(false);
+    entity1 = dao.merge(entity1);
+
+    // create a downgrade for ID 11
+    UpgradeEntity entity4 = new UpgradeEntity();
+    entity4.setId(44L);
+    entity4.setClusterId(clusterId.longValue());
+    entity4.setDirection(Direction.DOWNGRADE);
+    entity4.setRequestEntity(requestEntity);
+    entity4.setRepositoryVersion(repositoryVersion2500);
+    entity4.setUpgradeType(UpgradeType.ROLLING);
+    entity4.setUpgradePackage("test-upgrade");
+    entity4.setOrchestration(RepositoryType.MAINT);
+    entity4.setDowngradeAllowed(false);
+    dao.create(entity4);
+
+    revertable = dao.findRevertable(1L);
+    revertableViaJPQL = dao.findRevertableUsingJPQL(1L);
+    assertEquals(null, revertable);
+    assertEquals(null, revertableViaJPQL);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeContextTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeContextTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeContextTest.java
index 629ea9b..dc77fa6 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeContextTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeContextTest.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.internal.UpgradeResourceProvider;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
 import org.apache.ambari.server.orm.dao.UpgradeDAO;
@@ -138,6 +139,7 @@ public class UpgradeContextTest extends EasyMockSupport {
         m_upgradeDAO.findLastUpgradeForCluster(EasyMock.anyLong(),
             
eq(Direction.UPGRADE))).andReturn(m_completedRevertableUpgrade).anyTimes();
 
+    expect(m_completedRevertableUpgrade.getId()).andReturn(1L).anyTimes();
     
expect(m_completedRevertableUpgrade.getDirection()).andReturn(Direction.UPGRADE).anyTimes();
     
expect(m_completedRevertableUpgrade.getRepositoryVersion()).andReturn(m_targetRepositoryVersion).anyTimes();
     
expect(m_completedRevertableUpgrade.getOrchestration()).andReturn(RepositoryType.PATCH).anyTimes();
@@ -309,6 +311,8 @@ public class UpgradeContextTest extends EasyMockSupport {
         EasyMock.anyObject(UpgradeType.class), 
EasyMock.anyString())).andReturn(upgradePack).once();
 
 
+    
expect(m_upgradeDAO.findRevertable(1L)).andReturn(m_completedRevertableUpgrade).once();
+
     Map<String, Object> requestMap = new HashMap<>();
     requestMap.put(UpgradeResourceProvider.UPGRADE_TYPE, 
UpgradeType.ROLLING.name());
     requestMap.put(UpgradeResourceProvider.UPGRADE_REVERT_UPGRADE_ID, "1");
@@ -327,6 +331,52 @@ public class UpgradeContextTest extends EasyMockSupport {
   }
 
   /**
+   * Tests that if a different {@link UpgradeEntity} is returned instead of 
the one
+   * specified by the
+   *
+   * @throws Exception
+   */
+  @Test(expected = AmbariException.class)
+  public void testWrongUpgradeBeingReverted() throws Exception {
+    Long upgradeIdBeingReverted = 1L;
+    Long upgradeIdWhichCanBeReverted = 99L;
+
+    UpgradeHelper upgradeHelper = createNiceMock(UpgradeHelper.class);
+    ConfigHelper configHelper = createNiceMock(ConfigHelper.class);
+
+    UpgradePack upgradePack = createNiceMock(UpgradePack.class);
+
+    expect(upgradeHelper.suggestUpgradePack(EasyMock.anyString(), 
EasyMock.anyObject(StackId.class),
+        EasyMock.anyObject(StackId.class), EasyMock.anyObject(Direction.class),
+        EasyMock.anyObject(UpgradeType.class), 
EasyMock.anyString())).andReturn(upgradePack).once();
+
+    RepositoryVersionEntity repositoryVersionEntity = 
createNiceMock(RepositoryVersionEntity.class);
+    
expect(repositoryVersionEntity.getVersion()).andReturn("1.2.3.4").anyTimes();
+
+    UpgradeEntity wrongRevertableUpgrade = createNiceMock(UpgradeEntity.class);
+    
expect(wrongRevertableUpgrade.getId()).andReturn(upgradeIdWhichCanBeReverted).atLeastOnce();
+    
expect(wrongRevertableUpgrade.getRepositoryVersion()).andReturn(repositoryVersionEntity).atLeastOnce();
+
+    
expect(m_upgradeDAO.findRevertable(1L)).andReturn(wrongRevertableUpgrade).once();
+
+    Map<String, Object> requestMap = new HashMap<>();
+    requestMap.put(UpgradeResourceProvider.UPGRADE_TYPE, 
UpgradeType.ROLLING.name());
+    requestMap.put(UpgradeResourceProvider.UPGRADE_REVERT_UPGRADE_ID, 
upgradeIdBeingReverted.toString());
+
+    replayAll();
+
+    UpgradeContext context = new UpgradeContext(m_cluster, requestMap, null, 
upgradeHelper,
+        m_upgradeDAO, m_repositoryVersionDAO, configHelper);
+
+    assertEquals(Direction.DOWNGRADE, context.getDirection());
+    assertEquals(RepositoryType.PATCH, context.getOrchestrationType());
+    assertEquals(1, context.getSupportedServices().size());
+    assertTrue(context.isPatchRevert());
+
+    verifyAll();
+  }
+
+  /**
    * Tests that the {@link UpgradeContext} for a patch downgrade has the
    * correcting scope/orchestration set.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5d697c/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog260Test.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog260Test.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog260Test.java
index 4b0404d..2a62f2e 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog260Test.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog260Test.java
@@ -181,7 +181,8 @@ public class UpgradeCatalog260Test {
 
     Capture<DBColumnInfo> rvid = newCapture();
     Capture<DBColumnInfo> orchestration = newCapture();
-    expectUpdateUpgradeTable(rvid, orchestration);
+    Capture<DBColumnInfo> revertAllowed = newCapture();
+    expectUpdateUpgradeTable(rvid, orchestration, revertAllowed);
 
     Capture<List<DBAccessor.DBColumnInfo>> columns = newCapture();
     expectCreateUpgradeHistoryTable(columns);
@@ -216,7 +217,7 @@ public class UpgradeCatalog260Test {
     verifyUpdateServiceComponentDesiredStateTable(scdstadd1, scdstalter1, 
scdstadd2, scdstalter2);
     verifyUpdateServiceDesiredStateTable(sdstadd, sdstalter);
     verifyAddSelectedCollumsToClusterconfigTable(selectedColumnInfo, 
selectedmappingColumnInfo, selectedTimestampColumnInfo, 
createTimestampColumnInfo);
-    verifyUpdateUpgradeTable(rvid, orchestration);
+    verifyUpdateUpgradeTable(rvid, orchestration, revertAllowed);
     verifyCreateUpgradeHistoryTable(columns);
     verifyUpdateRepositoryVersionTableTable(repoVersionHiddenColumnCapture);
   }
@@ -299,7 +300,8 @@ public class UpgradeCatalog260Test {
     expectLastCall().once();
   }
 
-  public void verifyUpdateUpgradeTable(Capture<DBColumnInfo> rvid, 
Capture<DBColumnInfo> orchestration) {
+  public void verifyUpdateUpgradeTable(Capture<DBColumnInfo> rvid,
+      Capture<DBColumnInfo> orchestration, Capture<DBColumnInfo> 
revertAllowed) {
     DBColumnInfo rvidValue = rvid.getValue();
     Assert.assertEquals(UpgradeCatalog260.REPO_VERSION_ID_COLUMN, 
rvidValue.getName());
     Assert.assertEquals(Long.class, rvidValue.getType());
@@ -313,25 +315,43 @@ public class UpgradeCatalog260Test {
     Assert.assertEquals(Integer.valueOf(255), orchestrationValue.getLength());
     Assert.assertEquals(UpgradeCatalog260.STANDARD, 
orchestrationValue.getDefaultValue());
     Assert.assertEquals(false, orchestrationValue.isNullable());
+
+    DBColumnInfo revertAllowedValue = revertAllowed.getValue();
+    Assert.assertEquals(UpgradeCatalog260.ALLOW_REVERT_COLUMN, 
revertAllowedValue.getName());
+    Assert.assertEquals(Short.class, revertAllowedValue.getType());
+    Assert.assertEquals(null, revertAllowedValue.getLength());
+    Assert.assertEquals(0, revertAllowedValue.getDefaultValue());
+    Assert.assertEquals(false, revertAllowedValue.isNullable());
   }
 
-  public void expectUpdateUpgradeTable(Capture<DBColumnInfo> rvid, 
Capture<DBColumnInfo> orchestration) throws SQLException {
+  public void expectUpdateUpgradeTable(Capture<DBColumnInfo> rvid,
+      Capture<DBColumnInfo> orchestration, Capture<DBColumnInfo> revertAllowed)
+      throws SQLException {
+
     dbAccessor.clearTable(eq(UpgradeCatalog260.UPGRADE_TABLE));
     expectLastCall().once();
+
     dbAccessor.dropFKConstraint(eq(UpgradeCatalog260.UPGRADE_TABLE), 
eq(UpgradeCatalog260.FK_UPGRADE_FROM_REPO_ID));
     expectLastCall().once();
+
     dbAccessor.dropFKConstraint(eq(UpgradeCatalog260.UPGRADE_TABLE), 
eq(UpgradeCatalog260.FK_UPGRADE_TO_REPO_ID));
     expectLastCall().once();
+
     dbAccessor.dropColumn(eq(UpgradeCatalog260.UPGRADE_TABLE), 
eq(UpgradeCatalog260.FROM_REPO_VERSION_ID_COLUMN));
     expectLastCall().once();
+
     dbAccessor.dropColumn(eq(UpgradeCatalog260.UPGRADE_TABLE), 
eq(UpgradeCatalog260.TO_REPO_VERSION_ID_COLUMN));
     expectLastCall().once();
 
     dbAccessor.addColumn(eq(UpgradeCatalog260.UPGRADE_TABLE), capture(rvid));
     expectLastCall().once();
+
     dbAccessor.addColumn(eq(UpgradeCatalog260.UPGRADE_TABLE), 
capture(orchestration));
     expectLastCall().once();
 
+    dbAccessor.addColumn(eq(UpgradeCatalog260.UPGRADE_TABLE), 
capture(revertAllowed));
+    expectLastCall().once();
+
     dbAccessor.addFKConstraint(eq(UpgradeCatalog260.UPGRADE_TABLE), 
eq(UpgradeCatalog260.FK_UPGRADE_REPO_VERSION_ID), 
eq(UpgradeCatalog260.REPO_VERSION_ID_COLUMN), 
eq(UpgradeCatalog260.REPO_VERSION_TABLE), 
eq(UpgradeCatalog260.REPO_VERSION_ID_COLUMN), eq(false));
     expectLastCall().once();
   }
@@ -487,7 +507,7 @@ public class UpgradeCatalog260Test {
   @Test
   public void testRemoveDruidSuperset() throws Exception {
 
-    List<Integer> current = new ArrayList<Integer>();
+    List<Integer> current = new ArrayList<>();
     current.add(1);
 
     expect(dbAccessor.getConnection()).andReturn(connection).anyTimes();

Reply via email to