/** * 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.events.listeners.alerts; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.EagerSingleton; import org.apache.ambari.server.events.AggregateAlertRecalculateEvent; import org.apache.ambari.server.events.AlertReceivedEvent; import org.apache.ambari.server.events.AlertStateChangeEvent; import org.apache.ambari.server.events.InitialAlertEvent; import org.apache.ambari.server.events.publishers.AlertEventPublisher; import org.apache.ambari.server.orm.dao.AlertSummaryDTO; import org.apache.ambari.server.orm.dao.AlertsDAO; import org.apache.ambari.server.orm.entities.AlertCurrentEntity; import org.apache.ambari.server.state.Alert; import org.apache.ambari.server.state.AlertFirmness; import org.apache.ambari.server.state.AlertState; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.alert.AggregateDefinitionMapping; import org.apache.ambari.server.state.alert.AggregateSource; import org.apache.ambari.server.state.alert.AlertDefinition; import org.apache.ambari.server.state.alert.Reporting; import org.apache.ambari.server.state.alert.SourceType; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; /** * The {@link AlertAggregateListener} is used to listen for all incoming * {@link AlertStateChangeEvent} instances and determine if there exists a * {@link SourceType#AGGREGATE} alert that needs to run. * <p/> * This listener is only needed on state changes as aggregation of alerts is * only performed against the state of an alert and not the values that * contributed to that state. However, this listener should only be concerned * with {@link AlertFirmness#HARD} events as they represent a true change in the * state of an alert. Calculations should never be performed on * {@link AlertFirmness#SOFT} alerts since they may be false positives. */ @Singleton @EagerSingleton public class AlertAggregateListener { /** * Logger. */ private final static Logger LOG = LoggerFactory.getLogger(AlertAggregateListener.class); @Inject private AlertsDAO m_alertsDao = null; /** * Used for looking cluster name by cluster id */ @Inject Provider<Clusters> m_clusters; /** * The event publisher used to receive incoming events and publish new events * when an aggregate alert is run. */ private final AlertEventPublisher m_publisher; /** * A cache used to store the last state and text of an aggregate alert. We * shouldn't need to fire new aggregate alerts unless the state or text has * changed. */ private Map<String, Alert> m_alertCache = new ConcurrentHashMap<>(); /** * Used for quick lookups of aggregate alerts. */ @Inject private AggregateDefinitionMapping m_aggregateMapping; @Inject public AlertAggregateListener(AlertEventPublisher publisher) { m_publisher = publisher; m_publisher.register(this); } /** * Consumes an {@link InitialAlertEvent}. */ @Subscribe public void onInitialAlertEvent(InitialAlertEvent event) { LOG.debug("Received event {}", event); onAlertEvent(event.getClusterId(), event.getAlert().getName()); } /** * Consumes an {@link AlertStateChangeEvent}. */ @Subscribe public void onAlertStateChangeEvent(AlertStateChangeEvent event) { LOG.debug("Received event {}", event); // do not recalculate on SOFT events AlertCurrentEntity currentEntity = event.getCurrentAlert(); if (currentEntity.getFirmness() == AlertFirmness.SOFT) { return; } onAlertEvent(event.getClusterId(), event.getAlert().getName()); } /** * Consumes an {@link AggregateAlertRecalculateEvent}. When a component is * removed, there may be alerts that were removed which have aggregate alerts * associated with this. This will ensure that all aggregates recalculate. It * can also be used at any point to recalculate all of the aggregates for a * cluster. * */ @Subscribe public void onAlertStateChangeEvent(AggregateAlertRecalculateEvent event) { LOG.debug("Received event {}", event); List<String> alertNames = m_aggregateMapping.getAlertsWithAggregates(event.getClusterId()); for (String alertName : alertNames) { onAlertEvent(event.getClusterId(), alertName); } } /** * Calculates the aggregate alert state if there is an aggregate alert for the * specified alert. * <p/> * This method should not be decoratd with {@link AllowConcurrentEvents} since * it would need extra locking around {@link #m_alertCache}. * * @param clusterId * the ID of the cluster. * @param alertName * the name of the alert to use when looking up the aggregate. */ private void onAlertEvent(long clusterId, String alertName) { AlertDefinition aggregateDefinition = m_aggregateMapping.getAggregateDefinition(clusterId, alertName); if (null == aggregateDefinition || null == m_alertsDao) { return; } AggregateSource aggregateSource = (AggregateSource) aggregateDefinition.getSource(); AlertSummaryDTO summary = m_alertsDao.findAggregateCounts(clusterId, aggregateSource.getAlertName()); // OK should be based off of true OKs and those in maintenance mode int okCount = summary.getOkCount() + summary.getMaintenanceCount(); int warningCount = summary.getWarningCount(); int criticalCount = summary.getCriticalCount(); int unknownCount = summary.getUnknownCount(); int totalCount = okCount + warningCount + criticalCount + unknownCount; Alert aggregateAlert = new Alert(aggregateDefinition.getName(), null, aggregateDefinition.getServiceName(), null, null, AlertState.UNKNOWN); aggregateAlert.setLabel(aggregateDefinition.getLabel()); aggregateAlert.setTimestamp(System.currentTimeMillis()); try { aggregateAlert.setCluster(m_clusters.get().getClusterById(clusterId).getClusterName()); } catch (AmbariException exception) { LOG.error("Unable to lookup cluster with ID {}", clusterId, exception); } if (0 == totalCount) { aggregateAlert.setText("There are no instances of the aggregated alert."); } else if (summary.getUnknownCount() > 0) { aggregateAlert.setText("There are alerts with a state of UNKNOWN."); } else { Reporting reporting = aggregateSource.getReporting(); int numerator = summary.getCriticalCount() + summary.getWarningCount(); int denominator = totalCount; double value = ((double) (numerator) / denominator); if(Reporting.ReportingType.PERCENT.equals(reporting.getType())) { value *= 100; } if (value >= reporting.getCritical().getValue()) { aggregateAlert.setState(AlertState.CRITICAL); aggregateAlert.setText(MessageFormat.format( reporting.getCritical().getText(), denominator, numerator)); } else if (value >= reporting.getWarning().getValue()) { aggregateAlert.setState(AlertState.WARNING); aggregateAlert.setText(MessageFormat.format( reporting.getWarning().getText(), denominator, numerator)); } else { aggregateAlert.setState(AlertState.OK); aggregateAlert.setText(MessageFormat.format( reporting.getOk().getText(), denominator, numerator)); } } // now that the alert has been created, see if we need to send it; only send // alerts if the state or the text has changed boolean sendAlertEvent = true; Alert cachedAlert = m_alertCache.get(aggregateAlert.getName()); if (null != cachedAlert) { AlertState cachedState = cachedAlert.getState(); AlertState alertState = aggregateAlert.getState(); String cachedText = cachedAlert.getText(); String alertText = aggregateAlert.getText(); if (cachedState == alertState && StringUtils.equals(cachedText, alertText)) { sendAlertEvent = false; } } // update the cache m_alertCache.put(aggregateAlert.getName(), aggregateAlert); // make a new event and allow others to consume it, but only if the // aggregate has changed if (sendAlertEvent) { AlertReceivedEvent aggEvent = new AlertReceivedEvent(clusterId, aggregateAlert); m_publisher.publish(aggEvent); } } }