/** * 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.apache.ambari.server.state.MaintenanceState; 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, * "maintenance_count" : 1, * "original_timestamp": 1415372992337, * "latest_text" : "TCP Connection Failure" * }, * "OK": { * "count": 1, * "maintenance_count" : 0, * "original_timestamp": 1415372992337, * "latest_text" : "TCP OK" * }, * "UNKNOWN": { * "count": 0, * "maintenance_count" : 0, * "original_timestamp": 0 * }, * "WARN": { * "count": 0, * "maintenance_count" : 0, * "original_timestamp": 0 * } * } * }, * { * "definition_id" : 2, * "definition_name" : "namenode_process", * "summary" : { * "CRITICAL": { * "count": 1, * "maintenance_count" : 0, * "original_timestamp": 1415372992337 * }, * "OK": { * "count": 1, * "maintenance_count" : 0, * "original_timestamp": 1415372992337 * }, * "UNKNOWN": { * "count": 0, * "maintenance_count" : 0, * "original_timestamp": 0 * }, * "WARN": { * "count": 0, * "maintenance_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<>(); // 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_DEFINITION_ID); String definitionName = (String) resource.getPropertyValue(AlertResourceProvider.ALERT_DEFINITION_NAME); AlertState state = (AlertState) resource.getPropertyValue(AlertResourceProvider.ALERT_STATE); Long originalTimestampObject = (Long) resource.getPropertyValue(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP); MaintenanceState maintenanceState = (MaintenanceState) resource.getPropertyValue(AlertResourceProvider.ALERT_MAINTENANCE_STATE); String alertText = (String) resource.getPropertyValue(AlertResourceProvider.ALERT_TEXT); // NPE sanity if (null == state) { state = AlertState.UNKNOWN; } // NPE sanity long originalTimestamp = 0; if (null != originalTimestampObject) { originalTimestamp = originalTimestampObject.longValue(); } // NPE sanity boolean isMaintenanceModeEnabled = false; if (null != maintenanceState && maintenanceState != MaintenanceState.OFF) { isMaintenanceModeEnabled = true; } // 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 final AlertStateValues alertStateValues; switch (state) { case CRITICAL: { alertStateValues = groupSummaryInfo.State.Critical; break; } case OK: { alertStateValues = groupSummaryInfo.State.Ok; break; } case WARNING: { alertStateValues = groupSummaryInfo.State.Warning; break; } default: case UNKNOWN: { alertStateValues = groupSummaryInfo.State.Unknown; break; } } // update the maintenance count if in MM is enabled, otherwise the // regular count if (isMaintenanceModeEnabled) { alertStateValues.MaintenanceCount++; } else { alertStateValues.Count++; } // check to see if this alerts time is sooner; if so, keep track of it // and of its text if (originalTimestamp > alertStateValues.Timestamp) { alertStateValues.Timestamp = originalTimestamp; alertStateValues.AlertText = alertText; } } Set<Entry<String, AlertDefinitionSummary>> entrySet = summaries.entrySet(); List<AlertDefinitionSummary> groupedResources = new ArrayList<>( 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_DEFINITION_NAME}. */ @Override protected void addRequiredAlertProperties(Set<String> properties) { super.addRequiredAlertProperties(properties); properties.add(AlertResourceProvider.ALERT_ID); properties.add(AlertResourceProvider.ALERT_DEFINITION_NAME); properties.add(AlertResourceProvider.ALERT_MAINTENANCE_STATE); properties.add(AlertResourceProvider.ALERT_TEXT); } /** * 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. */ public final static class AlertDefinitionSummary { @JsonProperty(value = "definition_id") public long Id; @JsonProperty(value = "definition_name") public String Name; @JsonProperty(value = "summary") public final AlertStateSummary State = new AlertStateSummary(); } }