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/12 15:33:46 UTC

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

Repository: ambari
Updated Branches:
  refs/heads/trunk 76c27f200 -> 212adff46


AMBARI-8289 - Alerts: Provide Grouped 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/212adff4
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/212adff4
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/212adff4

Branch: refs/heads/trunk
Commit: 212adff46f4bfc64837fbaa6dcfccf9d7bc2f948
Parents: 76c27f2
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Tue Nov 11 16:36:27 2014 -0500
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Wed Nov 12 09:33:27 2014 -0500

----------------------------------------------------------------------
 .../render/AlertSummaryGroupedRenderer.java     | 270 +++++++++++++++++++
 .../api/query/render/AlertSummaryRenderer.java  |  32 ++-
 .../api/resources/AlertResourceDefinition.java  |   9 +-
 .../internal/AlertResourceProvider.java         |   4 +-
 .../internal/AlertResourceProviderTest.java     |  55 ++++
 5 files changed, 357 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/212adff4/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryGroupedRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryGroupedRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryGroupedRenderer.java
new file mode 100644
index 0000000..a7309f1
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/AlertSummaryGroupedRenderer.java
@@ -0,0 +1,270 @@
+/**
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+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.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;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * The {@link AlertSummaryGroupedRenderer} is used to format the results of
+ * queries to the alerts endpoint. Each alert instance returned from the backend
+ * is grouped by its alert definition and its state is then aggregated into the
+ * summary information for that definition.
+ * <p/>
+ * The finalized structure is:
+ *
+ * <pre>
+ * {
+ *   "alerts_summary_grouped" : [
+ *     {
+ *       "definition_id" : 1,
+ *       "definition_name" : "datanode_process",
+ *       "summary" : {
+ *         "CRITICAL": {
+ *           "count": 1,
+ *           "original_timestamp": 1415372992337
+ *         },
+ *         "OK": {
+ *           "count": 1,
+ *           "original_timestamp": 1415372992337
+ *         },
+ *         "UNKNOWN": {
+ *           "count": 0,
+ *           "original_timestamp": 0
+ *         },
+ *        "WARN": {
+ *          "count": 0,
+ *          "original_timestamp": 0
+ *         }
+ *       }
+ *     },
+ *     {
+ *       "definition_id" : 2,
+ *       "definition_name" : "namenode_process",
+ *       "summary" : {
+ *         "CRITICAL": {
+ *           "count": 1,
+ *           "original_timestamp": 1415372992337
+ *         },
+ *         "OK": {
+ *           "count": 1,
+ *           "original_timestamp": 1415372992337
+ *         },
+ *         "UNKNOWN": {
+ *           "count": 0,
+ *           "original_timestamp": 0
+ *         },
+ *        "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 AlertSummaryGroupedRenderer extends AlertSummaryRenderer {
+
+  private static final String ALERTS_SUMMARY_GROUP = "alerts_summary_grouped";
+
+  /**
+   * {@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();
+    Map<String, AlertDefinitionSummary> summaries = new HashMap<String, AlertDefinitionSummary>();
+
+    // iterate over all returned flattened alerts and build the summary info
+    for (TreeNode<Resource> node : resultTree.getChildren()) {
+      Resource resource = node.getObject();
+
+      Long definitionId = (Long) resource.getPropertyValue(AlertResourceProvider.ALERT_ID);
+      String definitionName = (String) resource.getPropertyValue(AlertResourceProvider.ALERT_NAME);
+      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();
+      }
+
+      // create the group summary info if it doesn't exist yet
+      AlertDefinitionSummary groupSummaryInfo = summaries.get(definitionName);
+      if (null == groupSummaryInfo) {
+        groupSummaryInfo = new AlertDefinitionSummary();
+        groupSummaryInfo.Id = definitionId;
+        groupSummaryInfo.Name = definitionName;
+
+        summaries.put(definitionName, groupSummaryInfo);
+      }
+
+      // set and increment the correct values based on state
+      switch (state) {
+        case CRITICAL: {
+          groupSummaryInfo.State.Critical.Count++;
+
+          if (originalTimestamp > groupSummaryInfo.State.Critical.Timestamp) {
+            groupSummaryInfo.State.Critical.Timestamp = originalTimestamp;
+          }
+
+          break;
+        }
+        case OK: {
+          groupSummaryInfo.State.Ok.Count++;
+
+          if (originalTimestamp > groupSummaryInfo.State.Ok.Timestamp) {
+            groupSummaryInfo.State.Ok.Timestamp = originalTimestamp;
+          }
+
+          break;
+        }
+        case WARNING: {
+          groupSummaryInfo.State.Warning.Count++;
+
+          if (originalTimestamp > groupSummaryInfo.State.Warning.Timestamp) {
+            groupSummaryInfo.State.Warning.Timestamp = originalTimestamp;
+          }
+
+          break;
+        }
+        default:
+        case UNKNOWN: {
+          groupSummaryInfo.State.Unknown.Count++;
+
+          if (originalTimestamp > groupSummaryInfo.State.Unknown.Timestamp) {
+            groupSummaryInfo.State.Unknown.Timestamp = originalTimestamp;
+          }
+
+          break;
+        }
+      }
+    }
+
+    Set<Entry<String, AlertDefinitionSummary>> entrySet = summaries.entrySet();
+    List<AlertDefinitionSummary> groupedResources = new ArrayList<AlertDefinitionSummary>(
+        entrySet.size());
+
+    // iterate over all summary groups, adding them to the final list
+    for (Entry<String, AlertDefinitionSummary> entry : entrySet) {
+      groupedResources.add(entry.getValue());
+    }
+
+    Result groupedSummary = new ResultImpl(true);
+    TreeNode<Resource> summaryTree = groupedSummary.getResultTree();
+
+    Resource resource = new ResourceImpl(Resource.Type.Alert);
+    summaryTree.addChild(resource, ALERTS_SUMMARY_GROUP);
+
+    resource.setProperty(ALERTS_SUMMARY_GROUP, groupedResources);
+    return groupedSummary;
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p/>
+   * Additionally adds {@link AlertResourceProvider#ALERT_ID} and
+   * {@link AlertResourceProvider#ALERT_NAME}.
+   */
+  @Override
+  protected void addRequiredAlertProperties(Set<String> properties) {
+    super.addRequiredAlertProperties(properties);
+
+    properties.add(AlertResourceProvider.ALERT_ID);
+    properties.add(AlertResourceProvider.ALERT_NAME);
+  }
+
+  /**
+   * The {@link AlertDefinitionSummary} is a simple data structure for keeping
+   * track of each alert definition's summary information as the result set is
+   * being iterated over.
+   */
+  private final static class AlertDefinitionSummary {
+    @JsonProperty(value = "definition_id")
+    private long Id;
+
+    @JsonProperty(value = "definition_name")
+    private String Name;
+
+    @JsonProperty(value = "summary")
+    private final AlertStateSummary State = new AlertStateSummary();
+  }
+
+  /**
+   * The {@link AlertStateSummary} class holds information about each possible
+   * alert state.
+   */
+  private final static class AlertStateSummary {
+    @JsonProperty(value = "OK")
+    private final AlertStateValues Ok = new AlertStateValues();
+
+    @JsonProperty(value = "WARNING")
+    private final AlertStateValues Warning = new AlertStateValues();
+
+    @JsonProperty(value = "CRITICAL")
+    private final AlertStateValues Critical = new AlertStateValues();
+
+    @JsonProperty(value = "UNKNOWN")
+    private final AlertStateValues Unknown = new AlertStateValues();
+  }
+
+  /**
+   * The {@link AlertStateValues} class holds various information about an alert
+   * state, such as the number of instances of that state and the most recent
+   * timestamp.
+   */
+  private final static class AlertStateValues {
+    @JsonProperty(value = "count")
+    private int Count = 0;
+
+    @JsonProperty(value = "original_timestamp")
+    private long Timestamp = 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/212adff4/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
index afe9798..48098e1 100644
--- 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
@@ -74,17 +74,17 @@ import org.apache.ambari.server.state.AlertState;
  */
 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";
+  protected static final String OK_COUNT_PROPERTY = "alerts_summary/OK/count";
+  protected 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";
+  protected static final String WARN_COUNT_PROPERTY = "alerts_summary/WARNING/count";
+  protected 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";
+  protected static final String CRITICAL_COUNT_PROPERTY = "alerts_summary/CRITICAL/count";
+  protected 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";
+  protected static final String UNKNOWN_COUNT_PROPERTY = "alerts_summary/UNKNOWN/count";
+  protected static final String UNKNOWN_TIMESTAMP_PROPERTY = "alerts_summary/UNKNOWN/original_timestamp";
 
   /**
    * {@inheritDoc}
@@ -110,8 +110,7 @@ public class AlertSummaryRenderer extends BaseRenderer implements Renderer {
     // 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);
+    addRequiredAlertProperties(properties);
 
     return resultTree;
   }
@@ -224,4 +223,17 @@ public class AlertSummaryRenderer extends BaseRenderer implements Renderer {
 
     return summary;
   }
+
+  /**
+   * Adds properties to the backend request that are required by this renderer.
+   * This method currently adds {@link AlertResourceProvider#ALERT_STATE} and
+   * {@link AlertResourceProvider#ALERT_ORIGINAL_TIMESTAMP}.
+   *
+   * @param properties
+   *          the properties collection to add to.
+   */
+  protected void addRequiredAlertProperties(Set<String> properties) {
+    properties.add(AlertResourceProvider.ALERT_STATE);
+    properties.add(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/212adff4/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 18f206e..483bd6a 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,6 +17,7 @@
  */
 package org.apache.ambari.server.api.resources;
 
+import org.apache.ambari.server.api.query.render.AlertSummaryGroupedRenderer;
 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;
@@ -55,8 +56,14 @@ public class AlertResourceDefinition extends BaseResourceDefinition {
    */
   @Override
   public Renderer getRenderer(String name) {
-    if (name != null && name.equals("summary")) {
+    if (null == name) {
+      return super.getRenderer(name);
+    }
+
+    if (name.equals("summary")) {
       return new AlertSummaryRenderer();
+    } else if (name.equals("groupedSummary")) {
+      return new AlertSummaryGroupedRenderer();
     } else {
       return super.getRenderer(name);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/212adff4/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 3430f8d..a4b2667 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
@@ -49,10 +49,10 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider {
 
   public static final String ALERT_STATE = "Alert/state";
   public static final String ALERT_ORIGINAL_TIMESTAMP = "Alert/original_timestamp";
+  public static final String ALERT_ID = "Alert/id";
+  public static final String ALERT_NAME = "Alert/name";
 
   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_INSTANCE = "Alert/instance";

http://git-wip-us.apache.org/repos/asf/ambari/blob/212adff4/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 2bac86a..eef05f6 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
@@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import javax.persistence.EntityManager;
 
+import org.apache.ambari.server.api.query.render.AlertSummaryGroupedRenderer;
 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;
@@ -252,6 +253,54 @@ public class AlertResourceProviderTest {
     Assert.assertEquals(3, unknownCount.intValue());
   }
 
+  /**
+   * Tests that the {@link AlertSummaryGroupedRenderer} correctly transforms the
+   * alert data.
+   *
+   * @throws Exception
+   */
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testGetClusterGroupedSummary() 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);
+
+    AlertSummaryGroupedRenderer renderer = new AlertSummaryGroupedRenderer();
+    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 groupedSummary = renderer.finalizeResult(result);
+    Assert.assertNotNull(groupedSummary);
+
+    // pull out the alerts_summary child set by the renderer
+    TreeNode<Resource> summaryResultTree = groupedSummary.getResultTree();
+    TreeNode<Resource> summaryResources = summaryResultTree.getChild("alerts_summary_grouped");
+
+    Resource summaryResource = summaryResources.getObject();
+    List<Object> summaryList = (List<Object>) summaryResource.getPropertyValue("alerts_summary_grouped");
+    Assert.assertEquals(4, summaryList.size());
+  }
+
   private AlertResourceProvider createProvider() {
     return new AlertResourceProvider(
         PropertyHelper.getPropertyIds(Resource.Type.Alert),
@@ -310,19 +359,23 @@ public class AlertResourceProviderTest {
       AlertState state = AlertState.OK;
       String service = "HDFS";
       String component = "NAMENODE";
+      String definitionName = "hdfs_namenode";
 
       if (i >= ok && i < ok + warning) {
         state = AlertState.WARNING;
         service = "YARN";
         component = "RESOURCEMANAGER";
+        definitionName = "yarn_resourcemanager";
       } else if (i >= ok + warning & i < ok + warning + critical) {
         state = AlertState.CRITICAL;
         service = "HIVE";
         component = "HIVE_SERVER";
+        definitionName = "hive_server";
       } else if (i >= ok + warning + critical) {
         state = AlertState.UNKNOWN;
         service = "FLUME";
         component = "FLUME_HANDLER";
+        definitionName = "flume_handler";
       }
 
       AlertCurrentEntity current = new AlertCurrentEntity();
@@ -343,6 +396,8 @@ public class AlertResourceProviderTest {
       history.setServiceName(service);
 
       AlertDefinitionEntity definition = new AlertDefinitionEntity();
+      definition.setDefinitionId(Long.valueOf(i));
+      definition.setDefinitionName(definitionName);
       history.setAlertDefinition(definition);
       current.setAlertHistory(history);
       currents.add(current);