Package Products :: Package ZenEvents :: Module zenactiond
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenEvents.zenactiond

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2010, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13   
 14  import Globals 
 15  from traceback import format_exc 
 16  from email.MIMEText import MIMEText 
 17  from email.MIMEMultipart import MIMEMultipart 
 18  from email.Utils import formatdate 
 19  from twisted.internet import reactor, defer 
 20   
 21  from zenoss.protocols.queueschema import SchemaException 
 22  from zenoss.protocols import hydrateQueueMessage 
 23  from zenoss.protocols.interfaces import IQueueSchema 
 24  from Products.ZenCollector.utils.maintenance import MaintenanceCycle, maintenanceBuildOptions, QueueHeartbeatSender 
 25  from Products.ZenCollector.utils.workers import ProcessWorkers, workersBuildOptions, exec_worker 
 26   
 27  from Products.ZenUtils.ZCmdBase import ZCmdBase 
 28  from Products.ZenUtils.Utils import getDefaultZopeUrl 
 29  from Products.ZenUtils.guid.interfaces import IGlobalIdentifier 
 30  from Products.ZenUtils.guid.guid import GUIDManager 
 31   
 32  from Products.ZenModel.NotificationSubscription import NotificationSubscriptionManager 
 33  from Products.ZenModel.actions import ActionMissingException, TargetableAction, ActionExecutionException 
 34  from Products.ZenModel.interfaces import IAction 
 35  from Products.ZenEvents.Event import Event 
 36  from Products.ZenMessaging.queuemessaging.QueueConsumer import QueueConsumer 
 37  from Products.ZenMessaging.queuemessaging.interfaces import IQueueConsumerTask 
 38  from Products.ZenEvents.ZenEventClasses import Warning as SEV_WARNING 
 39  from zope.component import getUtility, getUtilitiesFor 
 40  from zope.component.interfaces import ComponentLookupError 
 41  from zope.interface import implements 
 42   
 43   
 44  import logging 
 45  log = logging.getLogger("zen.zenactiond") 
46 47 48 -class NotificationDao(object):
49 - def __init__(self, dmd):
50 self.dmd = dmd 51 self.notification_manager = self.dmd.getDmdRoot(NotificationSubscriptionManager.root) 52 self.guidManager = GUIDManager(dmd)
53
54 - def getNotifications(self):
55 self.dmd._p_jar.sync() 56 return self.notification_manager.getChildNodes()
57
58 - def getSignalNotifications(self, signal):
59 """ 60 Given a signal, find which notifications match this signal. In order to 61 match, a notification must be active (enabled and if has maintenance 62 windows, at least one must be active) and must be subscribed to the 63 signal. 64 65 @param signal: The signal for which to get subscribers. 66 @type signal: protobuf zep.Signal 67 """ 68 active_matching_notifications = [] 69 for notification in self.getNotifications(): 70 if notification.isActive(): 71 if self.notificationSubscribesToSignal(notification, signal): 72 active_matching_notifications.append(notification) 73 log.debug('Found matching notification: %s' % notification) 74 else: 75 log.debug('Notification "%s" does not subscribe to this signal.' % notification) 76 else: 77 log.debug('Notification "%s" is not active.' % notification) 78 79 return active_matching_notifications
80
81 - def notificationSubscribesToSignal(self, notification, signal):
82 """ 83 Determine if the notification matches the specified signal. 84 85 @param notification: The notification to check 86 @type notification: NotificationSubscription 87 @param signal: The signal to match. 88 @type signal: zenoss.protocols.protbufs.zep_pb2.Signal 89 90 @rtype boolean 91 """ 92 return signal.subscriber_uuid == IGlobalIdentifier(notification).getGUID()
93
94 -class ProcessSignalTask(object):
95 implements(IQueueConsumerTask) 96
97 - def __init__(self, notificationDao):
98 self.notificationDao = notificationDao 99 100 # set by the constructor of queueConsumer 101 self.queueConsumer = None 102 103 self.schema = getUtility(IQueueSchema) 104 self.queue = self.schema.getQueue("$Signals")
105
106 - def getAction(self, action):
107 try: 108 return getUtility(IAction, action) 109 except ComponentLookupError, e: 110 raise ActionMissingException(action)
111
112 - def processMessage(self, message):
113 """ 114 Handles a queue message, can call "acknowledge" on the Queue Consumer 115 class when it is done with the message 116 """ 117 log.debug('processing message.') 118 119 if message.content.body == self.queueConsumer.MARKER: 120 log.info("Received MARKER sentinel, exiting message loop") 121 self.queueConsumer.acknowledge(message) 122 return 123 try: 124 signal = hydrateQueueMessage(message, self.schema) 125 self.processSignal(signal) 126 log.debug('Done processing signal.') 127 except SchemaException: 128 log.error("Unable to hydrate protobuf %s. " % message.content.body) 129 self.queueConsumer.acknowledge(message) 130 except Exception, e: 131 log.exception(e) 132 # FIXME: Send to an error queue instead of acknowledge. 133 log.error('Acknowledging broken message.') 134 self.queueConsumer.acknowledge(message) 135 else: 136 log.debug('Acknowledging message. (%s)' % signal.message) 137 self.queueConsumer.acknowledge(message)
138
139 - def processSignal(self, signal):
140 matches = self.notificationDao.getSignalNotifications(signal) 141 log.debug('Found these matching notifications: %s' % matches) 142 143 trigger = self.notificationDao.guidManager.getObject(signal.trigger_uuid) 144 audit_event_trigger_info = "Event:'%s' Trigger:%s" % ( 145 signal.event.occurrence[0].fingerprint, 146 trigger.id) 147 for notification in matches: 148 if signal.clear and not notification.send_clear: 149 log.debug('Ignoring clearing signal since send_clear is set to False on this subscription %s' % notification.id) 150 continue 151 try: 152 target = signal.subscriber_uuid or '<none>' 153 action = self.getAction(notification.action) 154 if isinstance(action, TargetableAction): 155 target = ','.join(action.getTargets(notification)) 156 action.execute(notification, signal) 157 except ActionMissingException, e: 158 log.error('Error finding action: {action}'.format(action = notification.action)) 159 audit_msg = "%s Action:%s Status:%s Target:%s Info:%s" % ( 160 audit_event_trigger_info, notification.action, "FAIL", target, "<action not found>") 161 except ActionExecutionException, aee: 162 log.error('Error executing action: {action} on notification {notification}'.format( 163 action = notification.action, 164 notification = notification.id, 165 )) 166 audit_msg = "%s Action:%s Status:%s Target:%s Info:%s" % ( 167 audit_event_trigger_info, notification.action, "FAIL", target, aee) 168 except Exception, e: 169 msg = 'Error executing action {notification}'.format( 170 notification = notification.id, 171 ) 172 log.error(e) 173 log.error(msg) 174 traceback = format_exc() 175 event = Event(device="localhost", 176 eventClass="/App/Failed", 177 summary=msg, 178 message=traceback, 179 severity=SEV_WARNING, component="zenactiond") 180 self.dmd.ZenEventManager.sendEvent(event) 181 audit_msg = "%s Action:%s Status:%s Target:%s Info:%s" % ( 182 audit_event_trigger_info, notification.action, "FAIL", target, action.getInfo(notification)) 183 else: 184 # audit trail of performed actions 185 audit_msg = "%s Action:%s Status:%s Target:%s Info:%s" % ( 186 audit_event_trigger_info, notification.action, "SUCCESS", target, action.getInfo(notification)) 187 log.info(audit_msg) 188 log.debug('Done processing signal. (%s)' % signal.message)
189
190 -class ZenActionD(ZCmdBase):
191 - def __init__(self):
192 super(ZenActionD, self).__init__() 193 self._consumer = None 194 self._workers = ProcessWorkers(self.options.workers - 1, 195 exec_worker, 196 "zenactiond worker") 197 self._heartbeatSender = QueueHeartbeatSender('localhost', 198 'zenactiond', 199 self.options.maintenancecycle *3) 200 201 self._maintenanceCycle = MaintenanceCycle(self.options.maintenancecycle, 202 self._heartbeatSender)
203
204 - def buildOptions(self):
205 super(ZenActionD, self).buildOptions() 206 maintenanceBuildOptions(self.parser) 207 workersBuildOptions(self.parser, 1) 208 209 default_max_commands = 10 210 self.parser.add_option('--maxcommands', dest="maxCommands", type="int", default=default_max_commands, 211 help='Max number of action commands to perform concurrently (default: %d)' % \ 212 default_max_commands) 213 default_url = getDefaultZopeUrl() 214 self.parser.add_option('--zopeurl', dest='zopeurl', default=default_url, 215 help="http path to the root of the zope server (default: %s)" % default_url)
216 217
218 - def run(self):
219 # Configure all actions with the command-line options 220 options_dict = dict(vars(self.options)) 221 for name, action in getUtilitiesFor(IAction): 222 action.configure(options_dict) 223 224 task = ProcessSignalTask(NotificationDao(self.dmd)) 225 226 if self.options.daemon: 227 self._maintenanceCycle.start() 228 if self.options.daemon and self.options.workers > 1: 229 self._workers.startWorkers() 230 231 self._consumer = QueueConsumer(task, self.dmd) 232 reactor.callWhenRunning(self._start) 233 reactor.run()
234
235 - def _start(self):
236 log.info('starting zenactiond consumer.') 237 reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) 238 self._consumer.run()
239 240 241 @defer.inlineCallbacks
242 - def _shutdown(self, *ignored):
243 log.info("Shutting down...") 244 self._maintenanceCycle.stop() 245 self._workers.shutdown() 246 if self._consumer: 247 yield self._consumer.shutdown()
248 249 250 if __name__ == '__main__': 251 zad = ZenActionD() 252 zad.run() 253