1
2
3
4
5
6
7
8
9
10
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")
53
55 self.dmd._p_jar.sync()
56 return self.notification_manager.getChildNodes()
57
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
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
95 implements(IQueueConsumerTask)
96
98 self.notificationDao = notificationDao
99
100
101 self.queueConsumer = None
102
103 self.schema = getUtility(IQueueSchema)
104 self.queue = self.schema.getQueue("$Signals")
105
111
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
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
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
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
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
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
219
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
236 log.info('starting zenactiond consumer.')
237 reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
238 self._consumer.run()
239
240
241 @defer.inlineCallbacks
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