/** * 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.state.services; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import org.apache.ambari.server.AmbariService; import org.apache.ambari.server.api.services.AmbariMetaInfo; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.events.AlertEvent; import org.apache.ambari.server.notifications.DispatchCallback; import org.apache.ambari.server.notifications.DispatchCredentials; import org.apache.ambari.server.notifications.DispatchFactory; import org.apache.ambari.server.notifications.DispatchRunnable; import org.apache.ambari.server.notifications.Notification; import org.apache.ambari.server.notifications.NotificationDispatcher; import org.apache.ambari.server.notifications.Recipient; import org.apache.ambari.server.orm.dao.AlertDispatchDAO; import org.apache.ambari.server.orm.entities.AlertDefinitionEntity; 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.AlertState; import org.apache.ambari.server.state.NotificationState; import org.apache.ambari.server.state.alert.AlertNotification; import org.apache.commons.io.IOUtils; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.util.concurrent.AbstractScheduledService; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; import com.google.inject.Provider; /** * The {@link AlertNoticeDispatchService} is used to scan the database for * {@link AlertNoticeEntity} that are in the {@link NotificationState#PENDING} * state. It will then process them through the dispatch system. * <p/> * The dispatch system will then make a callback to * {@link AlertNoticeDispatchCallback} so that the {@link NotificationState} can * be updated to its final value. * <p/> * This class uses the templates that are defined via * {@link Configuration#getAlertTemplateFile()} or the fallback internal * template {@code alert-templates.xml}. These files are parsed during * {@link #startUp()}. If there is a problem parsing them, the service will * still startup normally, producing an error in logs. It will fall back to * simple string concatenation for {@link Notification} content in this case. */ @AmbariService public class AlertNoticeDispatchService extends AbstractScheduledService { /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(AlertNoticeDispatchService.class); /** * The log tag to pass to Apache Velocity during rendering. */ private static final String VELOCITY_LOG_TAG = "ambari-alerts"; /** * The internal Ambari templates that ship. */ private static final String AMBARI_ALERT_TEMPLATES = "alert-templates.xml"; /** * The property containing the dispatch authentication username. */ public static final String AMBARI_DISPATCH_CREDENTIAL_USERNAME = "ambari.dispatch.credential.username"; /** * The property containing the dispatch authentication password. */ public static final String AMBARI_DISPATCH_CREDENTIAL_PASSWORD = "ambari.dispatch.credential.password"; /** * The property containing the dispatch recipients */ public static final String AMBARI_DISPATCH_RECIPIENTS = "ambari.dispatch.recipients"; /** * The context key for Ambari information to be passed to Velocity. */ private static final String VELOCITY_AMBARI_KEY = "ambari"; /** * The context key for alert summary information to be passed to Velocity. */ private static final String VELOCITY_SUMMARY_KEY = "summary"; /** * The context key for a single alert's information to be passed to Velocity. */ private static final String VELOCITY_ALERT_KEY = "alert"; /** * The context key for dispatch target information to be passed to Velocity. */ private static final String VELOCITY_DISPATCH_KEY = "dispatch"; /** * Gson used to convert JSON properties to a map. */ private final Gson m_gson; /** * Dispatch DAO to query pending {@link AlertNoticeEntity} instances from. */ @Inject private AlertDispatchDAO m_dao; /** * The factory used to get an {@link NotificationDispatcher} instance to * submit to the {@link #m_executor}. */ @Inject private DispatchFactory m_dispatchFactory; /** * The alert templates to use when rendering content for a * {@link Notification}. */ private AlertTemplates m_alertTemplates; /** * The configuration instance to get Ambari properties. */ @Inject private Configuration m_configuration; /** * Ambari meta information used fro alert {@link Notification}s. */ @Inject private Provider<AmbariMetaInfo> m_metaInfo; /** * The executor responsible for dispatching. */ private Executor m_executor; /** * Constructor. */ public AlertNoticeDispatchService() { m_executor = new ThreadPoolExecutor(0, 2, 5L, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(), new AlertDispatchThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(AlertTargetProperties.class, new AlertTargetPropertyDeserializer()); m_gson = gsonBuilder.create(); } /** * {@inheritDoc} * <p/> * Parse the XML template for {@link Notification} content. If there is a * problem parsing the content, the service will still startup normally but * the {@link Notification} content will fallback to plaintext. */ @Override protected void startUp() throws Exception { super.startUp(); InputStream inputStream = null; String alertTemplatesFile = null; try { alertTemplatesFile = m_configuration.getAlertTemplateFile(); if (null != alertTemplatesFile) { File file = new File(alertTemplatesFile); inputStream = new FileInputStream(file); } } catch (Exception exception) { LOG.warn("Unable to load alert template file {}", alertTemplatesFile, exception); } try { JAXBContext context = JAXBContext.newInstance(AlertTemplates.class); Unmarshaller unmarshaller = context.createUnmarshaller(); // if the file provided via the configuration is not available, use // the internal one if (null == inputStream) { inputStream = ClassLoader.getSystemResourceAsStream(AMBARI_ALERT_TEMPLATES); } m_alertTemplates = (AlertTemplates) unmarshaller.unmarshal(inputStream); } catch (Exception exception) { LOG.error( "Unable to load alert template file {}, outbound notifications will not be formatted", AMBARI_ALERT_TEMPLATES, exception); } finally { if (null != inputStream) { IOUtils.closeQuietly(inputStream); } } } /** * Sets the {@link Executor} to use when dispatching {@link Notification}s. * This should only be used by unit tests to provide a mock executor. * * @param executor * the executor to use (not {@code null). */ protected void setExecutor(Executor executor) { m_executor = executor; } /** * {@inheritDoc} */ @Override protected void runOneIteration() throws Exception { List<AlertNoticeEntity> pending = m_dao.findPendingNotices(); if (pending.size() == 0) { return; } LOG.info("There are {} pending alert notices about to be dispatched...", pending.size()); Map<AlertTargetEntity, List<AlertNoticeEntity>> aggregateMap = new HashMap<>(pending.size()); // combine all histories by target for (AlertNoticeEntity notice : pending) { AlertTargetEntity target = notice.getAlertTarget(); List<AlertNoticeEntity> notices = aggregateMap.get(target); if (null == notices) { notices = new ArrayList<>(); aggregateMap.put(target, notices); } // at this point, notices have been processed but not yet delivered notice.setNotifyState(NotificationState.DISPATCHED); notice = m_dao.merge(notice); notices.add(notice); } // now that all of the notices are grouped by target, dispatch them Set<AlertTargetEntity> targets = aggregateMap.keySet(); for (AlertTargetEntity target : targets) { List<AlertNoticeEntity> notices = aggregateMap.get(target); if (null == notices || notices.size() == 0) { continue; } String targetType = target.getNotificationType(); NotificationDispatcher dispatcher = m_dispatchFactory.getDispatcher(targetType); // create a single digest notification if supported if (dispatcher.isDigestSupported()) { AlertNotification notification = buildNotificationFromTarget(target); notification.CallbackIds = new ArrayList<>(notices.size()); List<AlertHistoryEntity> histories = new ArrayList<>( notices.size()); // add callback IDs so that the notices can be marked as DELIVERED or // FAILED, and create a list of just the alert histories for (AlertNoticeEntity notice : notices) { AlertHistoryEntity history = notice.getAlertHistory(); histories.add(history); notification.CallbackIds.add(notice.getUuid()); } // populate the subject and body fields; if there is a problem // generating the content, then mark the notices as FAILED try { renderDigestNotificationContent(dispatcher, notification, histories, target); // dispatch DispatchRunnable runnable = new DispatchRunnable(dispatcher, notification); m_executor.execute(runnable); } catch (Exception exception) { LOG.error("Unable to create notification for alerts", exception); // there was a problem generating content for the target; mark all // notices as FAILED and skip this target // mark these as failed notification.Callback.onFailure(notification.CallbackIds); } } else { // the dispatcher does not support digest, each notice must have a 1:1 // notification created for it for (AlertNoticeEntity notice : notices) { AlertNotification notification = buildNotificationFromTarget(target); AlertHistoryEntity history = notice.getAlertHistory(); notification.CallbackIds = Collections.singletonList(notice.getUuid()); // populate the subject and body fields; if there is a problem // generating the content, then mark the notices as FAILED try { renderNotificationContent(dispatcher, notification, history, target); // dispatch DispatchRunnable runnable = new DispatchRunnable(dispatcher, notification); m_executor.execute(runnable); } catch (Exception exception) { LOG.error("Unable to create notification for alert", exception); // mark these as failed notification.Callback.onFailure(notification.CallbackIds); } } } } } /** * {@inheritDoc} * <p/> * Returns a schedule that starts after 2 minute and runs every 2 minutes * after {@link #runOneIteration()} completes. */ @Override protected Scheduler scheduler() { return Scheduler.newFixedDelaySchedule(2, 2, TimeUnit.MINUTES); } /** * Initializes a {@link Notification} instance from an * {@link AlertTargetEntity}. This method does most of the boilerplate work to * get a {@link Notification} that is almost ready to send. * <p/> * The {@link Notification} will not have any of the callback IDs or content * set. * * @param target * the alert target * @return the initialized notification */ private AlertNotification buildNotificationFromTarget(AlertTargetEntity target) { String propertiesJson = target.getProperties(); AlertTargetProperties targetProperties = m_gson.fromJson(propertiesJson, AlertTargetProperties.class); Map<String, String> properties = targetProperties.Properties; // create an initialize the notification AlertNotification notification = new AlertNotification(); notification.Callback = new AlertNoticeDispatchCallback(); notification.DispatchProperties = properties; // set dispatch credentials if (properties.containsKey(AMBARI_DISPATCH_CREDENTIAL_USERNAME) && properties.containsKey(AMBARI_DISPATCH_CREDENTIAL_PASSWORD)) { DispatchCredentials credentials = new DispatchCredentials(); credentials.UserName = properties.get(AMBARI_DISPATCH_CREDENTIAL_USERNAME); credentials.Password = properties.get(AMBARI_DISPATCH_CREDENTIAL_PASSWORD); notification.Credentials = credentials; } // create recipients if (null != targetProperties.Recipients) { List<Recipient> recipients = new ArrayList<>( targetProperties.Recipients.size()); for (String stringRecipient : targetProperties.Recipients) { Recipient recipient = new Recipient(); recipient.Identifier = stringRecipient; recipients.add(recipient); } notification.Recipients = recipients; } return notification; } /** * Generates digest content for the {@link Notification} using the * {@link #m_alertTemplates} and the list of alerts passed in. If there is a * problem with the templates, this will fallback to non-formatted content. * * @param dispatcher * the dispatcher for this notification type (not {@code null}). * @param notification * the notification (not {@code null}). * @param histories * the alerts to generate the content from (not {@code null}. * @param target * the target of the {@link Notification}. */ private void renderDigestNotificationContent(NotificationDispatcher dispatcher, AlertNotification notification, List<AlertHistoryEntity> histories, AlertTargetEntity target) throws IOException { String targetType = target.getNotificationType(); // build the velocity objects for template rendering AmbariInfo ambari = new AmbariInfo(m_metaInfo.get(), m_configuration); AlertSummaryInfo summary = new AlertSummaryInfo(histories); DispatchInfo dispatch = new DispatchInfo(target); // get the template for this target type final Writer subjectWriter = new StringWriter(); final Writer bodyWriter = new StringWriter(); final AlertTemplate template = m_alertTemplates.getTemplate(targetType); if (dispatcher.isNotificationContentGenerationRequired()) { if (null != template) { // create the velocity context for template rendering VelocityContext velocityContext = new VelocityContext(); velocityContext.put(VELOCITY_AMBARI_KEY, ambari); velocityContext.put(VELOCITY_SUMMARY_KEY, summary); velocityContext.put(VELOCITY_DISPATCH_KEY, dispatch); // render the template and assign the content to the notification String subjectTemplate = template.getSubject(); String bodyTemplate = template.getBody(); // render the subject Velocity.evaluate(velocityContext, subjectWriter, VELOCITY_LOG_TAG, subjectTemplate); // render the body Velocity.evaluate(velocityContext, bodyWriter, VELOCITY_LOG_TAG, bodyTemplate); } else { // a null template is possible from parsing incorrectly or not // having the correct type defined for the target for (AlertHistoryEntity alert : histories) { subjectWriter.write("Apache Ambari Alert Summary"); bodyWriter.write(alert.getAlertState().name()); bodyWriter.write(" "); bodyWriter.write(alert.getAlertDefinition().getLabel()); bodyWriter.write(" "); bodyWriter.write(alert.getAlertText()); bodyWriter.write("\n"); } } } notification.Subject = subjectWriter.toString(); notification.Body = bodyWriter.toString(); } /** * Generates the content for the {@link Notification} using the * {@link #m_alertTemplates} and the single alert passed in. If there is a * problem with the templates, this will fallback to non-formatted content. * * @param dispatcher * the dispatcher for this notification type (not {@code null}). * @param notification * the notification (not {@code null}). * @param history * the alert to generate the content from (not {@code null}. * @param target * the target of the {@link Notification}. */ private void renderNotificationContent(NotificationDispatcher dispatcher, AlertNotification notification, AlertHistoryEntity history, AlertTargetEntity target) throws IOException { String targetType = target.getNotificationType(); // build the velocity objects for template rendering AmbariInfo ambari = new AmbariInfo(m_metaInfo.get(), m_configuration); AlertInfo alert = new AlertInfo(history); DispatchInfo dispatch = new DispatchInfo(target); // set the alert info on the notification subclass so that dispatchers // can use it directly notification.setAlertInfo(alert); // get the template for this target type final Writer subjectWriter = new StringWriter(); final Writer bodyWriter = new StringWriter(); final AlertTemplate template = m_alertTemplates.getTemplate(targetType); if (dispatcher.isNotificationContentGenerationRequired()) { if (null != template) { // create the velocity context for template rendering VelocityContext velocityContext = new VelocityContext(); velocityContext.put(VELOCITY_AMBARI_KEY, ambari); velocityContext.put(VELOCITY_ALERT_KEY, alert); velocityContext.put(VELOCITY_DISPATCH_KEY, dispatch); // render the template and assign the content to the notification String subjectTemplate = template.getSubject(); String bodyTemplate = template.getBody(); // render the subject Velocity.evaluate(velocityContext, subjectWriter, VELOCITY_LOG_TAG, subjectTemplate); // render the body Velocity.evaluate(velocityContext, bodyWriter, VELOCITY_LOG_TAG, bodyTemplate); } else { // a null template is possible from parsing incorrectly or not // having the correct type defined for the target subjectWriter.write(alert.getAlertState().name()); subjectWriter.write(" "); subjectWriter.write(alert.getAlertName()); bodyWriter.write(alert.getAlertState().name()); bodyWriter.write(" "); bodyWriter.write(alert.getAlertName()); bodyWriter.write(" "); bodyWriter.write(alert.getAlertText()); if (alert.hasHostName()) { bodyWriter.write(" "); bodyWriter.append(alert.getHostName()); } bodyWriter.write("\n"); } } notification.Subject = subjectWriter.toString(); notification.Body = bodyWriter.toString(); } /** * The {@link AlertTargetProperties} separates out the dispatcher properties * from the list of recipients which is a JSON array and not a String. */ private static final class AlertTargetProperties { /** * The properties to pass to the concrete dispatcher. */ public Map<String, String> Properties; /** * The recipients of the notice. */ public List<String> Recipients; } /** * The {@link AlertTargetPropertyDeserializer} is used to dump the majority of * JSON serialized properties into a {@link Map} of {@link String} while at * the same time, converting * {@link AlertNoticeDispatchService#AMBARI_DISPATCH_RECIPIENTS} into a list. */ private static final class AlertTargetPropertyDeserializer implements JsonDeserializer<AlertTargetProperties> { /** * {@inheritDoc} */ @Override public AlertTargetProperties deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { AlertTargetProperties properties = new AlertTargetProperties(); properties.Properties = new HashMap<>(); final JsonObject jsonObject = json.getAsJsonObject(); Set<Entry<String, JsonElement>> entrySet = jsonObject.entrySet(); for (Entry<String, JsonElement> entry : entrySet) { String entryKey = entry.getKey(); JsonElement entryValue = entry.getValue(); if (entryKey.equals(AMBARI_DISPATCH_RECIPIENTS)) { Type listType = new TypeToken<List<String>>() { }.getType(); JsonArray jsonArray = entryValue.getAsJsonArray(); properties.Recipients = context.deserialize(jsonArray, listType); } else { properties.Properties.put(entryKey, entryValue.getAsString()); } } return properties; } } /** * A custom {@link ThreadFactory} for the threads that will handle dispatching * {@link AlertNoticeEntity} instances. Threads created will have slightly * reduced priority since {@link AlertEvent} instances are not critical to the * system. */ private static final class AlertDispatchThreadFactory implements ThreadFactory { private static final AtomicInteger s_threadIdPool = new AtomicInteger(1); /** * {@inheritDoc} */ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "alert-dispatch-" + s_threadIdPool.getAndIncrement()); thread.setDaemon(false); thread.setPriority(Thread.NORM_PRIORITY - 1); return thread; } } /** * The {@link AlertNoticeDispatchCallback} is used to receive a callback from * the dispatch framework and then update the {@link AlertNoticeEntity} * {@link NotificationState}. */ private final class AlertNoticeDispatchCallback implements DispatchCallback { /** * {@inheritDoc} */ @Override public void onSuccess(List<String> callbackIds) { for (String callbackId : callbackIds) { updateAlertNotice(callbackId, NotificationState.DELIVERED); } } /** * {@inheritDoc} */ @Override public void onFailure(List<String> callbackIds) { for (String callbackId : callbackIds) { updateAlertNotice(callbackId, NotificationState.FAILED); } } /** * Updates the {@link AlertNoticeEntity} matching the given UUID with the * specified state. * * @param uuid * @param state */ private void updateAlertNotice(String uuid, NotificationState state) { try { AlertNoticeEntity entity = m_dao.findNoticeByUuid(uuid); if (null == entity) { LOG.warn("Unable to find an alert notice with UUID {}", uuid); return; } entity.setNotifyState(state); m_dao.merge(entity); } catch (Exception exception) { LOG.error( "Unable to update the alert notice with UUID {} to {}, notifications will continue to be sent", uuid, state, exception); } } } /** * The {@link AlertInfo} class encapsulates all information about a single * alert for a single outbound {@link Notification}. */ public final static class AlertInfo { private final AlertHistoryEntity m_history; /** * Constructor. * * @param history */ public AlertInfo(AlertHistoryEntity history) { m_history = history; } /** * Gets the host name or {@code null} if none. * * @return */ public String getHostName() { return m_history.getHostName(); } /** * Gets whether there is a host associated with the alert. Some alerts like * aggregate alerts don't have hosts. * * @return */ public boolean hasHostName() { return m_history.getHostName() != null; } /** * Gets the service name or {@code null} if none. * * @return */ public String getServiceName() { return m_history.getServiceName(); } /** * Gets the service component name, or {@code null} if none. * * @return */ public String getComponentName() { return m_history.getComponentName(); } /** * Gets whether there is a component associated with an alert. Some alerts * don't have an associated component. * * @return */ public boolean hasComponentName() { return m_history.getComponentName() != null; } /** * Gets the time that the alert was received * @return */ public long getAlertTimestamp() { return m_history.getAlertTimestamp(); } /** * Gets the state of the alert. * * @return */ public AlertState getAlertState() { return m_history.getAlertState(); } /** * Gets the definition id of the alert. * * @return */ public Long getAlertDefinitionId() { return m_history.getAlertDefinitionId(); } /** * Gets the hash of alert definition entity. * * @return */ public int getAlertDefinitionHash() { return m_history.getAlertDefinitionHash(); } /** * Gets the descriptive name of the alert. * * @return */ public String getAlertName() { return m_history.getAlertDefinition().getLabel(); } /** * Gets the alert definition for this alert. * * @return the alert definition. */ public AlertDefinitionEntity getAlertDefinition() { return m_history.getAlertDefinition(); } /** * Gets the text of the alert. * * @return */ public String getAlertText() { return m_history.getAlertText(); } } /** * The {@link AlertSummaryInfo} class encapsulates all of the alert * information for the {@link Notification}. This includes customized * structures to better organize information about each of the services, * hosts, and alert states. */ public final static class AlertSummaryInfo { private int m_okCount = 0; private int m_warningCount = 0; private int m_criticalCount = 0; private int m_unknownCount = 0; /** * The hosts that have at least 1 alert reported. */ private final Set<String> m_hosts = new HashSet<>(); /** * The services that have at least 1 alert reported. */ private final Set<String> m_services = new HashSet<>(); /** * All of the alerts for the {@link Notification}. */ private final List<AlertHistoryEntity> m_alerts; /** * A mapping of service to alerts where the alerts are also grouped by state * for that service. */ private final Map<String, Map<AlertState, List<AlertHistoryEntity>>> m_alertsByServiceAndState = new HashMap<>(); /** * A mapping of all services by state. */ private final Map<String, Set<String>> m_servicesByState = new HashMap<>(); /** * A mapping of all alerts by the service that owns them. */ private final Map<String, List<AlertHistoryEntity>> m_alertsByService = new HashMap<>(); /** * Constructor. * * @param histories */ protected AlertSummaryInfo(List<AlertHistoryEntity> histories) { m_alerts = histories; // group all alerts by their service and severity for (AlertHistoryEntity history : m_alerts) { AlertState alertState = history.getAlertState(); String serviceName = history.getServiceName(); String hostName = history.getHostName(); if (null != hostName) { m_hosts.add(hostName); } if (null != serviceName) { m_services.add(serviceName); } // group alerts by service name & state Map<AlertState, List<AlertHistoryEntity>> service = m_alertsByServiceAndState.get(serviceName); if (null == service) { service = new HashMap<>(); m_alertsByServiceAndState.put(serviceName, service); } List<AlertHistoryEntity> alertList = service.get(alertState); if (null == alertList) { alertList = new ArrayList<>(); service.put(alertState, alertList); } alertList.add(history); // group services by alert states Set<String> services = m_servicesByState.get(alertState.name()); if (null == services) { services = new HashSet<>(); m_servicesByState.put(alertState.name(), services); } services.add(serviceName); // group alerts by service List<AlertHistoryEntity> alertsByService = m_alertsByService.get(serviceName); if (null == alertsByService) { alertsByService = new ArrayList<>(); m_alertsByService.put(serviceName, alertsByService); } alertsByService.add(history); // keep track of totals switch (alertState) { case CRITICAL: m_criticalCount++; break; case OK: m_okCount++; break; case UNKNOWN: m_unknownCount++; break; case WARNING: m_warningCount++; break; default: m_unknownCount++; break; } } } /** * Gets the total number of OK alerts in the {@link Notification}. * * @return the OK count. */ public int getOkCount() { return m_okCount; } /** * Gets the total number of WARNING alerts in the {@link Notification}. * * @return the WARNING count. */ public int getWarningCount() { return m_warningCount; } /** * Gets the total number of CRITICAL alerts in the {@link Notification}. * * @return the CRITICAL count. */ public int getCriticalCount() { return m_criticalCount; } /** * Gets the total number of UNKNOWN alerts in the {@link Notification}. * * @return the UNKNOWN count. */ public int getUnknownCount() { return m_unknownCount; } /** * Gets the total count of all alerts in the {@link Notification} * * @return the total count of all alerts. */ public int getTotalCount() { return m_okCount + m_warningCount + m_criticalCount + m_unknownCount; } /** * Gets all of the services that have alerts being reporting in this * notification dispatch. * * @return the list of services. */ public Set<String> getServices() { return m_services; } /** * Gets all of the alerts in the {@link Notification}. * * @return all of the alerts. */ public List<AlertHistoryEntity> getAlerts() { return m_alerts; } /** * Gets all of the alerts in the {@link Notification} by service name. * * @param serviceName * the service name. * @return the alerts for that service, or {@code null} none. */ public List<AlertHistoryEntity> getAlerts(String serviceName) { return m_alertsByService.get(serviceName); } /** * Gets all of the alerts for a given service and alert state level. * * @param serviceName * the name of the service. * @param alertState * the alert state level. * @return the list of alerts or {@code null} for none. */ public List<AlertHistoryEntity> getAlerts(String serviceName, String alertState) { Map<AlertState, List<AlertHistoryEntity>> serviceAlerts = m_alertsByServiceAndState.get(serviceName); if (null == serviceAlerts) { return null; } AlertState state = AlertState.valueOf(alertState); return serviceAlerts.get(state); } /** * Gets a list of services that have an alert being reporting for the given * state. * * @param alertState * the state to get the services for. * @return the services or {@code null} if none. */ public Set<String> getServicesByAlertState(String alertState) { return m_servicesByState.get(alertState); } } /** * The {@link AmbariInfo} class is used to provide the template engine with * information about the Ambari installation. */ public final static class AmbariInfo { private String m_hostName = null; private String m_url = null; private String m_version = null; /** * Constructor. * * @param metaInfo */ protected AmbariInfo(AmbariMetaInfo metaInfo, Configuration m_configuration) { m_url = m_configuration.getAmbariDisplayUrl(); m_version = metaInfo.getServerVersion(); } /** * @return the hostName */ public String getHostName() { return m_hostName; } public boolean hasUrl() { return m_url != null; } /** * @return the url */ public String getUrl() { return m_url; } /** * Gets the Ambari server version. * * @return the version */ public String getServerVersion() { return m_version; } } /** * The {@link DispatchInfo} class is used to provide the template engine with * information about the intended target of the notification. */ public static final class DispatchInfo { private String m_targetName; private String m_targetDescription; /** * Constructor. * * @param target * the {@link AlertTargetEntity} receiving the notification. */ protected DispatchInfo(AlertTargetEntity target) { m_targetName = target.getTargetName(); m_targetDescription = target.getDescription(); } /** * Gets the name of the notification target. * * @return the name of the target. */ public String getTargetName() { return m_targetName; } /** * Gets the description of the notification target. * * @return the target description. */ public String getTargetDescription() { return m_targetDescription; } } /** * The {@link AlertTemplates} class represnts the {@link AlertTemplates} that * have been loaded, either by the {@link Configuration} or by the backup * {@code alert-templates.xml} file. */ @XmlRootElement(name = "alert-templates") private final static class AlertTemplates { /** * The alert templates defined. */ @XmlElement(name = "alert-template", required = true) private List<AlertTemplate> m_templates; /** * Gets the alert template given the specified template type. * * @param type * the template type. * @return the template, or {@code null} if none. * @see AlertTargetEntity#getNotificationType() */ public AlertTemplate getTemplate(String type) { for (AlertTemplate template : m_templates) { if (type.equals(template.getType())) { return template; } } return null; } } /** * The {@link AlertTemplate} class represents a template for a specified alert * target type that can be used when creating the content for dispatching * {@link Notification}s. */ private final static class AlertTemplate { /** * The type that this template is for. * * @see AlertTargetEntity#getNotificationType() */ @XmlAttribute(name = "type", required = true) private String m_type; /** * The subject template for the {@link Notification}. */ @XmlElement(name = "subject", required = true) private String m_subject; /** * The body template for the {@link Notification}. */ @XmlElement(name = "body", required = true) private String m_body; /** * Gets the template type. * * @return the template type. */ public String getType() { return m_type; } /** * Gets the subject template. * * @return the subject template. */ public String getSubject() { return m_subject; } /** * Gets the body template. * * @return the body template. */ public String getBody() { return m_body; } } }