You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2016/03/11 21:20:33 UTC

ambari git commit: AMBARI-15330. Bubble up errors during RU/EU (alejandro)

Repository: ambari
Updated Branches:
  refs/heads/trunk be9d76ed0 -> 0f0d77660


AMBARI-15330. Bubble up errors during RU/EU (alejandro)


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

Branch: refs/heads/trunk
Commit: 0f0d7766017d697f8e1e19be476743516f360b4f
Parents: be9d76e
Author: Alejandro Fernandez <af...@hortonworks.com>
Authored: Thu Mar 10 14:45:35 2016 -0800
Committer: Alejandro Fernandez <af...@hortonworks.com>
Committed: Fri Mar 11 12:20:16 2016 -0800

----------------------------------------------------------------------
 .../server/actionmanager/HostRoleStatus.java    |   9 +
 .../resources/ResourceInstanceFactoryImpl.java  |   5 +
 .../server/api/services/ClusterService.java     |  15 +
 .../api/services/UpgradeSummaryService.java     |  83 +++++
 .../internal/DefaultProviderModule.java         |   2 +
 .../internal/TaskResourceProvider.java          |  29 ++
 .../controller/internal/UpgradeSummary.java     |  77 ++++
 .../UpgradeSummaryResourceProvider.java         | 202 +++++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../server/orm/dao/HostRoleCommandDAO.java      |  37 ++
 .../orm/entities/HostRoleCommandEntity.java     |   2 +
 .../ambari/server/state/UpgradeHelper.java      |  27 ++
 .../UpgradeSummaryResourceProviderTest.java     | 360 +++++++++++++++++++
 13 files changed, 850 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/HostRoleStatus.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/HostRoleStatus.java b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/HostRoleStatus.java
index 52523c7..f5ba1c6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/HostRoleStatus.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/HostRoleStatus.java
@@ -84,6 +84,8 @@ public enum HostRoleStatus {
   private static List<HostRoleStatus> HOLDING_STATES = Arrays.asList(HOLDING, HOLDING_FAILED,
       HOLDING_TIMEDOUT);
 
+  public static List<HostRoleStatus> SCHEDULED_STATES = Arrays.asList(PENDING, QUEUED, IN_PROGRESS);
+
   /**
    * The {@link HostRoleStatus}s that represent any commands which are
    * considered to be "Failed".
@@ -92,6 +94,13 @@ public enum HostRoleStatus {
       SKIPPED_FAILED);
 
   /**
+   * The {@link HostRoleStatus}s that represent the current commands that failed during stack upgrade.
+   * This is not used to indicate commands that failed and then skipped.
+   */
+  public static EnumSet<HostRoleStatus> STACK_UPGRADE_FAILED_STATUSES = EnumSet.of(FAILED, HOLDING_FAILED,
+      HOLDING_TIMEDOUT);
+
+  /**
    * The {@link HostRoleStatus}s that represent any commands which are
    * considered to be "In Progress".
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index 3526e23..d10b7a8 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -346,6 +346,11 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
             Resource.Type.UpgradeItem, "upgrade_item", "upgrade_items", Resource.Type.Task);
         break;
 
+      case UpgradeSummary:
+        resourceDefinition = new SimpleResourceDefinition(
+            Resource.Type.UpgradeSummary, "upgrade_summary", "upgrade_summary");
+        break;
+
       case PreUpgradeCheck:
         resourceDefinition = new SimpleResourceDefinition(Resource.Type.PreUpgradeCheck, "rolling_upgrade_check", "rolling_upgrade_checks");
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
index 7200b83..8e9b771 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
@@ -576,6 +576,21 @@ public class ClusterService extends BaseService {
   }
 
   /**
+   * Gets a list of upgrade summaries.
+   *
+   * @param request the request
+   * @param clusterName the cluster name
+   *
+   * @return the upgrade summary service
+   */
+  @Path("{clusterName}/upgrade_summary")
+  public UpgradeSummaryService getUpgradeSummaryService(
+      @Context javax.ws.rs.core.Request request,
+      @PathParam("clusterName") String clusterName) {
+    return new UpgradeSummaryService(clusterName);
+  }
+  
+  /**
    * Gets the pre-upgrade checks service.
    *
    * @param request the request

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradeSummaryService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradeSummaryService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradeSummaryService.java
new file mode 100644
index 0000000..b8b3e86
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradeSummaryService.java
@@ -0,0 +1,83 @@
+/**
+ * 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.api.services;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Endpoint for a detailed summary of the cluster upgrades.
+ */
+public class UpgradeSummaryService extends BaseService {
+
+  private String m_clusterName = null;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterName the cluster name (not {@code null}).
+   */
+  UpgradeSummaryService(String clusterName) {
+    m_clusterName = clusterName;
+  }
+
+  @GET
+  @Produces("text/plain")
+  public Response getUpgradeSummaries(@Context HttpHeaders headers,
+                                      @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET,
+        createResourceInstance(null));
+  }
+
+  @GET
+  @Path("{requestId}")
+  @Produces("text/plain")
+  public Response getUpgradeSummary(@Context HttpHeaders headers,
+                                    @Context UriInfo ui,
+                                    @PathParam("requestId") Long requestId) {
+    return handleRequest(headers, null, ui, Request.Type.GET,
+        createResourceInstance(requestId));
+  }
+
+  /**
+   * @param requestId the upgrade's request Id
+   * @return the resource instance
+   */
+  private ResourceInstance createResourceInstance(Long requestId) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Cluster, m_clusterName);
+
+    if (null != requestId) {
+      mapIds.put(Resource.Type.UpgradeSummary, requestId.toString());
+    }
+
+    return createResource(Resource.Type.UpgradeSummary, mapIds);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
index d1d3fe6..c7dc117 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
@@ -102,6 +102,8 @@ public class DefaultProviderModule extends AbstractProviderModule {
         return new UpgradeGroupResourceProvider(managementController);
       case UpgradeItem:
         return new UpgradeItemResourceProvider(managementController);
+      case UpgradeSummary:
+        return new UpgradeSummaryResourceProvider(managementController);
       case ClusterStackVersion:
         return new ClusterStackVersionResourceProvider(managementController);
       case PreUpgradeCheck:

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/TaskResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/TaskResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/TaskResourceProvider.java
index 510d6fb..cb8a343 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/TaskResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/TaskResourceProvider.java
@@ -81,6 +81,35 @@ public class TaskResourceProvider extends AbstractControllerResourceProvider {
           TASK_ID_PROPERTY_ID}));
 
   /**
+   * The property ids for a task resource.
+   */
+  static final Set<String> PROPERTY_IDS = new HashSet<String>();
+
+  // These are static so that they can be referenced by other classes such as UpgradeSummaryResourceProvider.java
+  static {
+    // properties
+    PROPERTY_IDS.add(TASK_CLUSTER_NAME_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_REQUEST_ID_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_ID_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_STAGE_ID_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_HOST_NAME_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_ROLE_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_COMMAND_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_STATUS_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_EXIT_CODE_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_STDERR_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_STOUT_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_OUTPUTLOG_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_ERRORLOG_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_STRUCT_OUT_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_START_TIME_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_END_TIME_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_ATTEMPT_CNT_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_COMMAND_DET_PROPERTY_ID);
+    PROPERTY_IDS.add(TASK_CUST_CMD_NAME_PROPERTY_ID);
+  }
+
+  /**
    * Used for querying tasks.
    */
   @Inject

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummary.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummary.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummary.java
new file mode 100644
index 0000000..f0f6914
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummary.java
@@ -0,0 +1,77 @@
+/**
+ * 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.controller.internal;
+
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+
+/**
+ * Represents a detailed summary of an upgrade, including the most recent failed task in order to bubble up errors.
+ */
+public class UpgradeSummary {
+  private String displayText;
+  private Long requestId;
+  private Long stageId;
+  private Long taskId;
+  private String hostName;
+  private HostRoleCommandEntity failedTask;
+
+  public UpgradeSummary(String displayText, Long requestId, Long stageId, Long taskId, String hostName, HostRoleCommandEntity failedTask) {
+    this.displayText = displayText;
+    this.requestId = requestId;
+    this.stageId = stageId;
+    this.taskId = taskId;
+    this.hostName = hostName;
+    this.failedTask = failedTask;
+  }
+
+  public Long getStageId() {
+    return stageId;
+  }
+
+  public Long getTaskId() {
+    return taskId;
+  }
+
+  public UpgradeSummary(HostRoleCommandEntity hrc) {
+    this("", hrc.getRequestId(), hrc.getStageId(), hrc.getTaskId(), hrc.getHostName(), hrc);
+
+    // Construct a message to display on the UI.
+    displayText = "Failed";
+    if (hrc.getCommandDetail() != null) {
+      displayText += " calling " + hrc.getCommandDetail();
+    }
+    if (hrc.getHostName() != null) {
+      displayText += " on host " + hrc.getHostName();
+    }
+  }
+
+  /**
+   * Get the error message to display.
+   */
+  public String getDisplayText() {
+    return this.displayText;
+  }
+
+  /**
+   * Get the failed task if it exists.
+   * @return The failed task if it exists, otherwise null.
+   */
+  public HostRoleCommandEntity getFailedTask() {
+    return failedTask;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProvider.java
new file mode 100644
index 0000000..cf4b08f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProvider.java
@@ -0,0 +1,202 @@
+/**
+ * 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.controller.internal;
+
+import com.google.inject.Inject;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.StaticallyInject;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+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.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+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.UpgradeEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.UpgradeHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+/**
+ * Get a summary of an upgrade request.
+ */
+@StaticallyInject
+public class UpgradeSummaryResourceProvider extends AbstractControllerResourceProvider {
+  protected static final String UPGRADE_SUMMARY_CLUSTER_NAME = "UpgradeSummary/cluster_name";
+  protected static final String UPGRADE_SUMMARY_REQUEST_ID = "UpgradeSummary/request_id";
+
+  protected static final String UPGRADE_SUMMARY_FAIL_REASON = PropertyHelper.getPropertyId("UpgradeSummary", "fail_reason");
+
+  private static final Set<String> PK_PROPERTY_IDS = new HashSet<String>(
+      Arrays.asList(UPGRADE_SUMMARY_REQUEST_ID, UPGRADE_SUMMARY_CLUSTER_NAME));
+  private static final Set<String> PROPERTY_IDS = new HashSet<String>();
+  private static Map<String, String> TASK_MAPPED_IDS = new HashMap<String, String>();
+
+  private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = new HashMap<Resource.Type, String>();
+
+  @Inject
+  private static UpgradeDAO s_upgradeDAO = null;
+
+  @Inject
+  private static HostRoleCommandDAO s_hostRoleCommandDAO = null;
+
+  /**
+   * Used to request a resource for a given task.
+   */
+  @Inject
+  private static UpgradeHelper s_upgradeHelper;
+
+  static {
+    // Properties
+    PROPERTY_IDS.add(UPGRADE_SUMMARY_CLUSTER_NAME);
+    PROPERTY_IDS.add(UPGRADE_SUMMARY_REQUEST_ID);
+    PROPERTY_IDS.add(UPGRADE_SUMMARY_FAIL_REASON);
+
+    // Inherit all of the properties from a Task as well to return data about the current task if it failed.
+    for (String p : TaskResourceProvider.PROPERTY_IDS) {
+      TASK_MAPPED_IDS.put(p, p.replace("Tasks/", "UpgradeSummary/failed_task/"));
+    }
+    PROPERTY_IDS.addAll(TASK_MAPPED_IDS.values());
+
+    // Keys
+    KEY_PROPERTY_IDS.put(Resource.Type.UpgradeSummary, UPGRADE_SUMMARY_REQUEST_ID);
+    KEY_PROPERTY_IDS.put(Resource.Type.Cluster, UPGRADE_SUMMARY_CLUSTER_NAME);
+  }
+
+  private static final Logger LOG = LoggerFactory.getLogger(UpgradeSummaryResourceProvider.class);
+
+  /**
+   * Constructor.
+   *
+   * @param controller the controller
+   */
+  public UpgradeSummaryResourceProvider(AmbariManagementController controller) {
+    super(PROPERTY_IDS, KEY_PROPERTY_IDS, controller);
+  }
+
+  @Override
+  public RequestStatus createResources(final Request request) throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
+
+    throw new UnsupportedOperationException("Resource only supports GET operation.");
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException,
+      UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+    Set<Resource> resources = new HashSet<Resource>();
+    Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      String clusterName = (String) propertyMap.get(UPGRADE_SUMMARY_CLUSTER_NAME);
+
+      if (null == clusterName || clusterName.isEmpty()) {
+        throw new IllegalArgumentException(
+            "The cluster name is required when querying for upgrades");
+      }
+
+      Cluster cluster;
+      try {
+        cluster = getManagementController().getClusters().getCluster(clusterName);
+      } catch (AmbariException e) {
+        throw new NoSuchResourceException(
+            String.format("Cluster %s could not be loaded", clusterName));
+      }
+
+      List<UpgradeEntity> upgrades = new ArrayList<UpgradeEntity>();
+      String upgradeRequestIdStr = (String) propertyMap.get(UPGRADE_SUMMARY_REQUEST_ID);
+      if (null != upgradeRequestIdStr) {
+        UpgradeEntity upgrade = s_upgradeDAO.findUpgradeByRequestId(Long.valueOf(upgradeRequestIdStr));
+
+        if (null != upgrade) {
+          upgrades.add(upgrade);
+        }
+      } else {
+        upgrades = s_upgradeDAO.findUpgrades(cluster.getClusterId());
+      }
+
+      for (UpgradeEntity entity : upgrades) {
+        Resource resource = new ResourceImpl(Resource.Type.UpgradeSummary);
+        Long upgradeRequestId = entity.getRequestId();
+
+        setResourceProperty(resource, UPGRADE_SUMMARY_CLUSTER_NAME, clusterName, requestPropertyIds);
+        setResourceProperty(resource, UPGRADE_SUMMARY_REQUEST_ID, entity.getRequestId(), requestPropertyIds);
+
+        HostRoleCommandEntity mostRecentFailure = s_hostRoleCommandDAO.findMostRecentFailure(upgradeRequestId);
+
+        String displayText = null;
+        HostRoleCommandEntity failedTask = null;
+        if (mostRecentFailure != null) {
+          UpgradeSummary summary = new UpgradeSummary(mostRecentFailure);
+          displayText = summary.getDisplayText();
+          failedTask = summary.getFailedTask();
+
+          Resource taskResource = s_upgradeHelper.getTaskResource(clusterName, failedTask.getRequestId(),
+              failedTask.getStageId(), failedTask.getTaskId());
+
+          // Include properties from the failed task.
+          if (taskResource != null) {
+            for (Map.Entry<String, String> property : TASK_MAPPED_IDS.entrySet()) {
+              String taskPropertyId = property.getKey();
+              String upgradeSummaryPropertyId = property.getValue();
+
+              setResourceProperty(resource, upgradeSummaryPropertyId, taskResource.getPropertyValue(taskPropertyId), requestPropertyIds);
+            }
+          }
+        }
+        setResourceProperty(resource, UPGRADE_SUMMARY_FAIL_REASON, displayText, requestPropertyIds);
+        resources.add(resource);
+      }
+    }
+
+    return resources;
+  }
+
+  @Override
+  public RequestStatus updateResources(final Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException,
+      NoSuchParentResourceException {
+    throw new UnsupportedOperationException("Resource only supports GET operation.");
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate) throws SystemException,
+      UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+    throw new UnsupportedOperationException("Resource only supports GET operation.");
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return PK_PROPERTY_IDS;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index e79f300..85fd649 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -138,6 +138,7 @@ public interface Resource {
     Upgrade,
     UpgradeGroup,
     UpgradeItem,
+    UpgradeSummary,
     PreUpgradeCheck,
     Stage,
     StackArtifact,
@@ -251,6 +252,7 @@ public interface Resource {
     public static final Type Upgrade = InternalType.Upgrade.getType();
     public static final Type UpgradeGroup = InternalType.UpgradeGroup.getType();
     public static final Type UpgradeItem = InternalType.UpgradeItem.getType();
+    public static final Type UpgradeSummary = InternalType.UpgradeSummary.getType();
     public static final Type PreUpgradeCheck = InternalType.PreUpgradeCheck.getType();
     public static final Type Stage = InternalType.Stage.getType();
     public static final Type StackArtifact = InternalType.StackArtifact.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/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 b48ffa8..f5b1cb4 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
@@ -23,6 +23,7 @@ import static org.apache.ambari.server.orm.dao.DaoUtils.ORACLE_LIST_LIMIT;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -689,6 +690,42 @@ public class HostRoleCommandDAO {
     }
   }
 
+  /**
+   * During Rolling and Express Upgrade, want to bubble up the error of the most recent failure, i.e., greatest
+   * task id, assuming that there are no other completed tasks after it.
+   * @param requestId upgrade request id
+   * @return Most recent task failure during stack upgrade, or null if one doesn't exist.
+   */
+  public HostRoleCommandEntity findMostRecentFailure(Long requestId) {
+    TypedQuery<HostRoleCommandEntity> query = entityManagerProvider.get().createNamedQuery(
+        "HostRoleCommandEntity.findTasksByStatusesOrderByIdDesc", HostRoleCommandEntity.class);
+
+    query.setParameter("requestId", requestId);
+    query.setParameter("statuses", HostRoleStatus.STACK_UPGRADE_FAILED_STATUSES);
+    List results = query.getResultList();
+
+    if (!results.isEmpty()) {
+      HostRoleCommandEntity candidate = (HostRoleCommandEntity) results.get(0);
+
+      // Ensure that there are no other completed tasks in a future stage to avoid returning an old error.
+      // During Express Upgrade, we can run multiple commands in the same stage, so it's possible to have
+      // COMPLETED tasks in the failed task's stage.
+      // During Rolling Upgrade, we run exactly one command per stage.
+      TypedQuery<Number> numberAlreadyRanTasksInFutureStage = entityManagerProvider.get().createNamedQuery(
+          "HostRoleCommandEntity.findNumTasksAlreadyRanInStage", Number.class);
+
+      numberAlreadyRanTasksInFutureStage.setParameter("requestId", requestId);
+      numberAlreadyRanTasksInFutureStage.setParameter("taskId", candidate.getTaskId());
+      numberAlreadyRanTasksInFutureStage.setParameter("stageId", candidate.getStageId());
+      numberAlreadyRanTasksInFutureStage.setParameter("statuses", HostRoleStatus.SCHEDULED_STATES);
+
+      Number result = daoUtils.selectSingle(numberAlreadyRanTasksInFutureStage);
+      if (result.longValue() == 0L) {
+        return candidate;
+      }
+    }
+    return null;
+  }
 
   /**
    * Updates the {@link HostRoleCommandEntity#isFailureAutoSkipped()} flag for

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/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 1674175..19f0602 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
@@ -61,6 +61,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.findTasksByStatusesOrderByIdDesc", query = "SELECT task FROM HostRoleCommandEntity task WHERE task.requestId = :requestId AND task.status IN :statuses ORDER BY task.taskId DESC"),
+    @NamedQuery(name = "HostRoleCommandEntity.findNumTasksAlreadyRanInStage", query = "SELECT COUNT(task.taskId) FROM HostRoleCommandEntity task WHERE task.requestId = :requestId AND task.taskId > :taskId AND task.stageId > :stageId AND task.status NOT 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.findByHostRole", query = "SELECT command FROM HostRoleCommandEntity command WHERE command.hostEntity.hostName=:hostName AND command.requestId=:requestId AND command.stageId=:stageId AND command.role=:role ORDER BY command.taskId"),

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/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 2ac4d25..66272e3 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
@@ -31,6 +31,7 @@ import java.util.regex.Pattern;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.internal.StageResourceProvider;
+import org.apache.ambari.server.controller.internal.TaskResourceProvider;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
@@ -706,6 +707,32 @@ public class UpgradeHelper {
   }
 
   /**
+   * Get a single resource for the task with the given parameters.
+   * @param clusterName Cluster Name
+   * @param requestId Request Id
+   * @param stageId Stage Id
+   * @param taskId Task Id
+   * @return Single task resource that matches the predicates, otherwise, null.
+   */
+  public Resource getTaskResource(String clusterName, Long requestId, Long stageId, Long taskId)
+      throws UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException, SystemException {
+    ClusterController clusterController = ClusterControllerHelper.getClusterController();
+
+    Request request = PropertyHelper.getReadRequest();
+
+    Predicate p1 = new PredicateBuilder().property(TaskResourceProvider.TASK_CLUSTER_NAME_PROPERTY_ID).equals(clusterName).toPredicate();
+    Predicate p2 = new PredicateBuilder().property(TaskResourceProvider.TASK_REQUEST_ID_PROPERTY_ID).equals(requestId.toString()).toPredicate();
+    Predicate p3 = new PredicateBuilder().property(TaskResourceProvider.TASK_STAGE_ID_PROPERTY_ID).equals(stageId.toString()).toPredicate();
+    Predicate p4 = new PredicateBuilder().property(TaskResourceProvider.TASK_ID_PROPERTY_ID).equals(taskId.toString()).toPredicate();
+
+    QueryResponse response = clusterController.getResources(Resource.Type.Task,
+        request, new AndPredicate(p1, p2, p3, p4));
+
+    Set<Resource> task = response.getResources();
+    return task.size() == 1 ? task.iterator().next() : null;
+  }
+
+  /**
    * Helper to set service and component display names on the context
    * @param context   the context to update
    * @param service   the service name

http://git-wip-us.apache.org/repos/asf/ambari/blob/0f0d7766/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProviderTest.java
new file mode 100644
index 0000000..eccc1ed
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeSummaryResourceProviderTest.java
@@ -0,0 +1,360 @@
+/**
+ * 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.controller.internal;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.persist.PersistService;
+import com.google.inject.persist.Transactional;
+import com.google.inject.util.Modules;
+import junit.framework.Assert;
+import org.apache.ambari.server.Role;
+import org.apache.ambari.server.RoleCommand;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.actionmanager.ServiceComponentHostEventWrapper;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.AmbariServer;
+import org.apache.ambari.server.controller.predicate.AndPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+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.dao.HostDAO;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.RequestDAO;
+import org.apache.ambari.server.orm.dao.StackDAO;
+import org.apache.ambari.server.orm.dao.StageDAO;
+import org.apache.ambari.server.orm.dao.UpgradeDAO;
+import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
+import org.apache.ambari.server.orm.entities.RequestEntity;
+import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.orm.entities.StageEntity;
+import org.apache.ambari.server.orm.entities.UpgradeEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Host;
+import org.apache.ambari.server.state.HostState;
+import org.apache.ambari.server.state.RepositoryVersionState;
+import org.apache.ambari.server.state.Service;
+import org.apache.ambari.server.state.ServiceComponent;
+import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.ServiceComponentHostEvent;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.UpgradeHelper;
+import org.apache.ambari.server.state.stack.upgrade.Direction;
+import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostOpInProgressEvent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * UpgradeSummaryResourceProvider tests.
+ */
+public class UpgradeSummaryResourceProviderTest {
+
+  private HostDAO hostDAO;
+  private StackDAO stackDAO;
+  private RepositoryVersionDAO repoVersionDAO;
+  private UpgradeDAO upgradeDAO;
+  private RequestDAO requestDAO;
+  private StageDAO stageDAO;
+  private HostRoleCommandDAO hrcDAO;
+
+  @Inject
+  private UpgradeHelper m_upgradeHelper;
+
+  private Injector injector;
+  private Clusters clusters;
+  private OrmTestHelper helper;
+  private AmbariManagementController amc;
+
+  private String clusterName = "c1";
+
+  @Before
+  public void before() throws Exception {
+    m_upgradeHelper = createNiceMock(UpgradeHelper.class);
+
+    // Create an injector which will inject the mocks
+    injector = Guice.createInjector(Modules.override(new InMemoryDefaultTestModule()).with(new MockModule()));
+    injector.getInstance(GuiceJpaInitializer.class);
+    helper = injector.getInstance(OrmTestHelper.class);
+    amc = injector.getInstance(AmbariManagementController.class);
+
+    Field field = AmbariServer.class.getDeclaredField("clusterController");
+    field.setAccessible(true);
+    field.set(null, amc);
+
+    hostDAO = injector.getInstance(HostDAO.class);
+    stackDAO = injector.getInstance(StackDAO.class);
+    repoVersionDAO = injector.getInstance(RepositoryVersionDAO.class);
+    upgradeDAO = injector.getInstance(UpgradeDAO.class);
+    requestDAO = injector.getInstance(RequestDAO.class);
+    stageDAO = injector.getInstance(StageDAO.class);
+    hrcDAO = injector.getInstance(HostRoleCommandDAO.class);
+  }
+
+  @After
+  public void after() {
+    injector.getInstance(PersistService.class).stop();
+    injector = null;
+  }
+
+  /**
+   * Create a Cluster called c1 on HDP 2.2.0 with a single ZOOKEEPER_SERVER on Host h1
+   */
+  public void createCluster() throws Exception {
+    StackEntity stackEntity = stackDAO.find("HDP", "2.2.0");
+
+    RepositoryVersionEntity repoVersionEntity = new RepositoryVersionEntity();
+    repoVersionEntity.setDisplayName("For Stack Version 2.2.0");
+    repoVersionEntity.setOperatingSystems("");
+    repoVersionEntity.setStack(stackEntity);
+    repoVersionEntity.setVersion("2.2.0.0");
+    repoVersionDAO.create(repoVersionEntity);
+
+    clusters = injector.getInstance(Clusters.class);
+
+    StackId stackId = new StackId("HDP-2.2.0");
+    clusters.addCluster(clusterName, stackId);
+    Cluster cluster = clusters.getCluster("c1");
+
+    helper.getOrCreateRepositoryVersion(stackId, stackId.getStackVersion());
+    cluster.createClusterVersion(stackId, stackId.getStackVersion(), "admin", RepositoryVersionState.INSTALLING);
+
+    clusters.addHost("h1");
+    Host host = clusters.getHost("h1");
+    Map<String, String> hostAttributes = new HashMap<String, String>();
+    hostAttributes.put("os_family", "redhat");
+    hostAttributes.put("os_release_version", "6.4");
+    host.setHostAttributes(hostAttributes);
+    host.setState(HostState.HEALTHY);
+    host.persist();
+
+    clusters.mapHostToCluster("h1", "c1");
+
+    // add a single ZOOKEEPER server
+    Service service = cluster.addService("ZOOKEEPER");
+    service.setDesiredStackVersion(cluster.getDesiredStackVersion());
+    service.persist();
+
+    ServiceComponent component = service.addServiceComponent("ZOOKEEPER_SERVER");
+    ServiceComponentHost sch = component.addServiceComponentHost("h1");
+    sch.setVersion("2.2.0.0");
+
+    component = service.addServiceComponent("ZOOKEEPER_CLIENT");
+    sch = component.addServiceComponentHost("h1");
+    sch.setVersion("2.2.0.0");
+  }
+
+  /**
+   * Create a request, stage, and completed task.
+   * @param cluster
+   * @param upgradeRequestId
+   * @param stageId
+   */
+  @Transactional
+  private void createCommands(Cluster cluster, Long upgradeRequestId, Long stageId) {
+    HostEntity h1 = hostDAO.findByName("h1");
+    ServiceComponentHostEvent event = new ServiceComponentHostOpInProgressEvent("ZOOKEEPER_SERVER", "h1", 1L);
+    ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper(event);
+
+    RequestEntity requestEntity = new RequestEntity();
+    requestEntity.setRequestId(upgradeRequestId);
+    requestEntity.setClusterId(cluster.getClusterId());
+    requestEntity.setStatus(HostRoleStatus.PENDING);
+    requestDAO.create(requestEntity);
+
+    // Create the stage and add it to the request
+    StageEntity stageEntity = new StageEntity();
+    stageEntity.setRequest(requestEntity);
+    stageEntity.setClusterId(cluster.getClusterId());
+    stageEntity.setRequestId(upgradeRequestId);
+    stageEntity.setStageId(stageId);
+    requestEntity.setStages(Collections.singletonList(stageEntity));
+    stageDAO.create(stageEntity);
+    requestDAO.merge(requestEntity);
+
+    // Create the task and add it to the stage
+    HostRoleCommandEntity hrc1 = new HostRoleCommandEntity();
+
+    hrc1.setStage(stageEntity);
+    hrc1.setStatus(HostRoleStatus.COMPLETED);
+    hrc1.setRole(Role.ZOOKEEPER_SERVER);
+    hrc1.setRoleCommand(RoleCommand.RESTART);
+    hrc1.setHostEntity(h1);
+
+    stageEntity.setHostRoleCommands(new ArrayList<HostRoleCommandEntity>());
+    stageEntity.getHostRoleCommands().add(hrc1);
+    h1.getHostRoleCommandEntities().add(hrc1);
+
+    hrcDAO.create(hrc1);
+    hostDAO.merge(h1);
+  }
+
+  /**
+   * Test UpgradeSummaryResourceProvider on several cases.
+   * 1. Incorrect cluster name throws exception
+   * 2. Upgrade with no tasks.
+   * 3. Construct Upgrade with a single COMPLETED task. Resource should not have a failed reason.
+   * 4. Append a failed task to the Upgrade. Resource should have a failed reason.
+   * @throws Exception
+   */
+  @Test
+  public void testGetUpgradeSummary() throws Exception {
+    createCluster();
+
+    Cluster cluster = clusters.getCluster(clusterName);
+    ResourceProvider upgradeSummaryResourceProvider = createProvider(amc);
+
+    // Case 1: Incorrect cluster name throws exception
+    Request requestResource = PropertyHelper.getReadRequest();
+    Predicate pBogus = new PredicateBuilder().property(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_CLUSTER_NAME).equals("bogus name").toPredicate();
+    try {
+      Set<Resource> resources = upgradeSummaryResourceProvider.getResources(requestResource, pBogus);
+      assertTrue("Expected exception to be thrown", false);
+    } catch (Exception e) {
+      ;
+    }
+
+    // Case 2: Upgrade with no tasks.
+    Long upgradeRequestId = 1L;
+
+    Predicate p1 = new PredicateBuilder().property(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_CLUSTER_NAME).equals(clusterName).toPredicate();
+    Predicate p2 = new PredicateBuilder().property(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_REQUEST_ID).equals(upgradeRequestId.toString()).toPredicate();
+    Predicate p1And2 = new AndPredicate(p1, p2);
+
+    Set<Resource> resources = upgradeSummaryResourceProvider.getResources(requestResource, p1And2);
+    assertEquals(0, resources.size());
+
+    UpgradeEntity upgrade = new UpgradeEntity();
+    upgrade.setRequestId(upgradeRequestId);
+    upgrade.setClusterId(cluster.getClusterId());
+    upgrade.setId(1L);
+    upgrade.setUpgradePackage("some-name");
+    upgrade.setUpgradeType(UpgradeType.ROLLING);
+    upgrade.setDirection(Direction.UPGRADE);
+    upgrade.setFromVersion("2.2.0.0");
+    upgrade.setToVersion("2.2.0.1");
+    upgradeDAO.create(upgrade);
+
+    // Resource used to make assertions.
+    Resource r;
+
+    resources = upgradeSummaryResourceProvider.getResources(requestResource, p1And2);
+    assertEquals(1, resources.size());
+    r = resources.iterator().next();
+    Assert.assertNull(r.getPropertyValue(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_FAIL_REASON));
+
+    // Case 3: Construct Upgrade with a single COMPLETED task. Resource should not have a failed reason.
+    Long currentStageId = 1L;
+    createCommands(cluster, upgradeRequestId, currentStageId);
+
+    resources = upgradeSummaryResourceProvider.getResources(requestResource, p1And2);
+    assertEquals(1, resources.size());
+    r = resources.iterator().next();
+    Assert.assertNull(r.getPropertyValue(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_FAIL_REASON));
+
+    // Case 4: Append a failed task to the Upgrade. Resource should have a failed reason.
+    RequestEntity requestEntity = requestDAO.findByPK(upgradeRequestId);
+    HostEntity h1 = hostDAO.findByName("h1");
+
+    StageEntity nextStage = new StageEntity();
+    nextStage.setRequest(requestEntity);
+    nextStage.setClusterId(cluster.getClusterId());
+    nextStage.setRequestId(upgradeRequestId);
+    nextStage.setStageId(++currentStageId);
+    requestEntity.getStages().add(nextStage);
+    stageDAO.create(nextStage);
+    requestDAO.merge(requestEntity);
+
+    // Create the task and add it to the stage
+    HostRoleCommandEntity hrc2 = new HostRoleCommandEntity();
+
+    hrc2.setStage(nextStage);
+    // Important that it's on its own stage with a FAILED status.
+    hrc2.setStatus(HostRoleStatus.FAILED);
+    hrc2.setRole(Role.ZOOKEEPER_SERVER);
+    hrc2.setRoleCommand(RoleCommand.RESTART);
+    hrc2.setCommandDetail("Restart ZOOKEEPER_SERVER");
+    hrc2.setHostEntity(h1);
+
+    nextStage.setHostRoleCommands(new ArrayList<HostRoleCommandEntity>());
+    nextStage.getHostRoleCommands().add(hrc2);
+    h1.getHostRoleCommandEntities().add(hrc2);
+
+    hrcDAO.create(hrc2);
+    hostDAO.merge(h1);
+    hrc2.setRequestId(upgradeRequestId);
+    hrc2.setStageId(nextStage.getStageId());
+    hrcDAO.merge(hrc2);
+
+    Resource failedTask = new ResourceImpl(Resource.Type.Task);
+    expect(m_upgradeHelper.getTaskResource(anyString(), anyLong(), anyLong(), anyLong())).andReturn(failedTask).anyTimes();
+    replay(m_upgradeHelper);
+
+    resources = upgradeSummaryResourceProvider.getResources(requestResource, p1And2);
+    assertEquals(1, resources.size());
+    r = resources.iterator().next();
+    assertEquals("Failed calling Restart ZOOKEEPER_SERVER on host h1", r.getPropertyValue(UpgradeSummaryResourceProvider.UPGRADE_SUMMARY_FAIL_REASON));
+  }
+
+  /**
+   * @param amc
+   * @return the provider
+   */
+  private UpgradeSummaryResourceProvider createProvider(AmbariManagementController amc) {
+    return new UpgradeSummaryResourceProvider(amc);
+  }
+
+  /**
+   * Mock module that will bind UpgradeHelper to a mock instance.
+   */
+  private class MockModule implements Module {
+    @Override
+    public void configure(Binder binder) {
+      binder.bind(UpgradeHelper.class).toInstance(m_upgradeHelper);
+    }
+  }
+}
\ No newline at end of file