package org.ovirt.engine.core.notifier; import java.sql.SQLException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.EventNotificationMethod; import org.ovirt.engine.core.notifier.dao.EventsManager; import org.ovirt.engine.core.notifier.filter.AuditLogEvent; import org.ovirt.engine.core.notifier.filter.FirstMatchSimpleFilter; import org.ovirt.engine.core.notifier.transport.Transport; import org.ovirt.engine.core.notifier.utils.NotificationProperties; import org.ovirt.engine.core.notifier.utils.ShutdownHook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Responsible for an execution of the service for the current events in the system which should be notified to the * subscribers. */ public class NotificationService implements Runnable { public static final String FILTER = "FILTER"; private static final Logger log = LoggerFactory.getLogger(NotificationService.class); private final NotificationProperties prop; private final EventsManager eventsManager; private final FirstMatchSimpleFilter firstMatchSimpleFilter; private List<FirstMatchSimpleFilter.FilterEntry> configurationFilters; private List<Transport> transports = new LinkedList<>(); private int failedQueries = 0; public NotificationService(NotificationProperties prop) throws NotificationServiceException { this.prop = prop; this.eventsManager = new EventsManager(); firstMatchSimpleFilter = new FirstMatchSimpleFilter(); configurationFilters = FirstMatchSimpleFilter.parse(prop.getProperty(FILTER)); } private void markOldEventsAsProcessed() { eventsManager.markOldEventsAsProcessed(prop.getInteger(NotificationProperties.DAYS_TO_SEND_ON_STARTUP)); } public void registerTransport(Transport transport){ if (transport.isActive()) { firstMatchSimpleFilter.registerTransport(transport); transport.registerObserver(this.eventsManager); transports.add(transport); } } public boolean hasTransports() { return !transports.isEmpty(); } @Override public void run() { markOldEventsAsProcessed(); ShutdownHook shutdownHook = ShutdownHook.getInstance(); ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); shutdownHook.addScheduledExecutorService(exec); shutdownHook.addServiceHandler( exec.scheduleWithFixedDelay( () -> mainLogic(), 1, prop.getLong(NotificationProperties.INTERVAL_IN_SECONDS), TimeUnit.SECONDS ) ); shutdownHook.addServiceHandler( exec.scheduleWithFixedDelay( () -> idle(), 1, prop.getLong(NotificationProperties.IDLE_INTERVAL), TimeUnit.SECONDS ) ); } /** * Executes event notification to subscribers */ private void mainLogic() { try { try { log.debug("Start event notification service iteration"); // Clear filter chain firstMatchSimpleFilter.clearFilterEntries(); // Read Database subscriptions first firstMatchSimpleFilter.addFilterEntries(eventsManager.getAuditLogEventSubscribers()); // Backward compatibility, aim to remove (can be replaced by "FILTER") String dbDownSubscribers = prop.getProperty(NotificationProperties.FAILED_QUERIES_NOTIFICATION_RECIPIENTS, true); if (!StringUtils.isEmpty(dbDownSubscribers)) { for (String subscriber : dbDownSubscribers.split(",")) { FirstMatchSimpleFilter.FilterEntry subscriberEntry = new FirstMatchSimpleFilter.FilterEntry( EventsManager.DATABASE_UNREACHABLE, null, false, EventNotificationMethod.SMTP.getAsString(), subscriber); List<FirstMatchSimpleFilter.FilterEntry> subscriberEntries = Collections.singletonList(subscriberEntry); firstMatchSimpleFilter.addFilterEntries(subscriberEntries); } } // Add configurations subscription firstMatchSimpleFilter.addFilterEntries( configurationFilters ); for (AuditLogEvent event : eventsManager.getAuditLogEvents()) { firstMatchSimpleFilter.processEvent(event); eventsManager.updateAuditLogEventProcessed(event.getId()); } deleteObsoleteHistoryData(); log.debug("Finished event notification service iteration"); } catch (SQLException se) { distributeDbDownEvent(); throw se; } } catch (Throwable t) { log.error("Failed to run the service.", t); } } private void idle() { log.debug("Begin idle iteration"); for (Transport transport : transports) { transport.idle(); } log.debug("Finished idle iteration"); } private void deleteObsoleteHistoryData() throws SQLException { eventsManager.deleteObsoleteHistoryData(prop.getInteger(NotificationProperties.DAYS_TO_KEEP_HISTORY)); } private void distributeDbDownEvent() { firstMatchSimpleFilter.clearFilterEntries(); firstMatchSimpleFilter.addFilterEntries( configurationFilters ); if (failedQueries == 0) { try { firstMatchSimpleFilter.processEvent(eventsManager.createDBDownEvent()); } catch (Exception e) { log.error("Failed to dispatch {} event", EventsManager.DATABASE_UNREACHABLE, e); // Don't rethrow. we don't want to mask the original query exception. } } int failedQueriesNotificationThreshold = prop.getInteger(NotificationProperties.FAILED_QUERIES_NOTIFICATION_THRESHOLD); if (failedQueriesNotificationThreshold == 0) { failedQueriesNotificationThreshold = 1; } failedQueries = (failedQueries + 1) % failedQueriesNotificationThreshold; } }