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 <[email protected]> Authored: Tue Nov 11 16:36:27 2014 -0500 Committer: Jonathan Hurley <[email protected]> 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);
