/*****************************************************************************
*
* Copyright (C) Zenoss, Inc. 2010-2012, all rights reserved.
*
* This content is made available according to terms specified in
* License.zenoss under the directory where your Zenoss product is installed.
*
****************************************************************************/
package org.zenoss.zep.impl;
import com.google.common.base.Splitter;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyException;
import org.python.core.PyFunction;
import org.python.core.PyInteger;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PySyntaxError;
import org.python.util.PythonInterpreter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.zenoss.amqp.AmqpConnectionManager;
import org.zenoss.amqp.AmqpException;
import org.zenoss.amqp.ExchangeConfiguration;
import org.zenoss.amqp.ZenossQueueConfig;
import org.zenoss.protobufs.model.Model.ModelElementType;
import org.zenoss.protobufs.zep.Zep.Event;
import org.zenoss.protobufs.zep.Zep.EventActor;
import org.zenoss.protobufs.zep.Zep.EventDetail;
import org.zenoss.protobufs.zep.Zep.EventStatus;
import org.zenoss.protobufs.zep.Zep.EventSummary;
import org.zenoss.protobufs.zep.Zep.EventTrigger;
import org.zenoss.protobufs.zep.Zep.EventTriggerSubscription;
import org.zenoss.protobufs.zep.Zep.Signal;
import org.zenoss.zep.UUIDGenerator;
import org.zenoss.zep.ZepException;
import org.zenoss.zep.ZepUtils;
import org.zenoss.zep.dao.EventSignalSpool;
import org.zenoss.zep.dao.EventSignalSpoolDao;
import org.zenoss.zep.dao.EventStoreDao;
import org.zenoss.zep.dao.EventSummaryDao;
import org.zenoss.zep.dao.EventTriggerDao;
import org.zenoss.zep.dao.EventTriggerSubscriptionDao;
import org.zenoss.zep.plugins.EventPostIndexContext;
import org.zenoss.zep.plugins.EventPostIndexPlugin;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static org.zenoss.zep.ZepConstants.*;
/**
* Post processing plug-in used to determine if NEW or CLEARED events match a specified trigger. If the
* trigger doesn't match, no signal is sent. If the trigger matches and doesn't specify a delay or repeat
* interval, then it is sent immediately. If the trigger matches and specifies a delay, then a signal is
* sent only if the event is still in NEW state after the delay. If the trigger specifies a repeat
* interval, then after the initial signal is sent, the event is checked again after the repeat interval. If
* the event is still in NEW state after the repeat interval, then another signal is sent and the check is
* repeated again at the repeat interval.
*
* If an event which previously triggered a signal is cleared by another event, a final signal is sent
* with the clear attribute set to true.
*/
public class TriggerPlugin extends EventPostIndexPlugin {
private static final Logger logger = LoggerFactory.getLogger(TriggerPlugin.class);
private EventTriggerDao triggerDao;
private EventSignalSpoolDao signalSpoolDao;
private EventStoreDao eventStoreDao;
private EventSummaryDao eventSummaryDao;
private EventTriggerSubscriptionDao eventTriggerSubscriptionDao;
private UUIDGenerator uuidGenerator;
private AmqpConnectionManager connectionManager;
private ExchangeConfiguration destinationExchange;
// The default value is specified in zep-config.xml.
// Can be overridden by specifying plugin.TriggerPlugin.triggerRuleCacheSize
// in the zeneventserver.conf file.
private int triggerRuleCacheSize;
/**
* Caches the result of compiling a trigger rule. Contains the original rule source, and the compiled PyFunction
* from the source. The PyFunction can be null if the rule source is invalid and can't be compiled to valid
* Python. It is cached no matter what to prevent trying to compile an invalid rule over and over again.
*/
static final class TriggerRuleCache {
private final String ruleSource;
private final PyFunction pyFunction;
public TriggerRuleCache(String ruleSource, PyFunction pyFunction) {
this.ruleSource = ruleSource;
this.pyFunction = pyFunction;
}
public String getRuleSource() {
return ruleSource;
}
public PyFunction getPyFunction() {
return pyFunction;
}
@Override
public String toString() {
return "TriggerRuleCache{" +
"ruleSource='" + ruleSource + '\'' +
'}';
}
}
// Map of Trigger UUID -> TriggerRuleCache.
protected Map<String, TriggerRuleCache> triggerRuleCache;
// The maximum amount of time to wait between processing the signal spool.
private static final long MAXIMUM_DELAY_MS = TimeUnit.SECONDS.toMillis(60);
private TaskScheduler scheduler;
private ScheduledFuture<?> spoolFuture;
PythonHelper pythonHelper = new PythonHelper();
/**
* Helper class to enable lazy-initialization of Jython. Initializing the runtime and compiling code is expensive
* to perform on each startup - better to do it when we first need it for evaluating triggers.
*/
static final class PythonHelper {
private volatile boolean initialized = false;
private PythonInterpreter python;
private PyFunction toObject;
private synchronized void initialize() {
if (initialized) {
return;
}
logger.info("Initializing Jython");
this.initialized = true;
PythonInterpreter.initialize(System.getProperties(), new Properties(), new String[0]);
this.python = new PythonInterpreter();
// define basic infrastructure class for evaluating rules
this.python.exec(
"class DictAsObj(object):\n" +
" def __init__(self, **kwargs):\n" +
" for k,v in kwargs.iteritems(): setattr(self,k,v)");
// expose to Java a Python dict->DictAsObj conversion function
this.toObject = (PyFunction)this.python.eval("lambda dd : DictAsObj(**dd)");
logger.info("Completed Jython initialization");
}
public PythonInterpreter getPythonInterpreter() {
if (!initialized) {
initialize();
}
return this.python;
}
public PyFunction getToObject() {
if (!initialized) {
initialize();
}
return this.toObject;
}
public void cleanup() {
if (initialized) {
this.python.cleanup();
}
}
}
public TriggerPlugin() throws IOException {
destinationExchange = ZenossQueueConfig.getConfig().getExchange("$Signals");
}
@Autowired
public void setTaskScheduler(TaskScheduler scheduler) {
this.scheduler = scheduler;
}
@Autowired
public void setUuidGenerator(UUIDGenerator uuidGenerator) {
this.uuidGenerator = uuidGenerator;
}
@Override
public void start(Map<String, String> properties) {
int triggerRuleCacheSize = this.getTriggerRuleCacheSize();
logger.info("TriggerPlugin trigger rule cache size: {}", triggerRuleCacheSize);
Map<String,TriggerRuleCache> boundedMap = ZepUtils.createBoundedMap(triggerRuleCacheSize);
this.triggerRuleCache = Collections.synchronizedMap(boundedMap);
super.start(properties);
scheduleSpool();
}
@Override
public void stop() {
this.pythonHelper.cleanup();
if (spoolFuture != null) {
spoolFuture.cancel(true);
}
}
public int getTriggerRuleCacheSize() {
return triggerRuleCacheSize;
}
public void setTriggerRuleCacheSize(int size) {
triggerRuleCacheSize = size;
}
private boolean cacheIsFull(Map<String, TriggerRuleCache> cache) {
return cache.size() >= this.getTriggerRuleCacheSize();
}
private void scheduleSpool() {
if (spoolFuture != null) {
spoolFuture.cancel(false);
}
Trigger trigger = new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date nextExecution = null;
try {
long nextFlushTime = signalSpoolDao.getNextFlushTime();
if (nextFlushTime > 0) {
nextExecution = new Date(nextFlushTime);
logger.debug("Next flush time: {}", nextExecution);
}
} catch (Exception e) {
logger.warn("Exception getting next flush time", e);
}
if (nextExecution == null) {
nextExecution = new Date(System.currentTimeMillis() + MAXIMUM_DELAY_MS);
}
return nextExecution;
}
};
Runnable runnable = new ThreadRenamingRunnable(new Runnable() {
@Override
public void run() {
processSpool(System.currentTimeMillis());
}
}, "ZEP_TRIGGER_PLUGIN_SPOOL");
try {
spoolFuture = scheduler.schedule(runnable, trigger);
} catch (TaskRejectedException e) {
logger.warn("Zep and rabbitmq-server may require a restart. Exception: {}", e);
}
}
public void setTriggerDao(EventTriggerDao triggerDao) {
this.triggerDao = triggerDao;
}
public void setSignalSpoolDao(EventSignalSpoolDao spoolDao) {
this.signalSpoolDao = spoolDao;
}
public void setEventSummaryDao(EventSummaryDao eventSummaryDao) {
this.eventSummaryDao = eventSummaryDao;
}
public void setEventTriggerSubscriptionDao(EventTriggerSubscriptionDao eventTriggerSubscriptionDao) {
this.eventTriggerSubscriptionDao = eventTriggerSubscriptionDao;
}
public void setConnectionManager(AmqpConnectionManager connmgr) {
this.connectionManager = connmgr;
}
public void setEventStoreDao(EventStoreDao eventStoreDao) {
this.eventStoreDao = eventStoreDao;
}
/**
* Local context class used to store the Python objects created from the event which are passed in to the
* trigger's rule for evaluation.
*/
static class RuleContext {
PyObject event;
PyObject device;
PyObject element;
PyObject subElement;
PyObject zpDetails;
private RuleContext() {
}
private static void putTitleAndUuidInDict(PyDictionary dict, String title, String uuid) {
if (title != null) {
dict.put("name", title);
}
if (uuid != null) {
dict.put("uuid", uuid);
}
}
private static final Splitter ORGANIZER_SPLITTER = Splitter.on('/').omitEmptyStrings();
/**
* Given a list of organizers, returns a list containing those same organizers plus
* any parent organizers. For example, ['/First/Second/Third','/OtherFirst/OtherSecond']
* will return ['/First', '/First/Second', '/First/Second/Third', '/OtherFirst',
* '/OtherFirst/OtherSecond'].
*
* @param baseOrganizers List of most-specific organizer names.
* @return A list containing all of the organizers plus their parent organizers.
*/
private static List<String> includeParentOrganizers(List<String> baseOrganizers) {
Set<String> allOrganizers = new TreeSet<String>();
for (String organizer : baseOrganizers) {
final StringBuilder sb = new StringBuilder(organizer.length());
for (String subOrganizer : ORGANIZER_SPLITTER.split(organizer)) {
sb.append('/').append(subOrganizer);
allOrganizers.add(sb.toString());
}
}
return new ArrayList<String>(allOrganizers);
}
/**
* Creates a rule context from the toObject function and event summary.
*
* @param toObject The toObject function which converts a dictionary to the appropriate object.
* @param evtsummary The event to convert to a context.
* @return A rule context for the event.
*/
public static RuleContext createContext(PyFunction toObject, EventSummary evtsummary) {
// set up interpreter environment to evaluate the rule source
PyDictionary eventdict = new PyDictionary();
PyDictionary devdict = new PyDictionary();
PyDictionary elemdict = new PyDictionary();
PyDictionary subelemdict = new PyDictionary();
PyDictionary zpDetDict = new PyDictionary();
// Match old behavior (pre-4.x)
int prodState = 0;
int devicePriority = DEVICE_PRIORITY_NORMAL;
String ipAddress = "";
List<String> systemsAndParents = Collections.emptyList();
List<String> groupsAndParents = Collections.emptyList();
String location = "";
String deviceClass = "";
// extract event data from most recent occurrence
Event event = evtsummary.getOccurrence(0);
// copy event data to eventdict
eventdict.put("summary", new PyString(event.getSummary()));
eventdict.put("message", new PyString(event.getMessage()));
eventdict.put("event_class", new PyString(event.getEventClass()));
eventdict.put("fingerprint", new PyString(event.getFingerprint()));
eventdict.put("event_key", new PyString(event.getEventKey()));
eventdict.put("agent", new PyString(event.getAgent()));
eventdict.put("monitor", new PyString(event.getMonitor()));
eventdict.put("severity", event.getSeverity().getNumber());
eventdict.put("event_class_key", new PyString(event.getEventClassKey()));
if (event.hasSyslogPriority()) {
eventdict.put("syslog_priority", new PyInteger(event.getSyslogPriority().getNumber()));
}
if (event.hasSyslogFacility()) {
eventdict.put("syslog_facility", event.getSyslogFacility());
}
if (event.hasNtEventCode()) {
eventdict.put("nt_event_code", event.getNtEventCode());
}
// Initialize to empty attributes on elem and subelem in case a rule references
// it and they do not exist
putTitleAndUuidInDict(subelemdict, "", "");
putTitleAndUuidInDict(elemdict, "", "");
EventActor actor = event.getActor();
if (actor.hasElementTypeId()) {
if (actor.getElementTypeId() == ModelElementType.DEVICE) {
devdict = elemdict;
}
elemdict.put("type", actor.getElementTypeId().name());
final String id = (actor.hasElementIdentifier()) ? actor.getElementIdentifier() : null;
final String title = (actor.hasElementTitle()) ? actor.getElementTitle() : id;
final String uuid = actor.hasElementUuid() ? actor.getElementUuid() : null;
putTitleAndUuidInDict(elemdict, title, uuid);
}
if (actor.hasElementSubTypeId()) {
if (actor.getElementSubTypeId() == ModelElementType.DEVICE) {
devdict = subelemdict;
}
subelemdict.put("type", actor.getElementSubTypeId().name());
final String id = (actor.hasElementSubIdentifier()) ? actor.getElementSubIdentifier() : null;
final String title = (actor.hasElementSubTitle()) ? actor.getElementSubTitle() : id;
final String uuid = actor.hasElementSubUuid() ? actor.getElementSubUuid() : null;
putTitleAndUuidInDict(subelemdict, title, uuid);
}
for (EventDetail detail : event.getDetailsList()) {
final String detailName = detail.getName();
// This should never happen
if (detail.getValueCount() == 0) {
continue;
}
final String singleDetailValue = detail.getValue(0);
if (DETAIL_DEVICE_PRODUCTION_STATE.equals(detailName)) {
try {
prodState = Integer.parseInt(singleDetailValue);
} catch (NumberFormatException e) {
logger.warn("Failed retrieving production state", e);
}
}
else if (DETAIL_DEVICE_PRIORITY.equals(detailName)) {
try {
devicePriority = Integer.parseInt(singleDetailValue);
} catch (NumberFormatException e) {
logger.warn("Failed retrieving device priority", e);
}
}
else if (DETAIL_DEVICE_CLASS.equals(detailName)) {
// expect that this is a single-value detail.
deviceClass = singleDetailValue;
}
else if (DETAIL_DEVICE_SYSTEMS.equals(detailName)) {
// expect that this is a multi-value detail.
systemsAndParents = includeParentOrganizers(detail.getValueList());
}
else if (DETAIL_DEVICE_GROUPS.equals(detailName)) {
// expect that this is a multi-value detail.
groupsAndParents = includeParentOrganizers(detail.getValueList());
}
else if (DETAIL_DEVICE_IP_ADDRESS.equals(detailName)) {
// expect that this is a single-value detail.
ipAddress = singleDetailValue;
}
else if (DETAIL_DEVICE_LOCATION.equals(detailName)) {
// expect that this is a single-value detail.
location = singleDetailValue;
}
else {
// Custom details added by ZenPacks, we replace the dots by underscore and add them
// to the zp detail dict. The UI did the same when the rule was created
String customDetail = detailName.replaceAll("\\.", "_");
zpDetDict.put(customDetail, new PyString(singleDetailValue));
}
}
devdict.put("device_class", new PyString(deviceClass));
devdict.put("production_state", new PyInteger(prodState));
devdict.put("priority", new PyInteger(devicePriority));
devdict.put("groups", new PyList(groupsAndParents));
devdict.put("systems", new PyList(systemsAndParents));
devdict.put("ip_address", new PyString(ipAddress));
devdict.put("location", new PyString(location));
// add more data from the EventSummary itself
eventdict.put("status", evtsummary.getStatus().getNumber());
eventdict.put("count", new PyInteger(evtsummary.getCount()));
eventdict.put("current_user_name", new PyString(evtsummary.getCurrentUserName()));
RuleContext ctx = new RuleContext();
// create vars to pass to rule expression function
ctx.event = toObject.__call__(eventdict);
ctx.device = toObject.__call__(devdict);
ctx.element = toObject.__call__(elemdict);
ctx.subElement = toObject.__call__(subelemdict);
ctx.zpDetails = toObject.__call__(zpDetDict);
return ctx;
}
}
int cacheSizeWarningCounter = 0;
protected boolean eventSatisfiesRule(RuleContext ruleContext, String triggerUuid, String ruleSource) {
PyObject result;
try {
// check to see if the cache is full and log an error if so
if (this.cacheIsFull(this.triggerRuleCache)) {
++cacheSizeWarningCounter;
if (cacheSizeWarningCounter % 100 == 0) {
logger.error("Trigger rule cache is full ({}); consider reconfiguring zeneventserver, making it larger",
this.getTriggerRuleCacheSize());
cacheSizeWarningCounter = 0;
}
}
// use rule to build and evaluate a Python lambda expression
TriggerRuleCache cacheItem = triggerRuleCache.get(triggerUuid);
PyFunction fn = null;
if (cacheItem == null || !cacheItem.getRuleSource().equals(ruleSource)) {
try {
fn = (PyFunction)this.pythonHelper.getPythonInterpreter().eval(
"lambda evt, dev, elem, sub_elem, zp_det : " + ruleSource
);
} catch (PySyntaxError e) {
String fmt = Py.formatException(e.type, e.value);
logger.warn("syntax error exception raised while compiling rule: {}, {}", ruleSource, fmt);
}
// Cache result of trigger evaluation (even if it failed to compile). This will prevent trying to
// recompile the same invalid rule over and over again.
triggerRuleCache.put(triggerUuid, new TriggerRuleCache(ruleSource, fn));
}
else {
fn = cacheItem.getPyFunction();
}
if (fn == null) {
logger.debug("Invalid rule source: {}", ruleSource);
return false;
}
// evaluate the rule function
PyObject [] args = { ruleContext.event, ruleContext.device, ruleContext.element, ruleContext.subElement, ruleContext.zpDetails } ;
result = fn.__call__(args);
} catch (PySyntaxError pysynerr) {
// evaluating rule raised an exception - treat as "False" eval
String fmt = Py.formatException(pysynerr.type, pysynerr.value);
logger.warn("syntax error exception raised while compiling rule: {}, {}", ruleSource, fmt);
result = new PyInteger(0);
} catch (PyException pyexc) {
// evaluating rule raised an exception - treat as "False" eval
// If it's an AttributeError it just means the event doesn't have a value for the field
// and an eval of False is fine. Otherwise we should log in case there's a real issue.
if (!pyexc.match(Py.AttributeError)) {
String fmt = Py.formatException(pyexc.type, pyexc.value);
logger.warn("exception raised while evaluating rule: {}, {}", ruleSource, fmt);
}
else if (logger.isDebugEnabled()) {
String fmt = Py.formatException(pyexc.type, pyexc.value);
logger.debug("AttributeError raised while evaluating rule: {}, {}", ruleSource, fmt);
}
result = new PyInteger(0);
}
// return result as a boolean, using Python __nonzero__
// object-as-bool evaluator
return result.__nonzero__();
}
private static class BatchIndexState {
private List<EventTrigger> triggers;
private Map<String, List<EventTriggerSubscription>> triggerSubscriptions =
new HashMap<String, List<EventTriggerSubscription>>();
private Map<String, EventSummary> eventsToDeleteFromSpool = new HashMap<String, EventSummary>();
}
private final ThreadLocal<BatchIndexState> batchState = new ThreadLocal<BatchIndexState>();
@Override
public void startBatch(EventPostIndexContext context) throws Exception {
// Ignore events in the archive.
if (context.isArchive()) {
return;
}
batchState.set(new BatchIndexState());
}
@Override
public void endBatch(EventPostIndexContext context) throws Exception {
// Ignore events in the archive.
if (context.isArchive()) {
return;
}
BatchIndexState state = batchState.get();
if (!state.eventsToDeleteFromSpool.isEmpty()) {
Set<String> eventUuids = state.eventsToDeleteFromSpool.keySet();
List<EventSignalSpool> spools = signalSpoolDao.findAllByEventSummaryUuids(eventUuids);
for (EventSignalSpool spool : spools) {
if (spool.isSentSignal()) {
logger.debug("sending clear signal for event: {}", spool.getEventSummaryUuid());
EventTriggerSubscription subscription =
eventTriggerSubscriptionDao.findByUuid(spool.getSubscriptionUuid());
EventSummary eventSummary = state.eventsToDeleteFromSpool.get(spool.getEventSummaryUuid());
publishSignal(eventSummary, subscription);
} else {
logger.debug("Skipping sending of clear signal for event {} and subscription {} - !sentSignal",
spool.getEventSummaryUuid(), spool.getSubscriptionUuid());
}
}
signalSpoolDao.deleteByEventSummaryUuids(eventUuids);
}
batchState.remove();
}
@Override
public void preProcessEvents(Collection<EventSummary> eventSummaries, EventPostIndexContext context) throws ZepException {
Set<String> uuids = new HashSet<String>(eventSummaries.size());
for (EventSummary event : eventSummaries)
uuids.add(event.getUuid());
List<EventSignalSpool> spools = this.signalSpoolDao.findAllByEventSummaryUuids(uuids);
for (EventSignalSpool spool : spools) {
rememberSpool(spool, context);
}
}
private void rememberSpool(EventSignalSpool spool, EventPostIndexContext context) {
Map<String, Map<String, EventSignalSpool>> state = getStateFromContext(context);
Map<String, EventSignalSpool> m = state.get(spool.getSubscriptionUuid());
if (m == null) {
m = new HashMap<String, EventSignalSpool>();
state.put(spool.getSubscriptionUuid(), m);
}
m.put(spool.getEventSummaryUuid(), spool);
}
private EventSignalSpool getSpoolBySubscriptionAndEventSummary(EventPostIndexContext context,
EventTriggerSubscription subscription,
EventSummary eventSummary) {
Map<String, Map<String, EventSignalSpool>> state = getStateFromContext(context);
Map<String, EventSignalSpool> m = state.get(subscription.getUuid());
if (m == null) return null;
return m.get(eventSummary.getUuid());
}
private Map<String,Map<String,EventSignalSpool>> getStateFromContext(EventPostIndexContext context) {
Map<String,Map<String,EventSignalSpool>> state = (Map<String, Map<String, EventSignalSpool>>) context.getPluginState(this);
if (state == null) {
state = new HashMap<String, Map<String, EventSignalSpool>>();
context.setPluginState(this, state);
}
return state;
}
@Override
public void processEvent(EventSummary eventSummary, EventPostIndexContext context) throws ZepException {
// Ignore events in the archive.
if (context.isArchive()) {
return;
}
final EventStatus evtstatus = eventSummary.getStatus();
if (OPEN_STATUSES.contains(evtstatus)) {
processOpenEvent(eventSummary, context);
} else {
batchState.get().eventsToDeleteFromSpool.put(eventSummary.getUuid(), eventSummary);
}
}
private void processOpenEvent(EventSummary eventSummary, EventPostIndexContext context) throws ZepException {
final long now = System.currentTimeMillis();
BatchIndexState state = batchState.get();
List<EventTrigger> triggers = state.triggers;
if (triggers == null) {
triggers = this.triggerDao.findAllEnabled();
state.triggers = triggers;
}
// iterate over all enabled triggers to see if any rules will match
// for this event summary
boolean rescheduleSpool = false;
RuleContext ruleContext = null;
if (!triggers.isEmpty()) {
logger.debug("Event: {}", eventSummary);
}
for (EventTrigger trigger : triggers) {
// verify trigger has a defined rule
if (!(trigger.hasRule() && trigger.getRule().hasSource())) {
continue;
}
// confirm trigger has any subscriptions registered with it
List<EventTriggerSubscription> subscriptions = state.triggerSubscriptions.get(trigger.getUuid());
if (subscriptions == null) {
subscriptions = trigger.getSubscriptionsList();
state.triggerSubscriptions.put(trigger.getUuid(), subscriptions);
}
if (subscriptions.isEmpty()) {
continue;
}
final String ruleSource = trigger.getRule().getSource();
// Determine if event matches trigger rule
if (ruleContext == null) {
ruleContext = RuleContext.createContext(this.pythonHelper.getToObject(), eventSummary);
}
final boolean eventSatisfiesRule = eventSatisfiesRule(ruleContext, trigger.getUuid(), ruleSource);
if (eventSatisfiesRule) {
logger.debug("Trigger {} ({}) MATCHES", trigger.getName(), ruleSource);
}
else {
logger.debug("Trigger {} ({}) DOES NOT MATCH", trigger.getName(), ruleSource);
}
// handle interval evaluation/buffering
for (EventTriggerSubscription subscription : subscriptions) {
final int delaySeconds = subscription.getDelaySeconds();
final int repeatSeconds = subscription.getRepeatSeconds();
EventSignalSpool currentSpool = getSpoolBySubscriptionAndEventSummary(context, subscription,eventSummary);
boolean spoolExists = (currentSpool != null);
boolean spoolModified = false;
if (eventSatisfiesRule) {
logger.debug("subscriber: {}, delay: {}, repeat: {}, existing spool: {}",
new Object[] { subscription.getSubscriberUuid(), delaySeconds, repeatSeconds, spoolExists });
}
boolean onlySendInitial = subscription.getSendInitialOccurrence();
// If the rule wasn't satisfied
if (!eventSatisfiesRule) {
// If the rule previously matched and now no longer matches, ensure that the
// repeated signaling will not occur again.
if (spoolExists && currentSpool.getFlushTime() < Long.MAX_VALUE) {
logger.debug("Event previously matched trigger - disabling repeats");
currentSpool.setFlushTime(Long.MAX_VALUE);
spoolModified = true;
rescheduleSpool = true;
}
}
// Send signal immediately if no delay
else if (delaySeconds <= 0) {
if (!onlySendInitial) {
logger.debug("delay <= 0 and !onlySendInitial, send signal");
this.publishSignal(eventSummary, subscription);
if (!spoolExists) {
currentSpool = EventSignalSpool.buildSpool(subscription, eventSummary, this.uuidGenerator);
currentSpool.setSentSignal(true);
this.signalSpoolDao.create(currentSpool);
rescheduleSpool = true;
}
else if (!currentSpool.isSentSignal()) {
currentSpool.setSentSignal(true);
spoolModified = true;
}
}
else {
if (!spoolExists) {
logger.debug("delay <=0 and spool doesn't exist, send signal");
this.publishSignal(eventSummary, subscription);
currentSpool = EventSignalSpool.buildSpool(subscription, eventSummary, this.uuidGenerator);
currentSpool.setSentSignal(true);
this.signalSpoolDao.create(currentSpool);
rescheduleSpool = true;
}
else {
if (repeatSeconds > 0 &&
currentSpool.getFlushTime() > now + TimeUnit.SECONDS.toMillis(repeatSeconds)) {
logger.debug("adjust spool flush time to reflect new repeat seconds");
currentSpool.setFlushTime(now + TimeUnit.SECONDS.toMillis(repeatSeconds));
spoolModified = true;
rescheduleSpool = true;
}
}
}
}
else {
// delaySeconds > 0
if (!spoolExists) {
currentSpool = EventSignalSpool.buildSpool(subscription, eventSummary, this.uuidGenerator);
this.signalSpoolDao.create(currentSpool);
rescheduleSpool = true;
}
else {
if (repeatSeconds == 0) {
if (!onlySendInitial && currentSpool.getFlushTime() == Long.MAX_VALUE) {
currentSpool.setFlushTime(now + TimeUnit.SECONDS.toMillis(delaySeconds));
spoolModified = true;
rescheduleSpool = true;
}
}
else {
if (currentSpool.getFlushTime() > now + TimeUnit.SECONDS.toMillis(repeatSeconds)) {
currentSpool.setFlushTime(now + TimeUnit.SECONDS.toMillis(repeatSeconds));
spoolModified = true;
rescheduleSpool = true;
}
}
}
}
if (spoolModified) {
this.signalSpoolDao.update(currentSpool);
}
}
}
if (rescheduleSpool) {
scheduleSpool();
}
}
protected void publishSignal(EventSummary eventSummary, EventTriggerSubscription subscription) throws ZepException {
Event occurrence = eventSummary.getOccurrence(0);
Signal.Builder signalBuilder = Signal.newBuilder();
signalBuilder.setUuid(uuidGenerator.generate().toString());
signalBuilder.setCreatedTime(System.currentTimeMillis());
signalBuilder.setEvent(eventSummary);
signalBuilder.setSubscriberUuid(subscription.getSubscriberUuid());
signalBuilder.setTriggerUuid(subscription.getTriggerUuid());
signalBuilder.setMessage(occurrence.getMessage());
signalBuilder.setClear(CLOSED_STATUSES.contains(eventSummary.getStatus()));
if (EventStatus.STATUS_CLEARED == eventSummary.getStatus()) {
// Look up event which cleared this one
EventSummary clearEventSummary = this.eventStoreDao.findByUuid(eventSummary.getClearedByEventUuid());
if (clearEventSummary != null) {
signalBuilder.setClearEvent(clearEventSummary);
} else {
logger.warn("Unable to look up clear event with UUID: {}", eventSummary.getClearedByEventUuid());
}
}
Signal signal = signalBuilder.build();
logger.debug("Publishing signal: {}", signal);
try {
this.connectionManager.publish(destinationExchange, "zenoss.signal", signal);
} catch (AmqpException e) {
throw new ZepException(e);
}
}
protected synchronized void processSpool(long processCutoffTime) {
logger.debug("Processing signal spool");
try {
// TODO: This should be refactored to have findAllDue return consistent set of spool, event, subscription,
// and trigger.
// get spools that need to be processed
List<EventSignalSpool> spools = this.signalSpoolDao.findAllDue();
List<String> spoolsToDelete = new ArrayList<String>(spools.size());
for (EventSignalSpool spool : spools) {
EventSummary eventSummary = this.eventSummaryDao.findByUuid(spool.getEventSummaryUuid());
EventStatus status = (eventSummary != null) ? eventSummary.getStatus() : null;
// These should have been deleted when the event was run through the TriggerPlugin when the status
// changed, but just in case delete them as the event is now in a closed state.
if (!OPEN_STATUSES.contains(status)) {
spoolsToDelete.add(spool.getUuid());
continue;
}
EventTriggerSubscription trSub = this.eventTriggerSubscriptionDao
.findByUuid(spool.getSubscriptionUuid());
if (trSub == null) {
logger.debug("Current spool entry no longer valid (subscription deleted), skipping: {}",
spool.getUuid());
continue;
}
// Check to see if trigger is still enabled
EventTrigger trigger = this.triggerDao.findByUuid(trSub.getTriggerUuid());
if (trigger == null) {
logger.debug("Current spool entry no longer valid (trigger deleted), skipping: {}",
spool.getUuid());
continue;
}
if (trigger.getEnabled()) {
publishSignal(eventSummary, trSub);
if (!spool.isSentSignal()) {
spool.setSentSignal(true);
}
}
int repeatInterval = trSub.getRepeatSeconds();
// Schedule the next repeat
if (repeatInterval > 0 && OPEN_STATUSES.contains(status) && status != EventStatus.STATUS_ACKNOWLEDGED) {
long nextFlush = processCutoffTime + TimeUnit.SECONDS.toMillis(repeatInterval);
spool.setFlushTime(nextFlush);
}
else {
// Update the existing spool entry to make sure it won't send again
spool.setFlushTime(Long.MAX_VALUE);
}
this.signalSpoolDao.update(spool);
}
if (!spoolsToDelete.isEmpty()) {
this.signalSpoolDao.delete(spoolsToDelete);
}
} catch (Exception e) {
logger.warn("Failed to process signal spool", e);
}
}
}