/** * 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.util.LinkedList; import java.util.List; import java.util.Set; import java.util.UUID; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.EagerSingleton; import org.apache.ambari.server.controller.RootServiceResponseFactory.Services; import org.apache.ambari.server.events.AlertStateChangeEvent; import org.apache.ambari.server.events.publishers.AlertEventPublisher; import org.apache.ambari.server.orm.dao.AlertDispatchDAO; import org.apache.ambari.server.orm.entities.AlertCurrentEntity; import org.apache.ambari.server.orm.entities.AlertDefinitionEntity; import org.apache.ambari.server.orm.entities.AlertGroupEntity; import org.apache.ambari.server.orm.entities.AlertHistoryEntity; import org.apache.ambari.server.orm.entities.AlertNoticeEntity; import org.apache.ambari.server.orm.entities.AlertTargetEntity; 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.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.MaintenanceState; import org.apache.ambari.server.state.NotificationState; 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 AlertStateChangedListener} class response to * {@link AlertStateChangeEvent} and creates {@link AlertNoticeEntity} instances * in the database. * <p/> * {@link AlertNoticeEntity} instances will only be updated if the firmness of * the alert is {@link AlertFirmness#HARD}. In the case of {@link AlertState#OK} * (which is always {@link AlertFirmness#HARD}), then the prior alert must be * {@link AlertFirmness#HARD} for any notifications to be created. This is * because a SOFT non-OK alert (such as CRITICAL) would not have caused a * notification, so changing back from this SOFT state should not either. * <p/> * This class will not create {@link AlertNoticeEntity}s in the following cases: * <ul> * <li>If {@link AlertTargetEntity#isEnabled()} is {@code false} * <li>If the cluster is upgrading or the upgrade is suspended, only * {@link Services#AMBARI} alerts will be dispatched. * </ul> */ @Singleton @EagerSingleton public class AlertStateChangedListener { /** * Logger. */ private static Logger LOG = LoggerFactory.getLogger(AlertStateChangedListener.class); /** * A logger that is only for logging alert state changes so that there is an * audit trail in the event that definitions/history are removed. */ private static final Logger ALERT_LOG = LoggerFactory.getLogger("alerts"); /** * [CRITICAL] [HARD] [HDFS] [namenode_hdfs_blocks_health] (NameNode Blocks * Health) Total Blocks:[100], Missing Blocks:[6] */ private static final String ALERT_LOG_MESSAGE = "[{}] [{}] [{}] [{}] ({}) {}"; /** * Used for looking up groups and targets. */ @Inject private AlertDispatchDAO m_alertsDispatchDao; /** * Used to retrieve a cluster and check for upgrades in progress. */ @Inject private Provider<Clusters> m_clusters; /** * Constructor. * * @param publisher */ @Inject public AlertStateChangedListener(AlertEventPublisher publisher) { publisher.register(this); } /** * Listens for when an alert's state has changed and creates * {@link AlertNoticeEntity} instances when appropriate to notify * {@link AlertTargetEntity}. * <p/> * {@link AlertNoticeEntity} are only created when the target has the * {@link AlertState} in its list of states. */ @Subscribe @AllowConcurrentEvents public void onAlertEvent(AlertStateChangeEvent event) { LOG.debug("Received event {}", event); Alert alert = event.getAlert(); AlertCurrentEntity current = event.getCurrentAlert(); AlertHistoryEntity history = event.getNewHistoricalEntry(); AlertDefinitionEntity definition = history.getAlertDefinition(); // log to the alert audit log so there is physical record even if // definitions and historical enties are removed ALERT_LOG.info(ALERT_LOG_MESSAGE, alert.getState(), current.getFirmness(), definition.getServiceName(), definition.getDefinitionName(), definition.getLabel(), alert.getText()); // do nothing if the firmness is SOFT if (current.getFirmness() == AlertFirmness.SOFT) { return; } // OK alerts are always HARD, so we need to catch the case where we are // coming from a SOFT non-OK to an OK; in these cases we should not alert // // New State = OK // Old Firmness = SOFT if (history.getAlertState() == AlertState.OK && event.getFromFirmness() == AlertFirmness.SOFT) { return; } // don't create any outbound alert notices if in MM AlertCurrentEntity currentAlert = event.getCurrentAlert(); if (null != currentAlert && currentAlert.getMaintenanceState() != MaintenanceState.OFF) { return; } List<AlertGroupEntity> groups = m_alertsDispatchDao.findGroupsByDefinition(definition); List<AlertNoticeEntity> notices = new LinkedList<>(); // for each group, determine if there are any targets that need to receive // a notification about the alert state change event for (AlertGroupEntity group : groups) { Set<AlertTargetEntity> targets = group.getAlertTargets(); if (null == targets || targets.size() == 0) { continue; } for (AlertTargetEntity target : targets) { if (!canDispatch(target, history, definition)) { continue; } AlertNoticeEntity notice = new AlertNoticeEntity(); notice.setUuid(UUID.randomUUID().toString()); notice.setAlertTarget(target); notice.setAlertHistory(event.getNewHistoricalEntry()); notice.setNotifyState(NotificationState.PENDING); notices.add(notice); } } // create notices if there are any to create if (!notices.isEmpty()) { m_alertsDispatchDao.createNotices(notices); } } /** * Gets whether an {@link AlertNoticeEntity} should be created for the * {@link AlertHistoryEntity} and {@link AlertTargetEntity}. Reasons this * would be false include: * <ul> * <li>The target is disabled * <li>The target is not configured for the state of the alert * <li>The cluster is upgrading and the alert is cluster-related * </ul> * * @param target * the target (not {@code null}). * @param history * the history entry that represents the state change (not * {@code null}). * @return {@code true} if a notification should be dispatched for the target, * {@code false} otherwise. * @see AlertTargetEntity#isEnabled() */ private boolean canDispatch(AlertTargetEntity target, AlertHistoryEntity history, AlertDefinitionEntity definition) { // disable alert targets should be skipped if (!target.isEnabled()) { return false; } Set<AlertState> alertStates = target.getAlertStates(); if (null != alertStates && alertStates.size() > 0) { if (!alertStates.contains(history.getAlertState())) { return false; } } // check if in an upgrade Long clusterId = history.getClusterId(); try { Cluster cluster = m_clusters.get().getClusterById(clusterId); if (null != cluster.getUpgradeInProgress()) { // only send AMBARI alerts if in an upgrade String serviceName = definition.getServiceName(); if (!StringUtils.equals(serviceName, Services.AMBARI.name())) { LOG.debug( "Skipping alert notifications for {} because the cluster is upgrading", definition.getDefinitionName(), target); return false; } } } catch (AmbariException ambariException) { LOG.warn( "Unable to process an alert state change for cluster with ID {} because it does not exist", clusterId); return false; } return true; } }