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

[1/2] ambari git commit: AMBARI-13064 - Provide Summary Of Skipped Failures During Upgrade (jonathanhurley)

Repository: ambari
Updated Branches:
  refs/heads/branch-2.1 16a090643 -> e7b946a98


AMBARI-13064 - Provide Summary Of Skipped Failures During Upgrade (jonathanhurley)


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

Branch: refs/heads/branch-2.1
Commit: e96f5bd660bf86c079b4aaaae0ac8a72bd2a12d9
Parents: 16a0906
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Thu Sep 10 15:06:21 2015 -0400
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Sat Sep 12 09:45:54 2015 -0400

----------------------------------------------------------------------
 .../internal/UpgradeResourceProvider.java       |   2 +-
 .../server/orm/dao/HostRoleCommandDAO.java      |  29 +++
 .../orm/entities/HostRoleCommandEntity.java     |  19 +-
 .../upgrades/AutoSkipFailedSummaryAction.java   | 189 +++++++++++++++++++
 .../ambari/server/state/UpgradeHelper.java      |  16 +-
 .../ambari/server/state/stack/UpgradePack.java  |   1 +
 .../state/stack/upgrade/ClusterGrouping.java    |  35 ++--
 .../state/stack/upgrade/ColocatedGrouping.java  |  26 ++-
 .../server/state/stack/upgrade/Grouping.java    |  33 ++--
 .../state/stack/upgrade/ServerActionTask.java   |   2 -
 .../stack/upgrade/ServiceCheckGrouping.java     |  49 +++--
 .../stack/upgrade/StageWrapperBuilder.java      | 127 ++++++++++++-
 .../server/orm/dao/HostRoleCommandDAOTest.java  | 155 +++++++++++++++
 .../stack/upgrade/StageWrapperBuilderTest.java  | 162 ++++++++++++++++
 14 files changed, 784 insertions(+), 61 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/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 5824514..48e9e1a 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
@@ -36,7 +36,6 @@ import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import com.google.gson.Gson;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.Role;
 import org.apache.ambari.server.RoleCommand;
@@ -106,6 +105,7 @@ import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
index e57b450..f04c868 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
@@ -306,6 +306,35 @@ public class HostRoleCommandDAO {
   }
 
   /**
+   * Finds all the {@link HostRoleCommandEntity}s for the given request that are
+   * between the specified stage IDs and have the specified status.
+   *
+   * @param requestId
+   *          the request ID
+   * @param status
+   *          the command status to query for (not {@code null}).
+   * @param minStageId
+   *          the lowest stage ID to requests tasks for.
+   * @param maxStageId
+   *          the highest stage ID to request tasks for.
+   * @return the tasks that satisfy the specified parameters.
+   */
+  @RequiresSession
+  public List<HostRoleCommandEntity> findByStatusBetweenStages(long requestId,
+      HostRoleStatus status, long minStageId, long maxStageId) {
+
+    TypedQuery<HostRoleCommandEntity> query = entityManagerProvider.get().createNamedQuery(
+        "HostRoleCommandEntity.findByStatusBetweenStages", HostRoleCommandEntity.class);
+
+    query.setParameter("requestId", requestId);
+    query.setParameter("status", status);
+    query.setParameter("minStageId", minStageId);
+    query.setParameter("maxStageId", maxStageId);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
    * Gets requests that have tasks in any of the specified statuses.
    *
    * @param statuses

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java
index f0b0cb7..c005efc 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java
@@ -57,7 +57,8 @@ import org.apache.commons.lang.ArrayUtils;
 @NamedQueries({
     @NamedQuery(name = "HostRoleCommandEntity.findCountByCommandStatuses", query = "SELECT COUNT(command.taskId) FROM HostRoleCommandEntity command WHERE command.status IN :statuses"),
     @NamedQuery(name = "HostRoleCommandEntity.findByCommandStatuses", query = "SELECT command FROM HostRoleCommandEntity command WHERE command.status IN :statuses ORDER BY command.requestId, command.stageId"),
-    @NamedQuery(name = "HostRoleCommandEntity.findByHostId", query = "SELECT command FROM HostRoleCommandEntity command WHERE command.hostId=:hostId")
+    @NamedQuery(name = "HostRoleCommandEntity.findByHostId", query = "SELECT command FROM HostRoleCommandEntity command WHERE command.hostId=:hostId"),
+    @NamedQuery(name = "HostRoleCommandEntity.findByStatusBetweenStages", query = "SELECT command FROM HostRoleCommandEntity command WHERE command.requestId = :requestId AND command.stageId >= :minStageId AND command.stageId <= :maxStageId AND command.status = :status")
 })
 public class HostRoleCommandEntity {
 
@@ -490,4 +491,20 @@ public class HostRoleCommandEntity {
   public void setTopologyLogicalTaskEntity(TopologyLogicalTaskEntity topologyLogicalTaskEntity) {
     this.topologyLogicalTaskEntity = topologyLogicalTaskEntity;
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("HostRoleCommandEntity{ ");
+    buffer.append("taskId").append(taskId);
+    buffer.append(", stageId=").append(stageId);
+    buffer.append(", requestId=").append(requestId);
+    buffer.append(", role=").append(role);
+    buffer.append(", roleCommand=").append(roleCommand);
+    buffer.append(", exitcode=").append(exitcode);
+    buffer.append("}");
+    return buffer.toString();
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryAction.java
new file mode 100644
index 0000000..9a84e38
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryAction.java
@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.serveraction.upgrades;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.HostRoleCommand;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.actionmanager.ServiceComponentHostEventWrapper;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.dao.UpgradeDAO;
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+import org.apache.ambari.server.orm.entities.UpgradeGroupEntity;
+import org.apache.ambari.server.orm.entities.UpgradeItemEntity;
+import org.apache.ambari.server.serveraction.AbstractServerAction;
+import org.apache.ambari.server.state.ServiceComponentHostEvent;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+
+/**
+ * The {@link AutoSkipFailedSummaryAction} is used to check if any
+ * {@link HostRoleCommand}s were skipped automatically after they failed during
+ * an upgrade. This will be automatically marked as
+ * {@link HostRoleStatus#COMPLETED} if there are no skipped failures. Otherwise
+ * it will be placed into {@link HostRoleStatus#HOLDING}.
+ */
+public class AutoSkipFailedSummaryAction extends AbstractServerAction {
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(AutoSkipFailedSummaryAction.class);
+
+  /**
+   * The standard output template message.
+   */
+  private static final String FAILURE_STD_OUT_TEMPLATE = "There were {0} skipped failure(s) that must be addressed before you can proceed. Please resolve each failure before continuing with the upgrade.";
+
+  /**
+   * ...
+   */
+  private static final String MIDDLE_ELLIPSIZE_MARKER = "\n\u2026\n";
+
+  /**
+   * Used to lookup the {@link UpgradeGroupEntity}.
+   */
+  @Inject
+  private UpgradeDAO m_upgradeDAO;
+
+  /**
+   * Used to lookup the tasks that need to be checked for
+   * {@link HostRoleStatus#SKIPPED_FAILED}.
+   */
+  @Inject
+  private HostRoleCommandDAO m_hostRoleCommandDAO;
+
+  /**
+   * Used for writing structured out.
+   */
+  @Inject
+  private Gson m_gson;
+
+  /**
+   * A mapping of host -> Map<key,info> for each failure.
+   */
+  private Map<String, Map<String, Object>> m_structuredFailures = new HashMap<>();
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext)
+      throws AmbariException, InterruptedException {
+
+    HostRoleCommand hostRoleCommand = getHostRoleCommand();
+    long requestId = hostRoleCommand.getRequestId();
+    long stageId = hostRoleCommand.getStageId();
+
+    // use the host role command to get to the parent upgrade group
+    UpgradeItemEntity upgradeItem = m_upgradeDAO.findUpgradeItemByRequestAndStage(requestId,stageId);
+    UpgradeGroupEntity upgradeGroup = upgradeItem.getGroupEntity();
+
+    // find all of the stages in this group
+    long upgradeGroupId = upgradeGroup.getId();
+    UpgradeGroupEntity upgradeGroupEntity = m_upgradeDAO.findUpgradeGroup(upgradeGroupId);
+    List<UpgradeItemEntity> groupUpgradeItems = upgradeGroupEntity.getItems();
+    TreeSet<Long> stageIds = new TreeSet<>();
+    for (UpgradeItemEntity groupUpgradeItem : groupUpgradeItems) {
+      stageIds.add(groupUpgradeItem.getStageId());
+    }
+
+    // for every stage, find all tasks that have been SKIPPED_FAILED - we use a
+    // bit of trickery here since within any given request, the stage ID are
+    // always sequential. This allows us to make a simple query instead of some
+    // overly complex IN or NESTED SELECT query
+    long minStageId = stageIds.first();
+    long maxStageId = stageIds.last();
+
+    List<HostRoleCommandEntity> skippedTasks = m_hostRoleCommandDAO.findByStatusBetweenStages(
+        hostRoleCommand.getRequestId(),
+        HostRoleStatus.SKIPPED_FAILED, minStageId, maxStageId);
+
+    if (skippedTasks.isEmpty()) {
+      return createCommandReport(0, HostRoleStatus.COMPLETED, "{}",
+          "There were no skipped failures", null);
+    }
+
+    StringBuilder buffer = new StringBuilder("The following steps failed and were automatically skipped:\n");
+
+    for (HostRoleCommandEntity skippedTask : skippedTasks) {
+      try{
+        ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper(
+            skippedTask.getEvent());
+
+        ServiceComponentHostEvent event = eventWrapper.getEvent();
+
+        String hostName = skippedTask.getHostName();
+        if(null != hostName){
+          Map<String, Object> failures = m_structuredFailures.get(hostName);
+          if( null == failures ){
+            failures = new HashMap<>();
+            m_structuredFailures.put(hostName, failures);
+          }
+
+          failures.put("id", skippedTask.getTaskId());
+          failures.put("exit_code", skippedTask.getExitcode());
+          failures.put("output_log", skippedTask.getOutputLog());
+          failures.put("error_log", skippedTask.getErrorLog());
+
+          String stdOut = StringUtils.abbreviateMiddle(new String(skippedTask.getStdOut()),
+              MIDDLE_ELLIPSIZE_MARKER, 1000);
+
+          String stderr = StringUtils.abbreviateMiddle(new String(skippedTask.getStdError()),
+              MIDDLE_ELLIPSIZE_MARKER, 1000);
+
+          failures.put("stdout", stdOut);
+          failures.put("stderr", stderr);
+        }
+
+        buffer.append(event.getServiceComponentName());
+        if (null != event.getHostName()) {
+          buffer.append(" on ");
+          buffer.append(event.getHostName());
+        }
+
+        buffer.append(": ");
+        buffer.append(skippedTask.getCommandDetail());
+        buffer.append("\n");
+      } catch (Exception exception) {
+        LOG.warn("Unable to extract failure information for {}", skippedTask);
+        buffer.append(": ");
+        buffer.append(skippedTask);
+      }
+    }
+
+    String structuredOutput = m_gson.toJson(m_structuredFailures);
+    String standardOutput = MessageFormat.format(FAILURE_STD_OUT_TEMPLATE, skippedTasks.size());
+    String standardError = buffer.toString();
+
+    return createCommandReport(0, HostRoleStatus.HOLDING, structuredOutput, standardOutput,
+        standardError);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
index 5e63744..75c04da 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/UpgradeHelper.java
@@ -439,9 +439,21 @@ public class UpgradeHelper {
      * List of stages for the group
      */
     public List<StageWrapper> items = new ArrayList<StageWrapper>();
-  }
-
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+      StringBuilder buffer = new StringBuilder("UpgradeGroupHolder{ ");
+      buffer.append("name").append(name);
+      buffer.append(", title=").append(title);
+      buffer.append(", allowRetry=").append(allowRetry);
+      buffer.append(", skippable=").append(skippable);
+      buffer.append("}");
+      return buffer.toString();
+    }
+  }
 
   /**
    * Gets a set of Stages resources to aggregate an UpgradeItem with Stage.

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
index 9691292..3e9aa5f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
@@ -94,6 +94,7 @@ public class UpgradePack {
       if (null == group.intendedDirection || direction == group.intendedDirection) {
         checked.add(group);
       }
+
     }
 
     return checked;

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
index cf58511..eff1b13 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
@@ -31,7 +31,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 
 import org.apache.ambari.server.stack.HostsType;
@@ -59,12 +58,9 @@ public class ClusterGrouping extends Grouping {
   @XmlElement(name="execute-stage")
   public List<ExecuteStage> executionStages;
 
-  @XmlTransient
-  private ClusterBuilder m_builder = new ClusterBuilder();
-
   @Override
   public ClusterBuilder getBuilder() {
-    return m_builder;
+    return new ClusterBuilder(this);
   }
 
 
@@ -100,24 +96,39 @@ public class ClusterGrouping extends Grouping {
 
   public class ClusterBuilder extends StageWrapperBuilder {
 
+    /**
+     * Constructor.
+     *
+     * @param grouping
+     *          the upgrade/downgrade grouping (not {@code null}).
+     */
+    private ClusterBuilder(Grouping grouping) {
+      super(grouping);
+    }
+
     @Override
     public void add(UpgradeContext ctx, HostsType hostsType, String service,
         boolean clientOnly, ProcessingComponent pc) {
       // !!! no-op in this case
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public List<StageWrapper> build(UpgradeContext ctx) {
+    public List<StageWrapper> build(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+
       if (null == executionStages) {
-        return Collections.emptyList();
+        return stageWrappers;
       }
 
-      List<StageWrapper> results = new ArrayList<StageWrapper>();
+      List<StageWrapper> results = new ArrayList<StageWrapper>(stageWrappers);
 
       if (executionStages != null) {
         for (ExecuteStage execution : executionStages) {
-          if (null != execution.intendedDirection &&
-              execution.intendedDirection != ctx.getDirection()) {
+          if (null != execution.intendedDirection
+              && execution.intendedDirection != upgradeContext.getDirection()) {
             continue;
           }
 
@@ -127,7 +138,7 @@ public class ClusterGrouping extends Grouping {
 
           switch (task.getType()) {
             case MANUAL:
-              wrapper = getManualStageWrapper(ctx, execution);
+              wrapper = getManualStageWrapper(upgradeContext, execution);
               break;
 
             case SERVER_ACTION:
@@ -138,7 +149,7 @@ public class ClusterGrouping extends Grouping {
               break;
 
             case EXECUTE:
-              wrapper = getExecuteStageWrapper(ctx, execution);
+              wrapper = getExecuteStageWrapper(upgradeContext, execution);
               break;
 
             default:

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ColocatedGrouping.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ColocatedGrouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ColocatedGrouping.java
index a8e9c43..2aef43c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ColocatedGrouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ColocatedGrouping.java
@@ -54,9 +54,12 @@ public class ColocatedGrouping extends Grouping {
   public Batch batch;
 
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public StageWrapperBuilder getBuilder() {
-    return new MultiHomedBuilder(batch, performServiceCheck);
+    return new MultiHomedBuilder(this, batch, performServiceCheck);
   }
 
   private static class MultiHomedBuilder extends StageWrapperBuilder {
@@ -69,7 +72,9 @@ public class ColocatedGrouping extends Grouping {
     private Map<String, List<TaskProxy>> finalBatches = new LinkedHashMap<String, List<TaskProxy>>();
 
 
-    private MultiHomedBuilder(Batch batch, boolean serviceCheck) {
+    private MultiHomedBuilder(Grouping grouping, Batch batch, boolean serviceCheck) {
+      super(grouping);
+
       m_batch = batch;
       m_serviceCheck = serviceCheck;
     }
@@ -143,16 +148,20 @@ public class ColocatedGrouping extends Grouping {
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public List<StageWrapper> build(UpgradeContext ctx) {
-      List<StageWrapper> results = new ArrayList<StageWrapper>();
+    public List<StageWrapper> build(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+      List<StageWrapper> results = new ArrayList<StageWrapper>(stageWrappers);
 
       if (LOG.isDebugEnabled()) {
         LOG.debug("RU initial: {}", initialBatch);
         LOG.debug("RU final: {}", finalBatches);
       }
 
-      List<StageWrapper> befores = fromProxies(ctx.getDirection(), initialBatch);
+      List<StageWrapper> befores = fromProxies(upgradeContext.getDirection(), initialBatch);
       results.addAll(befores);
 
       if (!befores.isEmpty()) {
@@ -160,16 +169,17 @@ public class ColocatedGrouping extends Grouping {
         ManualTask task = new ManualTask();
         task.summary = m_batch.summary;
         task.message = m_batch.message;
-        formatFirstBatch(ctx, task, befores);
+        formatFirstBatch(upgradeContext, task, befores);
 
         StageWrapper wrapper = new StageWrapper(
             StageWrapper.Type.SERVER_SIDE_ACTION,
-            "Validate Partial " + ctx.getDirection().getText(true),
+            "Validate Partial " + upgradeContext.getDirection().getText(true),
             new TaskWrapper(null, null, Collections.<String>emptySet(), task));
+
         results.add(wrapper);
       }
 
-      results.addAll(fromProxies(ctx.getDirection(), finalBatches));
+      results.addAll(fromProxies(upgradeContext.getDirection(), finalBatches));
 
       return results;
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
index a1e1fcd..cd27722 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
@@ -65,7 +65,7 @@ public class Grouping {
    * Gets the default builder.
    */
   public StageWrapperBuilder getBuilder() {
-    return new DefaultBuilder(performServiceCheck);
+    return new DefaultBuilder(this, performServiceCheck);
   }
 
 
@@ -75,7 +75,8 @@ public class Grouping {
     private Set<String> m_servicesToCheck = new HashSet<String>();
     private boolean m_serviceCheck = true;
 
-    private DefaultBuilder(boolean serviceCheck) {
+    private DefaultBuilder(Grouping grouping, boolean serviceCheck) {
+      super(grouping);
       m_serviceCheck = serviceCheck;
     }
 
@@ -139,23 +140,32 @@ public class Grouping {
       }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public List<StageWrapper> build(UpgradeContext ctx) {
+    public List<StageWrapper> build(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+
+      // insert all pre-processed stage wrappers first
+      if (!stageWrappers.isEmpty()) {
+        m_stages.addAll(0, stageWrappers);
+      }
 
       List<TaskWrapper> tasks = new ArrayList<TaskWrapper>();
       List<String> displays = new ArrayList<String>();
       for (String service : m_servicesToCheck) {
         tasks.add(new TaskWrapper(
             service, "", Collections.<String>emptySet(), new ServiceCheckTask()));
-        displays.add(ctx.getServiceDisplay(service));
+
+        displays.add(upgradeContext.getServiceDisplay(service));
       }
 
-      if (ctx.getDirection().isUpgrade() && m_serviceCheck && m_servicesToCheck.size() > 0) {
-        StageWrapper wrapper = new StageWrapper(
-            StageWrapper.Type.SERVICE_CHECK,
-            "Service Check " + StringUtils.join(displays, ", "),
-            tasks.toArray(new TaskWrapper[0])
-            );
+      if (upgradeContext.getDirection().isUpgrade() && m_serviceCheck
+          && m_servicesToCheck.size() > 0) {
+
+        StageWrapper wrapper = new StageWrapper(StageWrapper.Type.SERVICE_CHECK,
+            "Service Check " + StringUtils.join(displays, ", "), tasks.toArray(new TaskWrapper[0]));
 
         m_stages.add(wrapper);
       }
@@ -168,8 +178,9 @@ public class Grouping {
    * Group all like-typed tasks together.  When they change, create a new type.
    */
   private static List<TaskBucket> buckets(List<Task> tasks) {
-    if (null == tasks || tasks.isEmpty())
+    if (null == tasks || tasks.isEmpty()) {
       return Collections.emptyList();
+    }
 
     List<TaskBucket> holders = new ArrayList<TaskBucket>();
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServerActionTask.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServerActionTask.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServerActionTask.java
index 7a42c3b..74144b7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServerActionTask.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServerActionTask.java
@@ -17,8 +17,6 @@
  */
 package org.apache.ambari.server.state.stack.upgrade;
 
-import org.apache.ambari.server.serveraction.upgrades.FinalizeUpgradeAction;
-
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServiceCheckGrouping.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServiceCheckGrouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServiceCheckGrouping.java
index fdf89cc..6061895 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServiceCheckGrouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ServiceCheckGrouping.java
@@ -58,11 +58,12 @@ public class ServiceCheckGrouping extends Grouping {
   @XmlElement(name="service")
   private Set<String> excludeServices = new HashSet<String>();
 
-  private ServiceCheckBuilder m_builder = new ServiceCheckBuilder();
-
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public ServiceCheckBuilder getBuilder() {
-    return m_builder;
+    return new ServiceCheckBuilder(this);
   }
 
   /**
@@ -76,23 +77,40 @@ public class ServiceCheckGrouping extends Grouping {
    * Used to build stages for service check groupings.
    */
   public class ServiceCheckBuilder extends StageWrapperBuilder {
+
     private Cluster m_cluster;
     private AmbariMetaInfo m_metaInfo;
 
+    /**
+     * Constructor.
+     *
+     * @param grouping
+     *          the upgrade/downgrade grouping (not {@code null}).
+     */
+    protected ServiceCheckBuilder(Grouping grouping) {
+      super(grouping);
+    }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void add(UpgradeContext ctx, HostsType hostsType, String service,
         boolean clientOnly, ProcessingComponent pc) {
       // !!! nothing to do here
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public List<StageWrapper> build(UpgradeContext ctx) {
-      m_cluster = ctx.getCluster();
-      m_metaInfo = ctx.getAmbariMetaInfo();
+    public List<StageWrapper> build(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+      m_cluster = upgradeContext.getCluster();
+      m_metaInfo = upgradeContext.getAmbariMetaInfo();
 
-      List<StageWrapper> result = new ArrayList<StageWrapper>();
-      if (ctx.getDirection().isDowngrade()) {
+      List<StageWrapper> result = new ArrayList<StageWrapper>(stageWrappers);
+      if (upgradeContext.getDirection().isDowngrade()) {
         return result;
       }
 
@@ -101,28 +119,29 @@ public class ServiceCheckGrouping extends Grouping {
       Set<String> clusterServices = new LinkedHashSet<String>(serviceMap.keySet());
 
       // create stages for the priorities
-      for (String service : ServiceCheckGrouping.this.priorityServices) {
-        if (checkServiceValidity(ctx, service, serviceMap)) {
+      for (String service : priorityServices) {
+        if (checkServiceValidity(upgradeContext, service, serviceMap)) {
           StageWrapper wrapper = new StageWrapper(
               StageWrapper.Type.SERVICE_CHECK,
-              "Service Check " + ctx.getServiceDisplay(service),
+              "Service Check " + upgradeContext.getServiceDisplay(service),
               new TaskWrapper(service, "", Collections.<String>emptySet(),
                   new ServiceCheckTask()));
-          result.add(wrapper);
 
+          result.add(wrapper);
           clusterServices.remove(service);
         }
       }
 
       // create stages for everything else, as long it is valid
       for (String service : clusterServices) {
-        if (ServiceCheckGrouping.this.excludeServices.contains(service)) {
+        if (excludeServices.contains(service)) {
           continue;
         }
-        if (checkServiceValidity(ctx, service, serviceMap)) {
+
+        if (checkServiceValidity(upgradeContext, service, serviceMap)) {
           StageWrapper wrapper = new StageWrapper(
               StageWrapper.Type.SERVICE_CHECK,
-              "Service Check " + ctx.getServiceDisplay(service),
+              "Service Check " + upgradeContext.getServiceDisplay(service),
               new TaskWrapper(service, "", Collections.<String>emptySet(),
                   new ServiceCheckTask()));
           result.add(wrapper);

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilder.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilder.java
index f7b37ab..57cd41f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilder.java
@@ -17,9 +17,12 @@
  */
 package org.apache.ambari.server.state.stack.upgrade;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.ambari.server.serveraction.upgrades.AutoSkipFailedSummaryAction;
 import org.apache.ambari.server.stack.HostsType;
 import org.apache.ambari.server.state.UpgradeContext;
 import org.apache.ambari.server.state.stack.UpgradePack.ProcessingComponent;
@@ -30,23 +33,129 @@ import org.apache.ambari.server.state.stack.UpgradePack.ProcessingComponent;
 public abstract class StageWrapperBuilder {
 
   /**
+   * The message for the task which checks for skipped failures.
+   */
+  private static final String AUTO_SKIPPED_TASK_SUMMARY = "Pauses the upgrade if there were failed steps that were automatically skipped.";
+
+  /**
+   * The upgrade/downgrade grouping that the builder is for.
+   */
+  protected final Grouping m_grouping;
+
+  /**
+   * Constructor.
+   *
+   * @param grouping
+   *          the upgrade/downgrade grouping (not {@code null}).
+   */
+  protected StageWrapperBuilder(Grouping grouping) {
+    m_grouping = grouping;
+  }
+
+  /**
    * Adds a processing component that will be built into stage wrappers.
    *
-   * @param ctx         the upgrade context
-   * @param hostsType   the hosts, along with their type
-   * @param service     the service name
-   * @param clientOnly  whether the service is client only, no service checks
-   * @param pc          the ProcessingComponent derived from the upgrade pack
+   * @param upgradeContext
+   *          the upgrade context
+   * @param hostsType
+   *          the hosts, along with their type
+   * @param service
+   *          the service name
+   * @param clientOnly
+   *          whether the service is client only, no service checks
+   * @param pc
+   *          the ProcessingComponent derived from the upgrade pack
    */
-  public abstract void add(UpgradeContext ctx, HostsType hostsType, String service,
+  public abstract void add(UpgradeContext upgradeContext, HostsType hostsType, String service,
       boolean clientOnly, ProcessingComponent pc);
 
   /**
-   * Builds the stage wrappers.
-   * @param ctx the upgrade context
+   * Builds the stage wrappers, including any pre- and post-procesing that needs
+   * to be performed.
+   *
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
    * @return a list of stages, never {@code null}
    */
-  public abstract List<StageWrapper> build(UpgradeContext ctx);
+  public final List<StageWrapper> build(UpgradeContext upgradeContext) {
+    List<StageWrapper> stageWrappers = beforeBuild(upgradeContext);
+    stageWrappers = build(upgradeContext, stageWrappers);
+    stageWrappers = afterBuild(upgradeContext, stageWrappers);
+    return stageWrappers;
+  }
+
+  /**
+   * Performs any pre-processing that needs to be performed on the list of stage
+   * wrappers.
+   *
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
+   * @return the initial list of stage wrappers, or an empty list (never
+   *         {@code null}).
+   */
+  protected List<StageWrapper> beforeBuild(UpgradeContext upgradeContext) {
+    List<StageWrapper> stageWrappers = new ArrayList<>(100);
+    return stageWrappers;
+  }
+
+  /**
+   * Builds the stage wrappers.
+   *
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
+   * @param stageWrappers
+   *          the list of stage wrappers created by
+   *          {@link #beforeBuild(UpgradeContext)}.
+   * @return the stage wrapper list, (never {@code null})
+   */
+  public abstract List<StageWrapper> build(UpgradeContext upgradeContext,
+      List<StageWrapper> stageWrappers);
+
+  /**
+   * Performs any post-processing that needs to be performed on the list of
+   * stage wrappers.
+   *
+   * @param upgradeContext
+   *          the upgrade context (not {@code null}).
+   * @param stageWrappers
+   *          the list of stage wrappers created by
+   *          {@link #build(UpgradeContext, List)}.
+   * @return the post-processed list of stage wrappers (never {@code null})
+   */
+  protected List<StageWrapper> afterBuild(UpgradeContext upgradeContext,
+      List<StageWrapper> stageWrappers) {
+
+    if (stageWrappers.isEmpty()) {
+      return stageWrappers;
+    }
+
+    // we only want to insert the auto skip summary if the group is skippable
+    // and the upgrade context says to auto skip failures
+    final boolean autoSkipFailures;
+    if (m_grouping instanceof ServiceCheckGrouping) {
+      autoSkipFailures = upgradeContext.isServiceCheckFailureAutoSkipped();
+    } else {
+      autoSkipFailures = upgradeContext.isComponentFailureAutoSkipped();
+    }
+
+    if (m_grouping.skippable && autoSkipFailures) {
+      ServerActionTask skippedFailedCheck = new ServerActionTask();
+      skippedFailedCheck.implClass = AutoSkipFailedSummaryAction.class.getName();
+      skippedFailedCheck.summary = AUTO_SKIPPED_TASK_SUMMARY;
+
+      TaskWrapper skippedFailedTaskWrapper = new TaskWrapper(null, null,
+          Collections.<String> emptySet(), skippedFailedCheck);
+
+      StageWrapper skippedFailedStageWrapper = new StageWrapper(
+          StageWrapper.Type.SERVER_SIDE_ACTION, "Verifying Skipped Failures",
+          skippedFailedTaskWrapper);
+
+      stageWrappers.add(skippedFailedStageWrapper);
+    }
+
+    return stageWrappers;
+  }
+
 
   /**
    * Consistently formats a string.

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAOTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAOTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAOTest.java
new file mode 100644
index 0000000..1fded28
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAOTest.java
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.Role;
+import org.apache.ambari.server.RoleCommand;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.OrmTestHelper;
+import org.apache.ambari.server.orm.entities.ClusterEntity;
+import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+import org.apache.ambari.server.orm.entities.RequestEntity;
+import org.apache.ambari.server.orm.entities.StageEntity;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+
+import junit.framework.Assert;
+
+/**
+ * Tests {@link HostRoleCommandDAO}.
+ */
+public class HostRoleCommandDAOTest {
+
+  private Injector m_injector;
+  private ClusterDAO m_clusterDAO;
+  private StageDAO m_stageDAO;
+  private HostRoleCommandDAO m_hostRoleCommandDAO;
+  private HostDAO m_hostDAO;
+  private RequestDAO m_requestDAO;
+
+  @Before
+  public void setup() throws Exception {
+    m_injector = Guice.createInjector(new InMemoryDefaultTestModule());
+    m_injector.getInstance(GuiceJpaInitializer.class);
+    m_injector.getInstance(AmbariMetaInfo.class);
+
+    m_clusterDAO = m_injector.getInstance(ClusterDAO.class);
+    m_stageDAO = m_injector.getInstance(StageDAO.class);
+    m_hostRoleCommandDAO = m_injector.getInstance(HostRoleCommandDAO.class);
+    m_hostDAO = m_injector.getInstance(HostDAO.class);
+    m_requestDAO = m_injector.getInstance(RequestDAO.class);
+  }
+
+  @After
+  public void teardown() throws AmbariException {
+    m_injector.getInstance(PersistService.class).stop();
+  }
+
+  /**
+   * Tests finding all tasks between a range of stages.
+   */
+  @Test
+  public void testFindTasksBetweenStages() {
+    OrmTestHelper helper = m_injector.getInstance(OrmTestHelper.class);
+    helper.createDefaultData();
+
+    Long requestId = Long.valueOf(100L);
+    ClusterEntity clusterEntity = m_clusterDAO.findByName("test_cluster1");
+
+    RequestEntity requestEntity = new RequestEntity();
+    requestEntity.setRequestId(requestId);
+    requestEntity.setClusterId(clusterEntity.getClusterId());
+    requestEntity.setStages(new ArrayList<StageEntity>());
+    m_requestDAO.create(requestEntity);
+
+    AtomicLong stageId = new AtomicLong(1);
+    HostEntity host = m_hostDAO.findByName("test_host1");
+    host.setHostRoleCommandEntities(new ArrayList<HostRoleCommandEntity>());
+
+    createStage(stageId.getAndIncrement(), 3, host, requestEntity, HostRoleStatus.COMPLETED, false);
+    createStage(stageId.getAndIncrement(), 2, host, requestEntity, HostRoleStatus.SKIPPED_FAILED, false);
+    createStage(stageId.getAndIncrement(), 1, host, requestEntity, HostRoleStatus.ABORTED, false);
+
+    List<HostRoleCommandEntity> tasks = m_hostRoleCommandDAO.findByStatusBetweenStages(requestId,
+        HostRoleStatus.SKIPPED_FAILED, 1, 3);
+
+    Assert.assertEquals(2, tasks.size());
+
+    tasks = m_hostRoleCommandDAO.findByStatusBetweenStages(requestId, HostRoleStatus.SKIPPED_FAILED, 1, 1);
+    Assert.assertEquals(0, tasks.size());
+  }
+
+  /**
+   * Creates a single stage with the specified number of commands.
+   *
+   * @param startStageId
+   * @param count
+   * @param hostEntity
+   * @param requestEntity
+   * @param status
+   * @param skipStage
+   * @return
+   */
+  private void createStage(long startStageId, int count, HostEntity hostEntity,
+      RequestEntity requestEntity, HostRoleStatus status, boolean skipStage) {
+    long stageId = startStageId;
+
+    ClusterEntity clusterEntity = m_clusterDAO.findByName("test_cluster1");
+
+    StageEntity stageEntity = new StageEntity();
+    stageEntity.setClusterId(clusterEntity.getClusterId());
+    stageEntity.setRequest(requestEntity);
+    stageEntity.setStageId(stageId);
+    stageEntity.setHostRoleCommands(new ArrayList<HostRoleCommandEntity>());
+    stageEntity.setSkippable(skipStage);
+    m_stageDAO.create(stageEntity);
+    requestEntity.getStages().add(stageEntity);
+
+    for (int i = 0; i < count; i++) {
+      HostRoleCommandEntity commandEntity = new HostRoleCommandEntity();
+      commandEntity.setRequestId(requestEntity.getRequestId());
+      commandEntity.setStageId(stageId);
+      commandEntity.setRoleCommand(RoleCommand.INSTALL);
+      commandEntity.setStatus(status);
+      commandEntity.setRole(Role.DATANODE);
+      commandEntity.setHostEntity(hostEntity);
+      commandEntity.setStage(stageEntity);
+      m_hostRoleCommandDAO.create(commandEntity);
+
+      hostEntity.getHostRoleCommandEntities().add(commandEntity);
+      hostEntity = m_hostDAO.merge(hostEntity);
+
+      stageEntity.getHostRoleCommands().add(commandEntity);
+      m_stageDAO.merge(stageEntity);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e96f5bd6/ambari-server/src/test/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilderTest.java
new file mode 100644
index 0000000..e2a3995
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/stack/upgrade/StageWrapperBuilderTest.java
@@ -0,0 +1,162 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack.upgrade;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ambari.server.serveraction.upgrades.AutoSkipFailedSummaryAction;
+import org.apache.ambari.server.stack.HostsType;
+import org.apache.ambari.server.state.UpgradeContext;
+import org.apache.ambari.server.state.stack.UpgradePack.ProcessingComponent;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests the {@link StageWrapperBuilder}.
+ */
+public class StageWrapperBuilderTest {
+
+
+  /**
+   * Tests that the various build methods of a builder are invoked in the
+   * correct order.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testBuildOrder() throws Exception {
+    UpgradeContext upgradeContext = new UpgradeContext(null, null, null, null, Direction.UPGRADE);
+    MockStageWrapperBuilder builder = new MockStageWrapperBuilder(null);
+    List<StageWrapper> stageWrappers = builder.build(upgradeContext);
+    List<Integer> invocationOrder = builder.getInvocationOrder();
+
+    Assert.assertEquals(Integer.valueOf(0), invocationOrder.get(0));
+    Assert.assertEquals(Integer.valueOf(1), invocationOrder.get(1));
+    Assert.assertEquals(Integer.valueOf(2), invocationOrder.get(2));
+
+    // nothing happened, so this should be empty
+    Assert.assertTrue(stageWrappers.isEmpty());
+  }
+
+  /**
+   * Tests that a new task was inserted into the upgrade which will check for
+   * skipped failures and display a summary.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAutoSkipCheckInserted() throws Exception {
+    UpgradeContext upgradeContext = new UpgradeContext(null, null, null, null, Direction.UPGRADE);
+    upgradeContext.setAutoSkipComponentFailures(true);
+    upgradeContext.setAutoSkipServiceCheckFailures(true);
+
+    Grouping grouping = new Grouping();
+    grouping.skippable = true;
+
+    MockStageWrapperBuilder builder = new MockStageWrapperBuilder(grouping);
+
+    List<StageWrapper> mockStageWrappers = new ArrayList<>();
+    StageWrapper mockStageWrapper = EasyMock.createNiceMock(StageWrapper.class);
+    mockStageWrappers.add(mockStageWrapper);
+
+    builder.setMockStageWrappers(mockStageWrappers);
+
+    List<StageWrapper> stageWrappers = builder.build(upgradeContext);
+    Assert.assertEquals(2, stageWrappers.size());
+
+    StageWrapper skipSummaryWrapper = stageWrappers.get(1);
+    Assert.assertEquals(StageWrapper.Type.SERVER_SIDE_ACTION, skipSummaryWrapper.getType());
+
+    ServerActionTask task = (ServerActionTask)(skipSummaryWrapper.getTasks().get(0).getTasks().get(0));
+    Assert.assertEquals(AutoSkipFailedSummaryAction.class.getName(), task.implClass);
+
+  }
+
+  /**
+   * A mock {@link StageWrapperBuilder}.
+   */
+  private final class MockStageWrapperBuilder extends StageWrapperBuilder {
+
+    private List<Integer> m_invocationOrder = new ArrayList<>();
+    private List<StageWrapper> m_stageWrappers = Collections.emptyList();
+
+    /**
+     * Constructor.
+     *
+     * @param grouping
+     */
+    protected MockStageWrapperBuilder(Grouping grouping) {
+      super(grouping);
+    }
+
+    private void setMockStageWrappers(List<StageWrapper> stageWrappers) {
+      m_stageWrappers = stageWrappers;
+    }
+
+    /**
+     * Gets the invocation order.
+     *
+     * @return
+     */
+    private List<Integer> getInvocationOrder() {
+      return m_invocationOrder;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void add(UpgradeContext upgradeContext, HostsType hostsType, String service,
+        boolean clientOnly, ProcessingComponent pc) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<StageWrapper> build(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+      m_invocationOrder.add(1);
+      return m_stageWrappers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected List<StageWrapper> beforeBuild(UpgradeContext upgradeContext) {
+      List<StageWrapper> stageWrappers = super.beforeBuild(upgradeContext);
+      m_invocationOrder.add(0);
+      return stageWrappers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected List<StageWrapper> afterBuild(UpgradeContext upgradeContext,
+        List<StageWrapper> stageWrappers) {
+      stageWrappers = super.afterBuild(upgradeContext, stageWrappers);
+      m_invocationOrder.add(2);
+      return stageWrappers;
+    }
+  }
+}


[2/2] ambari git commit: AMBARI-13078 - Upgrade Packs Should Define Skippable Failed Slave/Clients (jonathanhurley)

Posted by jo...@apache.org.
AMBARI-13078 - Upgrade Packs Should Define Skippable Failed Slave/Clients (jonathanhurley)


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

Branch: refs/heads/branch-2.1
Commit: e7b946a986ac7b25dac3dab1b7df8cfe89888b12
Parents: e96f5bd
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Fri Sep 11 14:39:52 2015 -0400
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Sat Sep 12 09:46:12 2015 -0400

----------------------------------------------------------------------
 .../internal/UpgradeResourceProvider.java       |  10 +-
 .../server/orm/entities/UpgradeGroupEntity.java |  13 ++
 .../ambari/server/state/stack/UpgradePack.java  |  45 ++++-
 .../stacks/HDP/2.2/upgrades/upgrade-2.2.xml     |   3 +-
 .../stacks/HDP/2.2/upgrades/upgrade-2.3.xml     |   3 +
 .../stacks/HDP/2.3/upgrades/upgrade-2.3.xml     |   2 +
 .../internal/UpgradeResourceProviderTest.java   | 171 ++++++++++++-------
 .../server/state/stack/UpgradePackTest.java     |  27 ++-
 .../upgrades/upgrade_test_skip_failures.xml     |  52 ++++++
 9 files changed, 246 insertions(+), 80 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/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 48e9e1a..653419b 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
@@ -660,13 +660,17 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
     }
 
     // optionally skip failures - this can be supplied on either the request or
-    // in the upgrade pack explicitely
-    boolean skipComponentFailures = false;
-    boolean skipServiceCheckFailures = false;
+    // in the upgrade pack explicitely, however the request will always override
+    // the upgrade pack if explicitely specified
+    boolean skipComponentFailures = pack.isComponentFailureAutoSkipped();
+    boolean skipServiceCheckFailures = pack.isServiceCheckFailureAutoSkipped();
+
+    // only override the upgrade pack if set on the request
     if (requestMap.containsKey(UPGRADE_SKIP_FAILURES)) {
       skipComponentFailures = Boolean.parseBoolean((String) requestMap.get(UPGRADE_SKIP_FAILURES));
     }
 
+    // only override the upgrade pack if set on the request
     if (requestMap.containsKey(UPGRADE_SKIP_SC_FAILURES)) {
       skipServiceCheckFailures = Boolean.parseBoolean(
           (String) requestMap.get(UPGRADE_SKIP_SC_FAILURES));

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeGroupEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeGroupEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeGroupEntity.java
index 7b57184..96f96d5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeGroupEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradeGroupEntity.java
@@ -129,5 +129,18 @@ public class UpgradeGroupEntity {
     upgradeItems = items;
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("UpgradeGroupEntity{");
+    buffer.append("upgradeGroupId=").append(upgradeGroupId);
+    buffer.append(", upgradeId=").append(upgradeId);
+    buffer.append(", groupName=").append(groupName);
+    buffer.append(", groupTitle=").append(groupTitle);
+    buffer.append("}");
+    return buffer.toString();
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
index 3e9aa5f..5b65732 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/UpgradePack.java
@@ -50,7 +50,6 @@ public class UpgradePack {
   @XmlElement(name="target-stack")
   private String targetStack;
 
-
   @XmlElementWrapper(name="order")
   @XmlElement(name="group")
   private List<Grouping> groups;
@@ -59,13 +58,30 @@ public class UpgradePack {
   @XmlElement(name="service")
   private List<ProcessingService> processing;
 
+  /**
+   * {@code true} to automatically skip slave/client component failures. The
+   * default is {@code false}.
+   */
+  @XmlElement(name = "skip-failures")
+  private boolean skipFailures = false;
+
+  /**
+   * {@code true} to automatically skip service check failures. The default is
+   * {@code false}.
+   */
+  @XmlElement(name = "skip-service-check-failures")
+  private boolean skipServiceCheckFailures = false;
+
   @XmlTransient
   private Map<String, List<String>> m_orders = null;
+
   @XmlTransient
   private Map<String, Map<String, ProcessingComponent>> m_process = null;
+
   @XmlTransient
   private boolean m_resolvedGroups = false;
 
+
   /**
    * @return the target version for the upgrade pack
    */
@@ -81,9 +97,30 @@ public class UpgradePack {
   }
 
   /**
-   * Gets the groups defined for the upgrade pack.  If a direction is defined
-   * for a group, it must match the supplied direction to be returned
-   * @param direction the direction to return the ordered groups
+   * Gets whether skippable components that failed are automatically skipped.
+   *
+   * @return the skipComponentFailures
+   */
+  public boolean isComponentFailureAutoSkipped() {
+    return skipFailures;
+  }
+
+  /**
+   * Gets whether skippable service checks that failed are automatically
+   * skipped.
+   *
+   * @return the skipServiceCheckFailures
+   */
+  public boolean isServiceCheckFailureAutoSkipped() {
+    return skipServiceCheckFailures;
+  }
+
+  /**
+   * Gets the groups defined for the upgrade pack. If a direction is defined for
+   * a group, it must match the supplied direction to be returned
+   * 
+   * @param direction
+   *          the direction to return the ordered groups
    * @return the list of groups
    */
   public List<Grouping> getGroups(Direction direction) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.2.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.2.xml b/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.2.xml
index 7d2b9aa..3837e63 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.2.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.2.xml
@@ -19,9 +19,10 @@
 
 <upgrade xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <target>2.2.*.*</target>
+  <skip-failures>false</skip-failures>
+  <skip-service-check-failures>false</skip-service-check-failures>
 
   <order>
-
     <group xsi:type="cluster" name="PRE_CLUSTER" title="Prepare Upgrade">
       <direction>UPGRADE</direction>
       <execute-stage service="HDFS" component="NAMENODE" title="Pre Upgrade HDFS">

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.3.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.3.xml b/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.3.xml
index c9b09b5..e7c55a6 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.3.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.2/upgrades/upgrade-2.3.xml
@@ -20,6 +20,9 @@
 <upgrade xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <target>2.3.*.*</target>
   <target-stack>HDP-2.3</target-stack>
+  <skip-failures>false</skip-failures>
+  <skip-service-check-failures>false</skip-service-check-failures>
+
   <order>
     <group xsi:type="cluster" name="PRE_CLUSTER" title="Prepare Upgrade">
       <direction>UPGRADE</direction>

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.3.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.3.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.3.xml
index 3b48322..acc65a6 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.3.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.3.xml
@@ -19,6 +19,8 @@
 
 <upgrade xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <target>2.3.*.*</target>
+  <skip-failures>false</skip-failures>
+  <skip-service-check-failures>false</skip-service-check-failures>
 
   <order>
     <group xsi:type="cluster" name="PRE_CLUSTER" title="Prepare Upgrade">

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/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 cf0cf4c..9873104 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
@@ -36,7 +36,6 @@ import java.util.Set;
 import org.apache.ambari.server.actionmanager.ActionManager;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
-import org.apache.ambari.server.actionmanager.RequestStatus;
 import org.apache.ambari.server.actionmanager.Stage;
 import org.apache.ambari.server.api.resources.UpgradeResourceDefinition;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
@@ -44,6 +43,7 @@ import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.AmbariServer;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
@@ -64,6 +64,7 @@ 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.serveraction.upgrades.AutoSkipFailedSummaryAction;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Config;
@@ -233,90 +234,43 @@ public class UpgradeResourceProviderTest {
     injector = null;
   }
 
-  private org.apache.ambari.server.controller.spi.RequestStatus testCreateResources() throws Exception {
-
+  @Test
+  public void testCreateResourcesWithAutoSkipFailures() throws Exception {
     Cluster cluster = clusters.getCluster("c1");
 
-    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
-    assertEquals(0, upgrades.size());
-
     Map<String, Object> requestProps = new HashMap<String, Object>();
     requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
     requestProps.put(UpgradeResourceProvider.UPGRADE_VERSION, "2.1.1.1");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES, Boolean.TRUE.toString());
+    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES, Boolean.TRUE.toString());
 
     ResourceProvider upgradeResourceProvider = createProvider(amc);
-
     Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
-    org.apache.ambari.server.controller.spi.RequestStatus status = upgradeResourceProvider.createResources(request);
+    upgradeResourceProvider.createResources(request);
 
-    upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
     assertEquals(1, upgrades.size());
 
     UpgradeEntity entity = upgrades.get(0);
     assertEquals(cluster.getClusterId(), entity.getClusterId().longValue());
 
-    StageDAO stageDAO = injector.getInstance(StageDAO.class);
-    List<StageEntity> stageEntities = stageDAO.findByRequestId(entity.getRequestId());
-    Gson gson = new Gson();
-    for (StageEntity se : stageEntities) {
-      Map<String, String> map = gson.<Map<String, String>>fromJson(se.getCommandParamsStage(), Map.class);
-      assertTrue(map.containsKey("upgrade_direction"));
-      assertEquals("upgrade", map.get("upgrade_direction"));
-    }
-
     List<UpgradeGroupEntity> upgradeGroups = entity.getUpgradeGroups();
     assertEquals(3, upgradeGroups.size());
 
-    UpgradeGroupEntity group = upgradeGroups.get(1);
-    assertEquals(4, group.getItems().size());
-
-    assertTrue(group.getItems().get(0).getText().contains(
-        "placeholder of placeholder-rendered-properly"));
-
-    assertTrue(group.getItems().get(1).getText().contains("Restarting"));
-    assertTrue(group.getItems().get(2).getText().contains("Skipping"));
-    assertTrue(group.getItems().get(3).getText().contains("Service Check"));
-
-    ActionManager am = injector.getInstance(ActionManager.class);
-    List<Long> requests = am.getRequestsByStatus(RequestStatus.IN_PROGRESS, 100, true);
-
-    assertEquals(1, requests.size());
-    assertEquals(requests.get(0), entity.getRequestId());
+    UpgradeGroupEntity zookeeperGroup = upgradeGroups.get(1);
+    assertEquals("ZOOKEEPER", zookeeperGroup.getName());
 
-    List<Stage> stages = am.getRequestStatus(requests.get(0).longValue());
-
-    assertEquals(8, stages.size());
+    List<UpgradeItemEntity> upgradeItems = zookeeperGroup.getItems();
+    assertEquals(5, upgradeItems.size());
 
-    List<HostRoleCommand> tasks = am.getRequestTasks(requests.get(0).longValue());
-    // same number of tasks as stages here
-    assertEquals(8, tasks.size());
-
-    Set<Long> slaveStageIds = new HashSet<Long>();
-
-    UpgradeGroupEntity coreSlavesGroup = upgradeGroups.get(1);
-
-    for (UpgradeItemEntity itemEntity : coreSlavesGroup.getItems()) {
-      slaveStageIds.add(itemEntity.getStageId());
-    }
-
-    for (Stage stage : stages) {
-
-      // For this test the core slaves group stages should be skippable and NOT allow retry.
-      assertEquals(slaveStageIds.contains(stage.getStageId()), stage.isSkippable());
-
-      for (Map<String, HostRoleCommand> taskMap : stage.getHostRoleCommands().values()) {
-
-        for (HostRoleCommand task : taskMap.values()) {
-          assertEquals(!slaveStageIds.contains(stage.getStageId()), task.isRetryAllowed());
-        }
-      }
-    }
-    return status;
+    // the last upgrade item is the skipped failure check
+    UpgradeItemEntity skippedFailureCheck = upgradeItems.get(upgradeItems.size() - 1);
+    skippedFailureCheck.getTasks().contains(AutoSkipFailedSummaryAction.class.getName());
   }
 
   @Test
   public void testGetResources() throws Exception {
-    org.apache.ambari.server.controller.spi.RequestStatus status = testCreateResources();
+    RequestStatus status = testCreateResources();
 
     Set<Resource> createdResources = status.getAssociatedResources();
     assertEquals(1, createdResources.size());
@@ -482,7 +436,8 @@ public class UpgradeResourceProviderTest {
     ResourceProvider upgradeResourceProvider = createProvider(amc);
 
     Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
-    org.apache.ambari.server.controller.spi.RequestStatus status = upgradeResourceProvider.createResources(request);
+    RequestStatus status = upgradeResourceProvider.createResources(
+        request);
 
     List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
     assertEquals(1, upgrades.size());
@@ -531,7 +486,7 @@ public class UpgradeResourceProviderTest {
 
   @Test
   public void testAbort() throws Exception {
-    org.apache.ambari.server.controller.spi.RequestStatus status = testCreateResources();
+    RequestStatus status = testCreateResources();
 
     Set<Resource> createdResources = status.getAssociatedResources();
     assertEquals(1, createdResources.size());
@@ -553,7 +508,7 @@ public class UpgradeResourceProviderTest {
 
   @Test
   public void testRetry() throws Exception {
-    org.apache.ambari.server.controller.spi.RequestStatus status = testCreateResources();
+    RequestStatus status = testCreateResources();
 
     Set<Resource> createdResources = status.getAssociatedResources();
     assertEquals(1, createdResources.size());
@@ -653,7 +608,7 @@ public class UpgradeResourceProviderTest {
 
   @Test
   public void testPercents() throws Exception {
-    org.apache.ambari.server.controller.spi.RequestStatus status = testCreateResources();
+    RequestStatus status = testCreateResources();
 
     Set<Resource> createdResources = status.getAssociatedResources();
     assertEquals(1, createdResources.size());
@@ -888,6 +843,90 @@ public class UpgradeResourceProviderTest {
     return new UpgradeResourceProvider(amc);
   }
 
+  private RequestStatus testCreateResources() throws Exception {
+
+    Cluster cluster = clusters.getCluster("c1");
+
+    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    assertEquals(0, upgrades.size());
+
+    Map<String, Object> requestProps = new HashMap<String, Object>();
+    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_VERSION, "2.1.1.1");
+
+    ResourceProvider upgradeResourceProvider = createProvider(amc);
+
+    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    RequestStatus status = upgradeResourceProvider.createResources(request);
+
+    upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
+    assertEquals(1, upgrades.size());
+
+    UpgradeEntity entity = upgrades.get(0);
+    assertEquals(cluster.getClusterId(), entity.getClusterId().longValue());
+
+    StageDAO stageDAO = injector.getInstance(StageDAO.class);
+    List<StageEntity> stageEntities = stageDAO.findByRequestId(entity.getRequestId());
+    Gson gson = new Gson();
+    for (StageEntity se : stageEntities) {
+      Map<String, String> map = gson.<Map<String, String>> fromJson(se.getCommandParamsStage(),
+          Map.class);
+      assertTrue(map.containsKey("upgrade_direction"));
+      assertEquals("upgrade", map.get("upgrade_direction"));
+    }
+
+    List<UpgradeGroupEntity> upgradeGroups = entity.getUpgradeGroups();
+    assertEquals(3, upgradeGroups.size());
+
+    UpgradeGroupEntity group = upgradeGroups.get(1);
+    assertEquals(4, group.getItems().size());
+
+    assertTrue(
+        group.getItems().get(0).getText().contains("placeholder of placeholder-rendered-properly"));
+
+    assertTrue(group.getItems().get(1).getText().contains("Restarting"));
+    assertTrue(group.getItems().get(2).getText().contains("Skipping"));
+    assertTrue(group.getItems().get(3).getText().contains("Service Check"));
+
+    ActionManager am = injector.getInstance(ActionManager.class);
+    List<Long> requests = am.getRequestsByStatus(
+        org.apache.ambari.server.actionmanager.RequestStatus.IN_PROGRESS, 100, true);
+
+    assertEquals(1, requests.size());
+    assertEquals(requests.get(0), entity.getRequestId());
+
+    List<Stage> stages = am.getRequestStatus(requests.get(0).longValue());
+
+    assertEquals(8, stages.size());
+
+    List<HostRoleCommand> tasks = am.getRequestTasks(requests.get(0).longValue());
+    // same number of tasks as stages here
+    assertEquals(8, tasks.size());
+
+    Set<Long> slaveStageIds = new HashSet<Long>();
+
+    UpgradeGroupEntity coreSlavesGroup = upgradeGroups.get(1);
+
+    for (UpgradeItemEntity itemEntity : coreSlavesGroup.getItems()) {
+      slaveStageIds.add(itemEntity.getStageId());
+    }
+
+    for (Stage stage : stages) {
+
+      // For this test the core slaves group stages should be skippable and NOT
+      // allow retry.
+      assertEquals(slaveStageIds.contains(stage.getStageId()), stage.isSkippable());
+
+      for (Map<String, HostRoleCommand> taskMap : stage.getHostRoleCommands().values()) {
+
+        for (HostRoleCommand task : taskMap.values()) {
+          assertEquals(!slaveStageIds.contains(stage.getStageId()), task.isRetryAllowed());
+        }
+      }
+    }
+    return status;
+  }
+
   /**
    *
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/test/java/org/apache/ambari/server/state/stack/UpgradePackTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/stack/UpgradePackTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/stack/UpgradePackTest.java
index e073b43..9ae78c4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/stack/UpgradePackTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/stack/UpgradePackTest.java
@@ -44,6 +44,7 @@ import org.apache.ambari.server.state.stack.upgrade.ServiceCheckGrouping;
 import org.apache.ambari.server.state.stack.upgrade.Task;
 import org.apache.ambari.server.state.stack.upgrade.TransferOperation;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -156,12 +157,12 @@ public class UpgradePackTest {
     assertEquals(4, ct.getTransfers().size());
 
     /*
-            <transfer operation="COPY" from-key="copy-key" to-key="copy-key-to" />
-            <transfer operation="COPY" from-type="my-site" from-key="my-copy-key" to-key="my-copy-key-to" />
-            <transfer operation="MOVE" from-key="move-key" to-key="move-key-to" />
-            <transfer operation="DELETE" delete-key="delete-key">
-              <keep-key>important-key</keep-key>
-            </transfer>
+    <transfer operation="COPY" from-key="copy-key" to-key="copy-key-to" />
+    <transfer operation="COPY" from-type="my-site" from-key="my-copy-key" to-key="my-copy-key-to" />
+    <transfer operation="MOVE" from-key="move-key" to-key="move-key-to" />
+    <transfer operation="DELETE" delete-key="delete-key">
+      <keep-key>important-key</keep-key>
+    </transfer>
     */
     Transfer t1 = ct.getTransfers().get(0);
     assertEquals(TransferOperation.COPY, t1.operation);
@@ -283,7 +284,21 @@ public class UpgradePackTest {
     group = groups.get(2);
     assertEquals(ClusterGrouping.class, group.getClass());
     assertEquals("Finalize Upgrade", group.title);
+  }
+
+  @Test
+  public void testSkippableFailures() throws Exception {
+    Map<String, UpgradePack> upgrades = ambariMetaInfo.getUpgradePacks("HDP", "2.1.1");
+    Set<String> keys = upgrades.keySet();
+    for (String key : keys) {
+      Assert.assertFalse(upgrades.get(key).isComponentFailureAutoSkipped());
+      Assert.assertFalse(upgrades.get(key).isServiceCheckFailureAutoSkipped());
+    }
 
+    upgrades = ambariMetaInfo.getUpgradePacks("HDP", "2.2.0");
+    UpgradePack upgradePack = upgrades.get("upgrade_test_skip_failures");
+    Assert.assertTrue(upgradePack.isComponentFailureAutoSkipped());
+    Assert.assertTrue(upgradePack.isServiceCheckFailureAutoSkipped());
   }
 
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e7b946a9/ambari-server/src/test/resources/stacks/HDP/2.2.0/upgrades/upgrade_test_skip_failures.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/stacks/HDP/2.2.0/upgrades/upgrade_test_skip_failures.xml b/ambari-server/src/test/resources/stacks/HDP/2.2.0/upgrades/upgrade_test_skip_failures.xml
new file mode 100644
index 0000000..467e63a
--- /dev/null
+++ b/ambari-server/src/test/resources/stacks/HDP/2.2.0/upgrades/upgrade_test_skip_failures.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<upgrade xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <target>2.2.*</target>
+  <skip-failures>true</skip-failures>
+  <skip-service-check-failures>true</skip-service-check-failures>
+  
+  <order>
+    <group name="ZOOKEEPER" title="Zookeeper">
+      <skippable>true</skippable>
+      <allow-retry>false</allow-retry>
+      <service name="ZOOKEEPER">
+        <component>ZOOKEEPER_SERVER</component>
+        <component>ZOOKEEPER_CLIENT</component>
+      </service>
+    </group>
+  </order>  
+
+  <processing>
+    <service name="ZOOKEEPER">
+      <component name="ZOOKEEPER_SERVER">
+        <pre-upgrade>
+          <task xsi:type="manual">
+            <summary>SUMMARY OF PREPARE</summary>
+            <message>This is a manual task with a placeholder of {{foo/bar}}</message>
+          </task>
+        </pre-upgrade>
+        <upgrade>
+          <task xsi:type="restart" />
+        </upgrade>
+        <post-upgrade>
+          <task xsi:type="configure" />
+        </post-upgrade>
+      </component>
+    </service>
+  </processing>
+</upgrade>