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 2014/11/10 17:00:19 UTC

ambari git commit: AMBARI-8237 - Alerts: Provide Summary Structure On Alerts Endpoint (jonathanhurley)

Repository: ambari
Updated Branches:
  refs/heads/trunk de1f8ff64 -> f088f55e4


AMBARI-8237 - Alerts: Provide Summary Structure On Alerts Endpoint (jonathanhurley)


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

Branch: refs/heads/trunk
Commit: f088f55e4d9853ec0f671e1d095abb349d9d8a20
Parents: de1f8ff
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Sun Nov 9 09:08:15 2014 -0500
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Mon Nov 10 11:00:14 2014 -0500

----------------------------------------------------------------------
 .../api/query/render/AlertSummaryRenderer.java  | 227 +++++++++++++++++++
 .../api/resources/AlertResourceDefinition.java  |  31 ++-
 .../internal/AlertResourceProvider.java         |  42 ++--
 .../internal/AlertResourceProviderTest.java     | 176 +++++++++++---
 4 files changed, 426 insertions(+), 50 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/f088f55e/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryRenderer.java
new file mode 100644
index 0000000..afe9798
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryRenderer.java
@@ -0,0 +1,227 @@
+/**
+ * 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.query.render;
+
+import java.util.Set;
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultImpl;
+import org.apache.ambari.server.api.services.ResultPostProcessor;
+import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.api.util.TreeNodeImpl;
+import org.apache.ambari.server.controller.internal.AlertResourceProvider;
+import org.apache.ambari.server.controller.internal.ResourceImpl;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.state.AlertState;
+
+/**
+ * The {@link AlertSummaryRenderer} is used to format the results of queries to
+ * the alerts endpoint. Each item returned from the query represents an
+ * individual current alert which is then aggregated into a summary structure
+ * based on the alert state.
+ * <p/>
+ * The finalized structure is:
+ *
+ * <pre>
+ * {
+ *   "href" : "http://localhost:8080/api/v1/clusters/c1/alerts?format=summary",
+ *   "alerts_summary" : {
+ *     "CRITICAL" : {
+ *       "count" : 3,
+ *       "original_timestamp" : 1415372828182
+ *     },
+ *     "OK" : {
+ *       "count" : 37,
+ *       "original_timestamp" : 1415375364937
+ *     },
+ *     "UNKNOWN" : {
+ *       "count" : 1,
+ *       "original_timestamp" : 1415372632261
+ *     },
+ *     "WARN" : {
+ *       "count" : 0,
+ *       "original_timestamp" : 0
+ *     }
+ *   }
+ * }
+ * </pre>
+ * <p/>
+ * The nature of a {@link Renderer} is that it manipulates the dataset returned
+ * by a query. In the case of alert data, the query could potentially return
+ * thousands of results if there are thousands of nodes in the cluster. This
+ * could present a performance issue that can only be addressed by altering the
+ * incoming query and modifying it to instruct the backend to return a JPA SUM
+ * instead of a collection of entities.
+ */
+public class AlertSummaryRenderer extends BaseRenderer implements Renderer {
+
+  private static final String OK_COUNT_PROPERTY = "alerts_summary/OK/count";
+  private static final String OK_TIMESTAMP_PROPERTY = "alerts_summary/OK/original_timestamp";
+
+  private static final String WARN_COUNT_PROPERTY = "alerts_summary/WARNING/count";
+  private static final String WARN_TIMESTAMP_PROPERTY = "alerts_summary/WARNING/original_timestamp";
+
+  private static final String CRITICAL_COUNT_PROPERTY = "alerts_summary/CRITICAL/count";
+  private static final String CRITICAL_TIMESTAMP_PROPERTY = "alerts_summary/CRITICAL/original_timestamp";
+
+  private static final String UNKNOWN_COUNT_PROPERTY = "alerts_summary/UNKNOWN/count";
+  private static final String UNKNOWN_TIMESTAMP_PROPERTY = "alerts_summary/UNKNOWN/original_timestamp";
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public TreeNode<Set<String>> finalizeProperties(
+      TreeNode<QueryInfo> queryTree, boolean isCollection) {
+
+    QueryInfo queryInfo = queryTree.getObject();
+    TreeNode<Set<String>> resultTree = new TreeNodeImpl<Set<String>>(
+        null, queryInfo.getProperties(), queryTree.getName());
+
+    copyPropertiesToResult(queryTree, resultTree);
+
+    boolean addKeysToEmptyResource = true;
+    if (!isCollection && isRequestWithNoProperties(queryTree)) {
+      addSubResources(queryTree, resultTree);
+      addKeysToEmptyResource = false;
+    }
+
+    ensureRequiredProperties(resultTree, addKeysToEmptyResource);
+
+    // ensure that state and original_timestamp are on the request since these
+    // are required by the finalization process of this renderer
+    Set<String> properties = resultTree.getObject();
+    properties.add(AlertResourceProvider.ALERT_STATE);
+    properties.add(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
+
+    return resultTree;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ResultPostProcessor getResultPostProcessor(Request request) {
+    // simply return the native rendering
+    return new ResultPostProcessorImpl(request);
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p/>
+   * This will iterate over all of the nodes in the result tree and combine
+   * their {@link AlertResourceProvider#ALERT_STATE} into a single summary
+   * structure.
+   */
+  @Override
+  public Result finalizeResult(Result queryResult) {
+    TreeNode<Resource> resultTree = queryResult.getResultTree();
+    Result summary = new ResultImpl(true);
+
+    // counts
+    int ok = 0;
+    int warning = 0;
+    int critical = 0;
+    int unknown = 0;
+
+    // keeps track of the most recent state change
+    // (not the most recent alert received)
+    long mostRecentOK = 0;
+    long mostRecentWarning = 0;
+    long mostRecentCritical = 0;
+    long mostRecentUnknown = 0;
+
+    // iterate over all returned flattened alerts and build the summary info
+    for (TreeNode<Resource> node : resultTree.getChildren()) {
+      Resource resource = node.getObject();
+      AlertState state = (AlertState) resource.getPropertyValue(AlertResourceProvider.ALERT_STATE);
+      Long originalTimestampObject = (Long) resource.getPropertyValue(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
+
+      // NPE sanity
+      if (null == state) {
+        state = AlertState.UNKNOWN;
+      }
+
+      // NPE sanity
+      long originalTimestamp = 0;
+      if (null != originalTimestampObject) {
+        originalTimestamp = originalTimestampObject.longValue();
+      }
+
+      switch (state) {
+        case CRITICAL: {
+          critical++;
+
+          if (originalTimestamp > mostRecentCritical) {
+            mostRecentCritical = originalTimestamp;
+          }
+
+          break;
+        }
+        case OK: {
+          ok++;
+
+          if (originalTimestamp > mostRecentOK) {
+            mostRecentOK = originalTimestamp;
+          }
+
+          break;
+        }
+        case WARNING: {
+          warning++;
+
+          if (originalTimestamp > mostRecentWarning) {
+            mostRecentWarning = originalTimestamp;
+          }
+
+          break;
+        }
+        default:
+        case UNKNOWN: {
+          unknown++;
+
+          if (originalTimestamp > mostRecentUnknown) {
+            mostRecentUnknown = originalTimestamp;
+          }
+
+          break;
+        }
+      }
+    }
+
+    Resource resource = new ResourceImpl(Resource.Type.Alert);
+    resource.setProperty(OK_COUNT_PROPERTY, ok);
+    resource.setProperty(WARN_COUNT_PROPERTY, warning);
+    resource.setProperty(CRITICAL_COUNT_PROPERTY, critical);
+    resource.setProperty(UNKNOWN_COUNT_PROPERTY, unknown);
+
+    resource.setProperty(OK_TIMESTAMP_PROPERTY, mostRecentOK);
+    resource.setProperty(WARN_TIMESTAMP_PROPERTY, mostRecentWarning);
+    resource.setProperty(CRITICAL_TIMESTAMP_PROPERTY, mostRecentCritical);
+    resource.setProperty(UNKNOWN_TIMESTAMP_PROPERTY, mostRecentUnknown);
+
+    TreeNode<Resource> summaryTree = summary.getResultTree();
+    summaryTree.addChild(resource, "alerts_summary");
+
+    return summary;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f088f55e/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertResourceDefinition.java
index d7aca22..18f206e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertResourceDefinition.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertResourceDefinition.java
@@ -17,25 +17,48 @@
  */
 package org.apache.ambari.server.api.resources;
 
+import org.apache.ambari.server.api.query.render.AlertSummaryRenderer;
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.controller.spi.Resource;
 
 /**
  * Definition for alert resources.
  */
 public class AlertResourceDefinition extends BaseResourceDefinition {
-  
+
+  /**
+   * Constructor.
+   *
+   */
   public AlertResourceDefinition() {
     super(Resource.Type.Alert);
   }
-  
+
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public String getPluralName() {
     return "alerts";
   }
-  
+
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public String getSingularName() {
     return "alert";
   }
-  
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Renderer getRenderer(String name) {
+    if (name != null && name.equals("summary")) {
+      return new AlertSummaryRenderer();
+    } else {
+      return super.getRenderer(name);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f088f55e/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
index 715d017..3430f8d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
@@ -47,21 +47,22 @@ import com.google.inject.Injector;
  */
 public class AlertResourceProvider extends ReadOnlyResourceProvider {
 
+  public static final String ALERT_STATE = "Alert/state";
+  public static final String ALERT_ORIGINAL_TIMESTAMP = "Alert/original_timestamp";
+
   protected static final String ALERT_CLUSTER_NAME = "Alert/cluster_name";
   protected static final String ALERT_ID = "Alert/id";
   protected static final String ALERT_NAME = "Alert/name";
   protected static final String ALERT_LATEST_TIMESTAMP = "Alert/latest_timestamp";
   protected static final String ALERT_MAINTENANCE_STATE = "Alert/maintenance_state";
-  protected static final String ALERT_ORIGINAL_TIMESTAMP = "Alert/original_timestamp";
   protected static final String ALERT_INSTANCE = "Alert/instance";
   protected static final String ALERT_LABEL = "Alert/label";
-  protected static final String ALERT_STATE = "Alert/state";
   protected static final String ALERT_TEXT = "Alert/text";
   protected static final String ALERT_COMPONENT = "Alert/component_name";
   protected static final String ALERT_HOST = "Alert/host_name";
   protected static final String ALERT_SERVICE = "Alert/service_name";
   protected static final String ALERT_SCOPE = "Alert/scope";
-  
+
   private static Set<String> pkPropertyIds = new HashSet<String>(
       Arrays.asList(ALERT_ID, ALERT_NAME));
 
@@ -78,7 +79,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
   AlertResourceProvider(Set<String> propertyIds,
       Map<Resource.Type, String> keyPropertyIds,
       AmbariManagementController managementController) {
-    
+
     super(propertyIds, keyPropertyIds, managementController);
   }
 
@@ -94,11 +95,11 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
       NoSuchResourceException, NoSuchParentResourceException {
 
     Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
-    
+
     Set<Resource> results = new HashSet<Resource>();
 
     for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
-      
+
       String clusterName = (String) propertyMap.get(ALERT_CLUSTER_NAME);
 
       if (null == clusterName || clusterName.isEmpty()) {
@@ -108,25 +109,25 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
       String id = (String) propertyMap.get(ALERT_ID);
       if (null != id) {
         AlertCurrentEntity entity = alertsDAO.findCurrentById(Long.parseLong(id));
-        
+
         if (null != entity) {
           results.add(toResource(false, clusterName, entity, requestPropertyIds));
         }
-        
+
       } else {
         Cluster cluster = null;
-        
+
         try {
           cluster = getManagementController().getClusters().getCluster(clusterName);
         } catch (AmbariException e) {
           throw new NoSuchResourceException("Parent Cluster resource doesn't exist", e);
         }
-        
+
         String serviceName = (String) propertyMap.get(ALERT_SERVICE);
         String hostName = (String) propertyMap.get(ALERT_HOST);
-        
+
         List<AlertCurrentEntity> entities = null;
-        
+
         if (null != hostName) {
           entities = alertsDAO.findCurrentByHost(cluster.getClusterId(),
               hostName);
@@ -134,12 +135,13 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
           entities = alertsDAO.findCurrentByService(cluster.getClusterId(),
               serviceName);
         } else {
-          entities = alertsDAO.findCurrentByCluster(cluster.getClusterId());          
+          entities = alertsDAO.findCurrentByCluster(cluster.getClusterId());
         }
-        
-        if (null == entities)
+
+        if (null == entities) {
           entities = Collections.emptyList();
-        
+        }
+
         for (AlertCurrentEntity entity : entities) {
           results.add(toResource(true, clusterName, entity, requestPropertyIds));
         }
@@ -151,7 +153,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
 
   /**
    * Converts an entity to a resource.
-   * 
+   *
    * @param isCollection {@code true} if the response is for a collection
    * @param clusterName the cluster name
    * @param entity the entity
@@ -168,7 +170,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
     setResourceProperty(resource, ALERT_MAINTENANCE_STATE, entity.getMaintenanceState(), requestedIds);
     setResourceProperty(resource, ALERT_ORIGINAL_TIMESTAMP, entity.getOriginalTimestamp(), requestedIds);
     setResourceProperty(resource, ALERT_TEXT, entity.getLatestText(), requestedIds);
-    
+
     AlertHistoryEntity history = entity.getAlertHistory();
     setResourceProperty(resource, ALERT_INSTANCE, history.getAlertInstance(), requestedIds);
     setResourceProperty(resource, ALERT_LABEL, history.getAlertLabel(), requestedIds);
@@ -176,11 +178,11 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
     setResourceProperty(resource, ALERT_COMPONENT, history.getComponentName(), requestedIds);
     setResourceProperty(resource, ALERT_HOST, history.getHostName(), requestedIds);
     setResourceProperty(resource, ALERT_SERVICE, history.getServiceName(), requestedIds);
-    
+
     AlertDefinitionEntity definition = history.getAlertDefinition();
     setResourceProperty(resource, ALERT_NAME, definition.getDefinitionName(), requestedIds);
     setResourceProperty(resource, ALERT_SCOPE, definition.getScope(), requestedIds);
-    
+
     if (isCollection) {
       // !!! want name to be populated as if it were a PK when requesting the collection
       resource.setProperty(ALERT_NAME, definition.getDefinitionName());

http://git-wip-us.apache.org/repos/asf/ambari/blob/f088f55e/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertResourceProviderTest.java
index ef014a9..2bac86a 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertResourceProviderTest.java
@@ -27,12 +27,19 @@ import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.persistence.EntityManager;
 
+import org.apache.ambari.server.api.query.render.AlertSummaryRenderer;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultImpl;
+import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -51,6 +58,7 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -77,7 +85,7 @@ public class AlertResourceProviderTest {
   private AmbariManagementController m_amc;
 
   @Before
-  @SuppressWarnings("boxing")  
+  @SuppressWarnings("boxing")
   public void before() throws Exception {
     Injector m_injector = Guice.createInjector(new MockModule());
 
@@ -91,9 +99,9 @@ public class AlertResourceProviderTest {
     expect(cluster.getClusterId()).andReturn(Long.valueOf(1L));
 
     replay(m_amc, clusters, cluster);
-    
+
     m_dao = m_injector.getInstance(AlertsDAO.class);
-    
+
     AlertResourceProvider.init(m_injector);
   }
 
@@ -105,17 +113,17 @@ public class AlertResourceProviderTest {
   public void testGetCluster() throws Exception {
     expect(m_dao.findCurrentByCluster(
         captureLong(new Capture<Long>()))).andReturn(getClusterMockEntities()).anyTimes();
-    
+
     replay(m_dao);
 
     Request request = PropertyHelper.getReadRequest(
         AlertResourceProvider.ALERT_ID,
         AlertResourceProvider.ALERT_NAME,
         AlertResourceProvider.ALERT_LABEL);
-    
+
     Predicate predicate = new PredicateBuilder().property(
         AlertResourceProvider.ALERT_CLUSTER_NAME).equals("c1").toPredicate();
-    
+
     AlertResourceProvider provider = createProvider();
     Set<Resource> results = provider.getResources(request, predicate);
 
@@ -123,10 +131,10 @@ public class AlertResourceProviderTest {
 
     Resource r = results.iterator().next();
     assertEquals("c1", r.getPropertyValue(AlertResourceProvider.ALERT_CLUSTER_NAME));
-    
+
     verify(m_dao);
   }
-  
+
   /**
    * Test for service
    */
@@ -134,18 +142,18 @@ public class AlertResourceProviderTest {
   public void testGetService() throws Exception {
     expect(m_dao.findCurrentByService(captureLong(new Capture<Long>()),
         capture(new Capture<String>()))).andReturn(getClusterMockEntities()).anyTimes();
-    
+
     replay(m_dao);
 
     Request request = PropertyHelper.getReadRequest(
         AlertResourceProvider.ALERT_ID,
         AlertResourceProvider.ALERT_NAME,
         AlertResourceProvider.ALERT_LABEL);
-    
+
     Predicate predicate = new PredicateBuilder().property(
         AlertResourceProvider.ALERT_CLUSTER_NAME).equals("c1").and()
         .property(AlertResourceProvider.ALERT_SERVICE).equals(ALERT_VALUE_SERVICE).toPredicate();
-    
+
     AlertResourceProvider provider = createProvider();
     Set<Resource> results = provider.getResources(request, predicate);
 
@@ -154,9 +162,9 @@ public class AlertResourceProviderTest {
     Resource r = results.iterator().next();
     assertEquals("c1", r.getPropertyValue(AlertResourceProvider.ALERT_CLUSTER_NAME));
     assertEquals(ALERT_VALUE_SERVICE, r.getPropertyValue(AlertResourceProvider.ALERT_SERVICE));
-    
+
     verify(m_dao);
-  }  
+  }
 
   /**
    * Test for service
@@ -165,18 +173,18 @@ public class AlertResourceProviderTest {
   public void testGetHost() throws Exception {
     expect(m_dao.findCurrentByHost(captureLong(new Capture<Long>()),
         capture(new Capture<String>()))).andReturn(getClusterMockEntities()).anyTimes();
-    
+
     replay(m_dao);
 
     Request request = PropertyHelper.getReadRequest(
         AlertResourceProvider.ALERT_ID,
         AlertResourceProvider.ALERT_NAME,
         AlertResourceProvider.ALERT_LABEL);
-    
+
     Predicate predicate = new PredicateBuilder().property(
         AlertResourceProvider.ALERT_CLUSTER_NAME).equals("c1").and()
         .property(AlertResourceProvider.ALERT_HOST).equals(ALERT_VALUE_HOSTNAME).toPredicate();
-    
+
     AlertResourceProvider provider = createProvider();
     Set<Resource> results = provider.getResources(request, predicate);
 
@@ -185,12 +193,65 @@ public class AlertResourceProviderTest {
     Resource r = results.iterator().next();
     assertEquals("c1", r.getPropertyValue(AlertResourceProvider.ALERT_CLUSTER_NAME));
     assertEquals(ALERT_VALUE_HOSTNAME, r.getPropertyValue(AlertResourceProvider.ALERT_HOST));
-    
+
     verify(m_dao);
-  }  
+  }
+
+  /**
+   * Tests that the {@link AlertSummaryRenderer} correctly transforms the alert
+   * data.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testGetClusterSummary() throws Exception {
+    expect(m_dao.findCurrentByCluster(captureLong(new Capture<Long>()))).andReturn(
+        getMockEntitiesManyStates()).anyTimes();
+
+    replay(m_dao);
+
+    Request request = PropertyHelper.getReadRequest(
+        AlertResourceProvider.ALERT_ID, AlertResourceProvider.ALERT_NAME,
+        AlertResourceProvider.ALERT_LABEL, AlertResourceProvider.ALERT_STATE,
+        AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
+
+    Predicate predicate = new PredicateBuilder().property(
+        AlertResourceProvider.ALERT_CLUSTER_NAME).equals("c1").toPredicate();
+
+    AlertResourceProvider provider = createProvider();
+    Set<Resource> results = provider.getResources(request, predicate);
+
+    verify(m_dao);
+
+    AlertSummaryRenderer renderer = new AlertSummaryRenderer();
+    ResultImpl result = new ResultImpl(true);
+    TreeNode<Resource> resources = result.getResultTree();
+
+    AtomicInteger alertResourceId = new AtomicInteger(1);
+    for (Resource resource : results) {
+      resources.addChild(resource, "Alert " + alertResourceId.getAndIncrement());
+    }
+
+    Result summary = renderer.finalizeResult(result);
+    Assert.assertNotNull(summary);
+
+    // pull out the alerts_summary child set by the renderer
+    TreeNode<Resource> summaryResultTree = summary.getResultTree();
+    TreeNode<Resource> summaryResources = summaryResultTree.getChild("alerts_summary");
+
+    Resource summaryResource = summaryResources.getObject();
+
+    Integer okCount = (Integer) summaryResource.getPropertyValue("alerts_summary/OK/count");
+    Integer warningCount = (Integer) summaryResource.getPropertyValue("alerts_summary/WARNING/count");
+    Integer criticalCount = (Integer) summaryResource.getPropertyValue("alerts_summary/CRITICAL/count");
+    Integer unknownCount = (Integer) summaryResource.getPropertyValue("alerts_summary/UNKNOWN/count");
+
+    Assert.assertEquals(10, okCount.intValue());
+    Assert.assertEquals(2, warningCount.intValue());
+    Assert.assertEquals(1, criticalCount.intValue());
+    Assert.assertEquals(3, unknownCount.intValue());
+  }
 
-  
-  
   private AlertResourceProvider createProvider() {
     return new AlertResourceProvider(
         PropertyHelper.getPropertyIds(Resource.Type.Alert),
@@ -206,7 +267,7 @@ public class AlertResourceProviderTest {
     current.setAlertId(Long.valueOf(1000L));
     current.setLatestTimestamp(Long.valueOf(1L));
     current.setOriginalTimestamp(Long.valueOf(2L));
-    
+
     AlertHistoryEntity history = new AlertHistoryEntity();
     history.setAlertId(ALERT_VALUE_ID);
     history.setAlertInstance(null);
@@ -218,15 +279,78 @@ public class AlertResourceProviderTest {
     history.setComponentName(ALERT_VALUE_COMPONENT);
     history.setHostName(ALERT_VALUE_HOSTNAME);
     history.setServiceName(ALERT_VALUE_SERVICE);
-    
+
     AlertDefinitionEntity definition = new AlertDefinitionEntity();
-    
+
     history.setAlertDefinition(definition);
     current.setAlertHistory(history);
-    
+
     return Arrays.asList(current);
   }
 
+  /**
+   * Gets a bunch of alerts with various values for state and timestamp.
+   *
+   * @return
+   */
+  private List<AlertCurrentEntity> getMockEntitiesManyStates() throws Exception {
+    // yesterday
+    AtomicLong timestamp = new AtomicLong(System.currentTimeMillis() - 86400000);
+    AtomicLong alertId = new AtomicLong(1);
+
+    int ok = 10;
+    int warning = 2;
+    int critical = 1;
+    int unknown = 3;
+    int total = ok + warning + critical + unknown;
+
+    List<AlertCurrentEntity> currents = new ArrayList<AlertCurrentEntity>(total);
+
+    for (int i = 0; i < total; i++) {
+      AlertState state = AlertState.OK;
+      String service = "HDFS";
+      String component = "NAMENODE";
+
+      if (i >= ok && i < ok + warning) {
+        state = AlertState.WARNING;
+        service = "YARN";
+        component = "RESOURCEMANAGER";
+      } else if (i >= ok + warning & i < ok + warning + critical) {
+        state = AlertState.CRITICAL;
+        service = "HIVE";
+        component = "HIVE_SERVER";
+      } else if (i >= ok + warning + critical) {
+        state = AlertState.UNKNOWN;
+        service = "FLUME";
+        component = "FLUME_HANDLER";
+      }
+
+      AlertCurrentEntity current = new AlertCurrentEntity();
+      current.setAlertId(alertId.getAndIncrement());
+      current.setOriginalTimestamp(timestamp.getAndAdd(10000));
+      current.setLatestTimestamp(timestamp.getAndAdd(10000));
+
+      AlertHistoryEntity history = new AlertHistoryEntity();
+      history.setAlertId(alertId.getAndIncrement());
+      history.setAlertInstance(null);
+      history.setAlertLabel(ALERT_VALUE_LABEL);
+      history.setAlertState(state);
+      history.setAlertText(ALERT_VALUE_TEXT);
+      history.setAlertTimestamp(current.getOriginalTimestamp());
+      history.setClusterId(Long.valueOf(1L));
+      history.setComponentName(component);
+      history.setHostName(ALERT_VALUE_HOSTNAME);
+      history.setServiceName(service);
+
+      AlertDefinitionEntity definition = new AlertDefinitionEntity();
+      history.setAlertDefinition(definition);
+      current.setAlertHistory(history);
+      currents.add(current);
+    }
+
+    return currents;
+  }
+
 
   /**
   *
@@ -238,10 +362,10 @@ public class AlertResourceProviderTest {
       binder.bind(AlertsDAO.class).toInstance(EasyMock.createMock(AlertsDAO.class));
       binder.bind(AmbariManagementController.class).toInstance(createMock(AmbariManagementController.class));
       binder.bind(DBAccessor.class).to(DBAccessorImpl.class);
-      
+
       Clusters clusters = EasyMock.createNiceMock(Clusters.class);
       Configuration configuration = EasyMock.createMock(Configuration.class);
-      
+
       binder.bind(Clusters.class).toInstance(clusters);
       binder.bind(Configuration.class).toInstance(configuration);