You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2015/11/04 22:27:09 UTC

ambari git commit: AMBARI-13722. Confusing message is shown when there are service check failures that have been skipped during RU (Dmitro Lisnichenko via ncole)

Repository: ambari
Updated Branches:
  refs/heads/branch-2.1 b90a7e33c -> 36681a57e


AMBARI-13722. Confusing message is shown when there are service check failures that have been skipped during RU (Dmitro Lisnichenko via ncole)


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

Branch: refs/heads/branch-2.1
Commit: 36681a57e8cbda3ce9530e49e90426df6a91e2ef
Parents: b90a7e3
Author: Nate Cole <nc...@hortonworks.com>
Authored: Wed Nov 4 16:26:54 2015 -0500
Committer: Nate Cole <nc...@hortonworks.com>
Committed: Wed Nov 4 16:26:54 2015 -0500

----------------------------------------------------------------------
 .../ambari/server/metadata/ActionMetadata.java  |  14 +
 .../upgrades/AutoSkipFailedSummaryAction.java   | 114 +++--
 .../AutoSkipFailedSummaryActionTest.java        | 455 +++++++++++++++++++
 3 files changed, 552 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/36681a57/ambari-server/src/main/java/org/apache/ambari/server/metadata/ActionMetadata.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/metadata/ActionMetadata.java b/ambari-server/src/main/java/org/apache/ambari/server/metadata/ActionMetadata.java
index f5642a0..db10125 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/metadata/ActionMetadata.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/metadata/ActionMetadata.java
@@ -99,6 +99,20 @@ public class ActionMetadata {
     return serviceCheckActions.get(serviceName.toLowerCase());
   }
 
+  /**
+   * Get service name by service check action name
+   * @param serviceCheckAction service check action name like ZOOKEEPER_QUORUM_SERVICE_CHECK
+   * @return service name (capitalized) or null if not found
+   */
+  public String getServiceNameByServiceCheckAction(String serviceCheckAction) {
+    for (Map.Entry<String, String> entry : serviceCheckActions.entrySet()) {
+      if (entry.getValue().equals(serviceCheckAction)) {
+        return entry.getKey().toUpperCase();
+      }
+    }
+    return null;
+  }
+
   public void addServiceCheckAction(String serviceName) {
     String actionName = serviceName + SERVICE_CHECK_POSTFIX;
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/36681a57/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
index 9a84e38..80c1611 100644
--- 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
@@ -18,25 +18,28 @@
 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.*;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.Role;
+import org.apache.ambari.server.RoleCommand;
 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.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.metadata.ActionMetadata;
 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.Cluster;
+import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ServiceComponentHostEvent;
-import org.apache.commons.lang.StringUtils;
+import org.apache.ambari.server.state.StackId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,6 +70,11 @@ public class AutoSkipFailedSummaryAction extends AbstractServerAction {
    */
   private static final String MIDDLE_ELLIPSIZE_MARKER = "\n\u2026\n";
 
+  private static final String SKIPPED_SERVICE_CHECK = "service_check";
+  private static final String SKIPPED_HOST_COMPONENT = "host_component";
+  private static final String SKIPPED = "skipped";
+  private static final String FAILURES = "failures";
+
   /**
    * Used to lookup the {@link UpgradeGroupEntity}.
    */
@@ -87,9 +95,21 @@ public class AutoSkipFailedSummaryAction extends AbstractServerAction {
   private Gson m_gson;
 
   /**
+   * Used to look up service check name -> service name bindings
+   */
+  @Inject
+  private ActionMetadata actionMetadata;
+
+  @Inject
+  private AmbariMetaInfo ambariMetaInfo;
+
+  @Inject
+  private Clusters clusters;
+
+  /**
    * A mapping of host -> Map<key,info> for each failure.
    */
-  private Map<String, Map<String, Object>> m_structuredFailures = new HashMap<>();
+  private Map<String, Object> m_structuredFailures = new HashMap<>();
 
   /**
    * {@inheritDoc}
@@ -102,6 +122,10 @@ public class AutoSkipFailedSummaryAction extends AbstractServerAction {
     long requestId = hostRoleCommand.getRequestId();
     long stageId = hostRoleCommand.getStageId();
 
+    String clusterName = hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getClusterName();
+    Cluster cluster = clusters.getCluster(clusterName);
+    StackId stackId = cluster.getDesiredStackVersion();
+
     // use the host role command to get to the parent upgrade group
     UpgradeItemEntity upgradeItem = m_upgradeDAO.findUpgradeItemByRequestAndStage(requestId,stageId);
     UpgradeGroupEntity upgradeGroup = upgradeItem.getGroupEntity();
@@ -131,37 +155,63 @@ public class AutoSkipFailedSummaryAction extends AbstractServerAction {
           "There were no skipped failures", null);
     }
 
-    StringBuilder buffer = new StringBuilder("The following steps failed and were automatically skipped:\n");
+    StringBuilder buffer = new StringBuilder("The following steps failed but were automatically skipped:\n");
+    Set<String> skippedCategories = new HashSet<>();
+    Map<String, Object> skippedFailures = new HashMap<>();
 
-    for (HostRoleCommandEntity skippedTask : skippedTasks) {
-      try{
-        ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper(
-            skippedTask.getEvent());
+    Set<String> skippedServiceChecks = new HashSet<>();
+    Map<String, Object> hostComponents= new HashMap<>();
 
-        ServiceComponentHostEvent event = eventWrapper.getEvent();
+    // Internal representation for failed host components
+    // To avoid duplicates
+    // Format: <hostname, Set<Role>>
+    Map<String, Set<Role>> publishedHostComponents= new HashMap<>();
 
-        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);
+    for (HostRoleCommandEntity skippedTask : skippedTasks) {
+      try {
+        String skippedCategory;
+        if (skippedTask.getRoleCommand().equals(RoleCommand.SERVICE_CHECK)) {
+          skippedCategory = SKIPPED_SERVICE_CHECK;
+
+          String serviceCheckActionName = skippedTask.getRole().toString();
+          String service = actionMetadata.getServiceNameByServiceCheckAction(serviceCheckActionName);
+          skippedServiceChecks.add(service);
+
+          skippedFailures.put(SKIPPED_SERVICE_CHECK, skippedServiceChecks);
+          m_structuredFailures.put(FAILURES, skippedFailures);
+        } else {
+          skippedCategory = SKIPPED_HOST_COMPONENT;
+
+          String hostName = skippedTask.getHostName();
+          if (null != hostName) {
+            List<Object> failures = (List<Object>) hostComponents.get(hostName);
+            if (null == failures) {
+              failures = new ArrayList<>();
+              hostComponents.put(hostName, failures);
+
+              publishedHostComponents.put(hostName, new HashSet<Role>());
+            }
+            Set<Role> publishedHostComponentsOnHost = publishedHostComponents.get(hostName);
+            Role role = skippedTask.getRole();
+            if (! publishedHostComponentsOnHost.contains(role)) {
+              HashMap<String, String> details = new HashMap<>();
+              String service = ambariMetaInfo.getComponentToService(
+                stackId.getStackName(), stackId.getStackVersion(), role.toString());
+
+              details.put("service", service);
+              details.put("component", role.toString());
+              failures.add(details);
+            }
           }
+          skippedFailures.put(SKIPPED_HOST_COMPONENT, hostComponents);
+          m_structuredFailures.put(FAILURES, skippedFailures);
+        }
+        skippedCategories.add(skippedCategory);
 
-          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);
+        ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper(
+          skippedTask.getEvent());
 
-          failures.put("stdout", stdOut);
-          failures.put("stderr", stderr);
-        }
+        ServiceComponentHostEvent event = eventWrapper.getEvent();
 
         buffer.append(event.getServiceComponentName());
         if (null != event.getHostName()) {
@@ -179,6 +229,8 @@ public class AutoSkipFailedSummaryAction extends AbstractServerAction {
       }
     }
 
+    m_structuredFailures.put(SKIPPED, skippedCategories);
+
     String structuredOutput = m_gson.toJson(m_structuredFailures);
     String standardOutput = MessageFormat.format(FAILURE_STD_OUT_TEMPLATE, skippedTasks.size());
     String standardError = buffer.toString();

http://git-wip-us.apache.org/repos/asf/ambari/blob/36681a57/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryActionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryActionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryActionTest.java
new file mode 100644
index 0000000..5e35b301
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/upgrades/AutoSkipFailedSummaryActionTest.java
@@ -0,0 +1,455 @@
+/**
+ * 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 com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+import com.google.inject.persist.UnitOfWork;
+import com.google.inject.util.Modules;
+import org.apache.ambari.server.*;
+import org.apache.ambari.server.actionmanager.*;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.ambari.server.agent.ExecutionCommand;
+import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.dao.*;
+import org.apache.ambari.server.orm.entities.*;
+import org.apache.ambari.server.serveraction.AbstractServerAction;
+import org.apache.ambari.server.state.*;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostOpInProgressEvent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.junit.Assert.*;
+
+public class AutoSkipFailedSummaryActionTest {
+
+  private Injector m_injector;
+
+  private static final StackId HDP_STACK = new StackId("HDP-2.2.0");
+
+  @Inject
+  private ExecutionCommandDAO executionCommandDAO;
+
+  @Inject
+  private HostDAO hostDAO;
+
+  // Mocked out values
+  private UpgradeDAO upgradeDAOMock;
+  private HostRoleCommandDAO hostRoleCommandDAOMock;
+  private Clusters clustersMock;
+  private Cluster clusterMock;
+
+
+  @Before
+  public void setup() throws Exception {
+    // Create instances of mocks
+    upgradeDAOMock = createNiceMock(UpgradeDAO.class);
+    hostRoleCommandDAOMock = createNiceMock(HostRoleCommandDAO.class);
+    clustersMock = createNiceMock(Clusters.class);
+    clusterMock = createNiceMock(Cluster.class);
+
+    expect(clustersMock.getCluster(anyString())).andReturn(clusterMock).anyTimes();
+    replay(clustersMock);
+
+    expect(clusterMock.getDesiredStackVersion()).andReturn(HDP_STACK).anyTimes();
+    replay(clusterMock);
+
+    // Initialize injector
+    InMemoryDefaultTestModule module = new InMemoryDefaultTestModule();
+    m_injector = Guice.createInjector(Modules.override(module).with(new MockModule()));
+    m_injector.getInstance(GuiceJpaInitializer.class);
+    m_injector.injectMembers(this);
+    m_injector.getInstance(UnitOfWork.class).begin();
+
+    // Normally initialized in runtime when loading stack
+    m_injector.getInstance(ActionMetadata.class).addServiceCheckAction("ZOOKEEPER");
+  }
+
+  @After
+  public void teardown() throws Exception {
+    m_injector.getInstance(UnitOfWork.class).end();
+    m_injector.getInstance(PersistService.class).stop();
+  }
+
+
+  /**
+   * Tests successful workflow
+   */
+  @Test
+  public void testAutoSkipFailedSummaryAction__green() throws Exception {
+    AutoSkipFailedSummaryAction action = new AutoSkipFailedSummaryAction();
+    m_injector.injectMembers(action);
+
+    ServiceComponentHostEvent event = createNiceMock(ServiceComponentHostEvent.class);
+
+    // Set mock for parent's getHostRoleCommand()
+    HostRoleCommand hostRoleCommand = new HostRoleCommand("host1", Role.AMBARI_SERVER_ACTION,
+      event, RoleCommand.EXECUTE, hostDAO, executionCommandDAO);
+    hostRoleCommand.setRequestId(1l);
+    hostRoleCommand.setStageId(1l);
+
+    ExecutionCommand executionCommand = new ExecutionCommand();
+    executionCommand.setClusterName("cc");
+    executionCommand.setRoleCommand(RoleCommand.EXECUTE);
+    executionCommand.setRole("AMBARI_SERVER_ACTION");
+    executionCommand.setServiceName("");
+    executionCommand.setTaskId(1l);
+    ExecutionCommandWrapper wrapper = new ExecutionCommandWrapper(executionCommand);
+    hostRoleCommand.setExecutionCommandWrapper(wrapper);
+
+    Field f = AbstractServerAction.class.getDeclaredField("hostRoleCommand");
+    f.setAccessible(true);
+    f.set(action, hostRoleCommand);
+
+    final UpgradeItemEntity upgradeItem1 = new UpgradeItemEntity();
+    upgradeItem1.setStageId(5l);
+    final UpgradeItemEntity upgradeItem2 = new UpgradeItemEntity();
+    upgradeItem2.setStageId(6l);
+
+    UpgradeGroupEntity upgradeGroupEntity = new UpgradeGroupEntity();
+    upgradeGroupEntity.setId(11l);
+    // List of upgrade items in group
+    List<UpgradeItemEntity> groupUpgradeItems = new ArrayList<UpgradeItemEntity>(){{
+      add(upgradeItem1);
+      add(upgradeItem2);
+    }};
+    upgradeGroupEntity.setItems(groupUpgradeItems);
+
+    UpgradeItemEntity upgradeItemEntity = new UpgradeItemEntity();
+    upgradeItemEntity.setGroupEntity(upgradeGroupEntity);
+
+    expect(upgradeDAOMock.findUpgradeItemByRequestAndStage(anyLong(), anyLong())).andReturn(upgradeItemEntity).anyTimes();
+    expect(upgradeDAOMock.findUpgradeGroup(anyLong())).andReturn(upgradeGroupEntity).anyTimes();
+    replay(upgradeDAOMock);
+
+    List<HostRoleCommandEntity> skippedTasks = new ArrayList<HostRoleCommandEntity>() {{
+      // It's empty - no skipped tasks
+    }};
+    expect(hostRoleCommandDAOMock.findByStatusBetweenStages(anyLong(),
+      anyObject(HostRoleStatus.class), anyLong(), anyLong())).andReturn(skippedTasks).anyTimes();
+    replay(hostRoleCommandDAOMock);
+
+    ConcurrentMap<String, Object> requestSharedDataContext = new ConcurrentHashMap<String, Object>();
+    CommandReport result = action.execute(requestSharedDataContext);
+
+    assertNotNull(result.getStructuredOut());
+    assertEquals(0, result.getExitCode());
+    assertEquals(HostRoleStatus.COMPLETED.toString(), result.getStatus());
+    assertEquals("There were no skipped failures", result.getStdOut());
+    assertEquals("{}", result.getStructuredOut());
+    assertEquals("", result.getStdErr());
+  }
+
+
+  /**
+   * Tests workflow with few skipped tasks
+   */
+  @Test
+  public void testAutoSkipFailedSummaryAction__red() throws Exception {
+    AutoSkipFailedSummaryAction action = new AutoSkipFailedSummaryAction();
+    m_injector.injectMembers(action);
+
+    ServiceComponentHostEvent event = createNiceMock(ServiceComponentHostEvent.class);
+
+    // Set mock for parent's getHostRoleCommand()
+    final HostRoleCommand hostRoleCommand = new HostRoleCommand("host1", Role.AMBARI_SERVER_ACTION,
+      event, RoleCommand.EXECUTE, hostDAO, executionCommandDAO);
+    hostRoleCommand.setRequestId(1l);
+    hostRoleCommand.setStageId(1l);
+
+    ExecutionCommand executionCommand = new ExecutionCommand();
+    executionCommand.setClusterName("cc");
+    executionCommand.setRoleCommand(RoleCommand.EXECUTE);
+    executionCommand.setRole("AMBARI_SERVER_ACTION");
+    executionCommand.setServiceName("");
+    executionCommand.setTaskId(1l);
+    ExecutionCommandWrapper wrapper = new ExecutionCommandWrapper(executionCommand);
+    hostRoleCommand.setExecutionCommandWrapper(wrapper);
+
+    Field f = AbstractServerAction.class.getDeclaredField("hostRoleCommand");
+    f.setAccessible(true);
+    f.set(action, hostRoleCommand);
+
+    final UpgradeItemEntity upgradeItem1 = new UpgradeItemEntity();
+    upgradeItem1.setStageId(5l);
+    final UpgradeItemEntity upgradeItem2 = new UpgradeItemEntity();
+    upgradeItem2.setStageId(6l);
+
+    UpgradeGroupEntity upgradeGroupEntity = new UpgradeGroupEntity();
+    upgradeGroupEntity.setId(11l);
+    // List of upgrade items in group
+    List<UpgradeItemEntity> groupUpgradeItems = new ArrayList<UpgradeItemEntity>(){{
+      add(upgradeItem1);
+      add(upgradeItem2);
+    }};
+    upgradeGroupEntity.setItems(groupUpgradeItems);
+
+    UpgradeItemEntity upgradeItemEntity = new UpgradeItemEntity();
+    upgradeItemEntity.setGroupEntity(upgradeGroupEntity);
+
+    expect(upgradeDAOMock.findUpgradeItemByRequestAndStage(anyLong(), anyLong())).andReturn(upgradeItemEntity).anyTimes();
+    expect(upgradeDAOMock.findUpgradeGroup(anyLong())).andReturn(upgradeGroupEntity).anyTimes();
+    replay(upgradeDAOMock);
+
+    List<HostRoleCommandEntity> skippedTasks = new ArrayList<HostRoleCommandEntity>() {{
+      add(createSkippedTask("DATANODE", "DATANODE", "host1.vm",
+        "RESTART HDFS/DATANODE", RoleCommand.CUSTOM_COMMAND,
+        "RESTART"
+        ));
+      add(createSkippedTask("DATANODE", "DATANODE", "host2.vm",
+        "RESTART HDFS/DATANODE", RoleCommand.CUSTOM_COMMAND,
+        "RESTART"));
+      add(createSkippedTask("ZOOKEEPER_QUORUM_SERVICE_CHECK", "ZOOKEEPER_CLIENT", "host2.vm",
+        "SERVICE_CHECK ZOOKEEPER", RoleCommand.SERVICE_CHECK, null));
+    }};
+    expect(hostRoleCommandDAOMock.findByStatusBetweenStages(anyLong(),
+      anyObject(HostRoleStatus.class), anyLong(), anyLong())).andReturn(skippedTasks).anyTimes();
+    replay(hostRoleCommandDAOMock);
+
+    ConcurrentMap<String, Object> requestSharedDataContext = new ConcurrentHashMap<String, Object>();
+    CommandReport result = action.execute(requestSharedDataContext);
+
+    assertNotNull(result.getStructuredOut());
+    assertEquals(0, result.getExitCode());
+    assertEquals(HostRoleStatus.HOLDING.toString(), result.getStatus());
+    assertEquals("There were 3 skipped failure(s) that must be addressed " +
+      "before you can proceed. Please resolve each failure before continuing with the upgrade.",
+      result.getStdOut());
+    assertEquals("{\"failures\":" +
+        "{\"service_check\":[\"ZOOKEEPER\"]," +
+        "\"host_component\":{" +
+        "\"host1.vm\":[{\"component\":\"DATANODE\",\"service\":\"HDFS\"}]," +
+        "\"host2.vm\":[{\"component\":\"DATANODE\",\"service\":\"HDFS\"}]}}," +
+        "\"skipped\":[\"service_check\",\"host_component\"]}",
+      result.getStructuredOut());
+    assertEquals("The following steps failed but were automatically skipped:\n" +
+      "DATANODE on host1.vm: RESTART HDFS/DATANODE\n" +
+      "DATANODE on host2.vm: RESTART HDFS/DATANODE\n" +
+      "ZOOKEEPER_CLIENT on host2.vm: SERVICE_CHECK ZOOKEEPER\n", result.getStdErr());
+  }
+
+  /**
+   * Tests workflow with failed service check
+   */
+  @Test
+  public void testAutoSkipFailedSummaryAction__red__service_checks_only() throws Exception {
+    AutoSkipFailedSummaryAction action = new AutoSkipFailedSummaryAction();
+    m_injector.injectMembers(action);
+
+    ServiceComponentHostEvent event = createNiceMock(ServiceComponentHostEvent.class);
+
+    // Set mock for parent's getHostRoleCommand()
+    final HostRoleCommand hostRoleCommand = new HostRoleCommand("host1", Role.AMBARI_SERVER_ACTION,
+      event, RoleCommand.EXECUTE, hostDAO, executionCommandDAO);
+    hostRoleCommand.setRequestId(1l);
+    hostRoleCommand.setStageId(1l);
+
+    ExecutionCommand executionCommand = new ExecutionCommand();
+    executionCommand.setClusterName("cc");
+    executionCommand.setRoleCommand(RoleCommand.EXECUTE);
+    executionCommand.setRole("AMBARI_SERVER_ACTION");
+    executionCommand.setServiceName("");
+    executionCommand.setTaskId(1l);
+    ExecutionCommandWrapper wrapper = new ExecutionCommandWrapper(executionCommand);
+    hostRoleCommand.setExecutionCommandWrapper(wrapper);
+
+    Field f = AbstractServerAction.class.getDeclaredField("hostRoleCommand");
+    f.setAccessible(true);
+    f.set(action, hostRoleCommand);
+
+    final UpgradeItemEntity upgradeItem1 = new UpgradeItemEntity();
+    upgradeItem1.setStageId(5l);
+    final UpgradeItemEntity upgradeItem2 = new UpgradeItemEntity();
+    upgradeItem2.setStageId(6l);
+
+    UpgradeGroupEntity upgradeGroupEntity = new UpgradeGroupEntity();
+    upgradeGroupEntity.setId(11l);
+    // List of upgrade items in group
+    List<UpgradeItemEntity> groupUpgradeItems = new ArrayList<UpgradeItemEntity>(){{
+      add(upgradeItem1);
+      add(upgradeItem2);
+    }};
+    upgradeGroupEntity.setItems(groupUpgradeItems);
+
+    UpgradeItemEntity upgradeItemEntity = new UpgradeItemEntity();
+    upgradeItemEntity.setGroupEntity(upgradeGroupEntity);
+
+    expect(upgradeDAOMock.findUpgradeItemByRequestAndStage(anyLong(), anyLong())).andReturn(upgradeItemEntity).anyTimes();
+    expect(upgradeDAOMock.findUpgradeGroup(anyLong())).andReturn(upgradeGroupEntity).anyTimes();
+    replay(upgradeDAOMock);
+
+    List<HostRoleCommandEntity> skippedTasks = new ArrayList<HostRoleCommandEntity>() {{
+      add(createSkippedTask("ZOOKEEPER_QUORUM_SERVICE_CHECK", "ZOOKEEPER_CLIENT", "host2.vm",
+        "SERVICE_CHECK ZOOKEEPER", RoleCommand.SERVICE_CHECK, null));
+    }};
+    expect(hostRoleCommandDAOMock.findByStatusBetweenStages(anyLong(),
+      anyObject(HostRoleStatus.class), anyLong(), anyLong())).andReturn(skippedTasks).anyTimes();
+    replay(hostRoleCommandDAOMock);
+
+    ConcurrentMap<String, Object> requestSharedDataContext = new ConcurrentHashMap<String, Object>();
+    CommandReport result = action.execute(requestSharedDataContext);
+
+    assertNotNull(result.getStructuredOut());
+    assertEquals(0, result.getExitCode());
+    assertEquals(HostRoleStatus.HOLDING.toString(), result.getStatus());
+    assertEquals("There were 1 skipped failure(s) that must be addressed " +
+        "before you can proceed. Please resolve each failure before continuing with the upgrade.",
+      result.getStdOut());
+    assertEquals("{\"failures\":{\"service_check\":[\"ZOOKEEPER\"]},\"skipped\":[\"service_check\"]}",
+      result.getStructuredOut());
+    assertEquals("The following steps failed but were automatically skipped:\n" +
+      "ZOOKEEPER_CLIENT on host2.vm: SERVICE_CHECK ZOOKEEPER\n", result.getStdErr());
+  }
+
+  /**
+   * Tests workflow with failed host component tasks only
+   */
+  @Test
+  public void testAutoSkipFailedSummaryAction__red__host_components_only() throws Exception {
+    AutoSkipFailedSummaryAction action = new AutoSkipFailedSummaryAction();
+    m_injector.injectMembers(action);
+
+    ServiceComponentHostEvent event = createNiceMock(ServiceComponentHostEvent.class);
+
+    // Set mock for parent's getHostRoleCommand()
+    final HostRoleCommand hostRoleCommand = new HostRoleCommand("host1", Role.AMBARI_SERVER_ACTION,
+      event, RoleCommand.EXECUTE, hostDAO, executionCommandDAO);
+    hostRoleCommand.setRequestId(1l);
+    hostRoleCommand.setStageId(1l);
+
+    ExecutionCommand executionCommand = new ExecutionCommand();
+    executionCommand.setClusterName("cc");
+    executionCommand.setRoleCommand(RoleCommand.EXECUTE);
+    executionCommand.setRole("AMBARI_SERVER_ACTION");
+    executionCommand.setServiceName("");
+    executionCommand.setTaskId(1l);
+    ExecutionCommandWrapper wrapper = new ExecutionCommandWrapper(executionCommand);
+    hostRoleCommand.setExecutionCommandWrapper(wrapper);
+
+    Field f = AbstractServerAction.class.getDeclaredField("hostRoleCommand");
+    f.setAccessible(true);
+    f.set(action, hostRoleCommand);
+
+    final UpgradeItemEntity upgradeItem1 = new UpgradeItemEntity();
+    upgradeItem1.setStageId(5l);
+    final UpgradeItemEntity upgradeItem2 = new UpgradeItemEntity();
+    upgradeItem2.setStageId(6l);
+
+    UpgradeGroupEntity upgradeGroupEntity = new UpgradeGroupEntity();
+    upgradeGroupEntity.setId(11l);
+    // List of upgrade items in group
+    List<UpgradeItemEntity> groupUpgradeItems = new ArrayList<UpgradeItemEntity>(){{
+      add(upgradeItem1);
+      add(upgradeItem2);
+    }};
+    upgradeGroupEntity.setItems(groupUpgradeItems);
+
+    UpgradeItemEntity upgradeItemEntity = new UpgradeItemEntity();
+    upgradeItemEntity.setGroupEntity(upgradeGroupEntity);
+
+    expect(upgradeDAOMock.findUpgradeItemByRequestAndStage(anyLong(), anyLong())).andReturn(upgradeItemEntity).anyTimes();
+    expect(upgradeDAOMock.findUpgradeGroup(anyLong())).andReturn(upgradeGroupEntity).anyTimes();
+    replay(upgradeDAOMock);
+
+    List<HostRoleCommandEntity> skippedTasks = new ArrayList<HostRoleCommandEntity>() {{
+      add(createSkippedTask("DATANODE", "DATANODE", "host1.vm",
+        "RESTART HDFS/DATANODE", RoleCommand.CUSTOM_COMMAND,
+        "RESTART"
+      ));
+      add(createSkippedTask("DATANODE", "DATANODE", "host2.vm",
+        "RESTART HDFS/DATANODE", RoleCommand.CUSTOM_COMMAND,
+        "RESTART"));
+    }};
+    expect(hostRoleCommandDAOMock.findByStatusBetweenStages(anyLong(),
+      anyObject(HostRoleStatus.class), anyLong(), anyLong())).andReturn(skippedTasks).anyTimes();
+    replay(hostRoleCommandDAOMock);
+
+    ConcurrentMap<String, Object> requestSharedDataContext = new ConcurrentHashMap<String, Object>();
+    CommandReport result = action.execute(requestSharedDataContext);
+
+    assertNotNull(result.getStructuredOut());
+    assertEquals(0, result.getExitCode());
+    assertEquals(HostRoleStatus.HOLDING.toString(), result.getStatus());
+    assertEquals("There were 2 skipped failure(s) that must be addressed " +
+        "before you can proceed. Please resolve each failure before continuing with the upgrade.",
+      result.getStdOut());
+    assertEquals("{\"failures\":" +
+        "{\"host_component\":" +
+        "{\"host1.vm\":[{\"component\":\"DATANODE\",\"service\":\"HDFS\"}]," +
+        "\"host2.vm\":[{\"component\":\"DATANODE\",\"service\":\"HDFS\"}]}}," +
+        "\"skipped\":[\"host_component\"]}",
+      result.getStructuredOut());
+    assertEquals("The following steps failed but were automatically skipped:\n" +
+      "DATANODE on host1.vm: RESTART HDFS/DATANODE\n" +
+      "DATANODE on host2.vm: RESTART HDFS/DATANODE\n", result.getStdErr());
+  }
+
+
+
+  private HostRoleCommandEntity createSkippedTask(String role, String componentName,
+                                                  String hostname, String commandDetail,
+                                                  RoleCommand roleCommand, String customCommandName) {
+    HostRoleCommandEntity result = new HostRoleCommandEntity();
+
+    ServiceComponentHostEvent event = new ServiceComponentHostOpInProgressEvent(componentName, hostname, 77l);
+    ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper(event);
+    result.setEvent(eventWrapper.getEventJson());
+
+    HostEntity hostEntity = new HostEntity();
+    hostEntity.setHostName(hostname);
+    result.setHostEntity(hostEntity);
+
+    result.setTaskId(7l);
+    result.setExitcode(1);
+    result.setOutputLog("/output.log");
+    result.setErrorLog("/error.log");
+    result.setStdOut("Some stdout".getBytes());
+    result.setStdError("Some stderr".getBytes());
+    result.setCommandDetail(commandDetail);
+    result.setRole(Role.valueOf(role));
+    result.setRoleCommand(roleCommand);
+    result.setCustomCommandName(customCommandName);
+    result.setStatus(HostRoleStatus.SKIPPED_FAILED);
+
+    return result;
+  }
+
+
+  public class MockModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(UpgradeDAO.class).toInstance(upgradeDAOMock);
+      bind(HostRoleCommandDAO.class).toInstance(hostRoleCommandDAOMock);
+      bind(Clusters.class).toInstance(clustersMock);
+    }
+  }
+
+}