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

Source Code for Module Products.ZenEvents.zenactions

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, 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 as published by 
  8  # the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13  #! /usr/bin/env python  
 14   
 15  __doc__='''zenactions 
 16   
 17  Turn events into notifications (pages, emails). 
 18   
 19  ''' 
 20   
 21   
 22  import socket 
 23  import time 
 24  from sets import Set 
 25  import Globals 
 26   
 27  from ZODB.POSException import POSError 
 28  from _mysql_exceptions import OperationalError, ProgrammingError  
 29   
 30  from Products.ZenUtils.ZCmdBase import ZCmdBase 
 31  from Products.ZenUtils.ZenTales import talesCompile, getEngine 
 32  from Products.ZenEvents.Exceptions import ZenEventNotFound 
 33  from ZenEventClasses import App_Start, App_Stop, Status_Heartbeat  
 34  from ZenEventClasses import Cmd_Fail 
 35  import Event 
 36  from Schedule import Schedule 
 37  from UpdateCheck import UpdateCheck 
 38  from Products.ZenUtils import Utils 
 39  from twisted.internet import reactor 
 40  from twisted.internet.protocol import ProcessProtocol 
 41  from email.Utils import formatdate 
 42   
 43  DEFAULT_MONITOR = "localhost" 
 44   
45 -def _capitalize(s):
46 return s[0:1].upper() + s[1:]
47
48 -class EventCommandProtocol(ProcessProtocol):
49
50 - def __init__(self, cmd, server):
51 self.cmd = cmd 52 self.server = server 53 self.data = '' 54 self.error = '' 55 self.timeout = reactor.callLater(cmd.defaultTimeout, self.timedOut)
56
57 - def timedOut(self):
58 self.timeout = None 59 self.server.log.error("Command %s timed out" % self.cmd.id) 60 self.server.sendEvent(Event.Event( 61 device=self.server.options.monitor, 62 eventClass=Cmd_Fail, 63 severity=Event.Error, 64 component="zenactions", 65 eventKey=self.cmd.id, 66 summary="Timeout running %s" % (self.cmd.id,), 67 ))
68
69 - def processEnded(self, reason):
70 self.server.log.debug("Command finished: %s" % reason.getErrorMessage()) 71 code = 1 72 try: 73 code = reason.value.exitCode 74 except AttributeError: 75 pass 76 77 # The process has ended. We can cancel the timeout now. 78 if self.timeout: 79 self.timeout.cancel() 80 self.timeout = None 81 82 if code == 0: 83 cmdData = self.data or "<command produced no output>" 84 self.server.log.debug("Command %s says: %s", self.cmd.id, cmdData) 85 self.server.sendEvent(Event.Event( 86 device=self.server.options.monitor, 87 eventClass=Cmd_Fail, 88 severity=Event.Clear, 89 component="zenactions", 90 eventKey=self.cmd.id, 91 summary="Command succeeded: %s: %s" % ( 92 self.cmd.id, cmdData), 93 )) 94 else: 95 cmdError = self.error or "<command produced no output>" 96 self.server.log.error("Command %s says %s", self.cmd.id, cmdError) 97 self.server.sendEvent(Event.Event( 98 device=self.server.options.monitor, 99 eventClass=Cmd_Fail, 100 severity=Event.Error, 101 component="zenactions", 102 eventKey=self.cmd.id, 103 summary="Error running: %s: %s" % ( 104 self.cmd.id, cmdError), 105 ))
106
107 - def outReceived(self, text):
108 self.data += text
109
110 - def errReceived(self, text):
111 self.error += text
112 113
114 -class ZenActions(ZCmdBase):
115 """ 116 Take actions based on events in the event manager. 117 Start off by sending emails and pages. 118 """ 119 120 lastCommand = None 121 122 addstate = ("INSERT INTO alert_state " 123 "VALUES ('%s', '%s', '%s', NULL) " 124 "ON DUPLICATE KEY UPDATE lastSent = now()") 125 126 127 clearstate = ("DELETE FROM alert_state " 128 " WHERE evid='%s' " 129 " AND userid='%s' " 130 " AND rule='%s'") 131 132 #FIXME attempt to convert subquery to left join that doesn't work 133 # newsel = """select %s, evid from status s left join alert_state a 134 # on s.evid=a.evid where a.evid is null and 135 # a.userid='%s' and a.rule='%s'""" 136 137 newsel = ("SELECT %s, evid FROM status WHERE " 138 "%s AND evid NOT IN " 139 " (SELECT evid FROM alert_state " 140 " WHERE userid='%s' AND rule='%s' %s)") 141 142 clearsel = ("SELECT %s, h.evid FROM history h, alert_state a " 143 " WHERE h.evid=a.evid AND a.userid='%s' AND a.rule='%s'") 144 145 clearEventSelect = ("SELECT %s " 146 " FROM history clear, history event " 147 " WHERE clear.evid = event.clearid " 148 " AND event.evid = '%s'") 149 150
151 - def __init__(self):
152 ZCmdBase.__init__(self) 153 self.schedule = Schedule(self.options, self.dmd) 154 self.schedule.sendEvent = self.dmd.ZenEventManager.sendEvent 155 self.schedule.monitor = self.options.monitor 156 157 self.actions = [] 158 self.loadActionRules() 159 self.updateCheck = UpdateCheck() 160 self.sendEvent(Event.Event(device=self.options.monitor, 161 eventClass=App_Start, 162 summary="zenactions started", 163 severity=0, component="zenactions"))
164
165 - def loadActionRules(self):
166 """Load the ActionRules into the system. 167 """ 168 self.actions = [] 169 for ar in self.dmd.ZenUsers.getAllActionRules(): 170 if not ar.enabled: continue 171 userid = ar.getUser().id 172 self.actions.append(ar) 173 self.log.debug("action:%s for:%s loaded", ar.getId(), userid)
174 175
176 - def execute(self, stmt):
177 result = None 178 self.lastCommand = stmt 179 self.log.debug(stmt) 180 zem = self.dmd.ZenEventManager 181 conn = zem.connect() 182 try: 183 curs = conn.cursor() 184 result = curs.execute(stmt) 185 finally: zem.close(conn) 186 return result
187 188
189 - def query(self, stmt):
190 result = None 191 self.lastCommand = stmt 192 self.log.debug(stmt) 193 zem = self.dmd.ZenEventManager 194 conn = zem.connect() 195 try: 196 curs = conn.cursor() 197 curs.execute(stmt) 198 result = curs.fetchall() 199 finally: zem.close(conn) 200 return result
201 202
203 - def getBaseUrl(self, device=None):
204 url = self.options.zopeurl 205 if device: 206 return "%s%s" % (url, device.getPrimaryUrlPath()) 207 else: 208 return "%s/zport/dmd/Events" % (url)
209 210
211 - def getEventUrl(self, evid, device=None):
212 return "%s/eventFields?evid=%s" % (self.getBaseUrl(device), evid)
213 214
215 - def getEventsUrl(self, device=None):
216 return "%s/viewEvents" % self.getBaseUrl(device)
217 218
219 - def getAckUrl(self, evid, device=None):
220 return "%s/manage_ackEvents?evids=%s&zenScreenName=viewEvents" % ( 221 self.getBaseUrl(device), evid)
222 223
224 - def getDeleteUrl(self, evid, device=None):
225 return "%s/manage_deleteEvents?evids=%s" % ( 226 self.getBaseUrl(device), evid) + \ 227 "&zenScreenName=viewHistoryEvents"
228 229
230 - def getUndeleteUrl(self, evid, device=None):
231 return "%s/manage_undeleteEvents?evids=%s" % ( 232 self.getBaseUrl(device), evid) + \ 233 "&zenScreenName=viewEvents"
234 235
236 - def processRules(self, zem):
237 """Run through all rules matching them against events. 238 """ 239 for ar in self.actions: 240 try: 241 self.lastCommand = None 242 # call sendPage or sendEmail 243 actfunc = getattr(self, "send"+ar.action.title()) 244 self.processEvent(zem, ar, actfunc) 245 except (SystemExit, KeyboardInterrupt, OperationalError, POSError): 246 raise 247 except: 248 if self.lastCommand: 249 self.log.warning(self.lastCommand) 250 self.log.exception("action:%s",ar.getId())
251
252 - def checkVersion(self, zem):
253 self.updateCheck.check(self.dmd, zem) 254 import transaction 255 transaction.commit()
256
257 - def processEvent(self, zem, context, action):
258 fields = context.getEventFields() 259 userid = context.getUserid() 260 # get new events 261 nwhere = context.where.strip() or '1 = 1' 262 if context.delay > 0: 263 nwhere += " and firstTime + %s < UNIX_TIMESTAMP()" % context.delay 264 awhere = '' 265 if context.repeatTime: 266 awhere += ' and DATE_ADD(lastSent, INTERVAL %d SECOND) > now() ' % ( 267 context.repeatTime,) 268 q = self.newsel % (",".join(fields), nwhere, userid, context.getId(), 269 awhere) 270 for result in self.query(q): 271 evid = result[-1] 272 data = dict(zip(fields, map(zem.convert, fields, result[:-1]))) 273 274 # Make details available to event commands. zem.getEventDetail 275 # uses the status table (which is where this event came from 276 try: 277 details = dict( zem.getEventDetail(evid).getEventDetails() ) 278 data.update( details ) 279 except ZenEventNotFound: 280 pass 281 282 device = self.dmd.Devices.findDevice(data.get('device', None)) 283 data['eventUrl'] = self.getEventUrl(evid, device) 284 if device: 285 data['eventsUrl'] = self.getEventsUrl(device) 286 else: 287 data['eventsUrl'] = 'n/a' 288 data['device'] = data.get('device', None) or '' 289 data['ackUrl'] = self.getAckUrl(evid, device) 290 data['deleteUrl'] = self.getDeleteUrl(evid, device) 291 severity = data.get('severity', -1) 292 data['severityString'] = zem.getSeverityString(severity) 293 if action(context, data, False): 294 addcmd = self.addstate % (evid, userid, context.getId()) 295 self.execute(addcmd) 296 297 # get clear events 298 historyFields = [("h.%s" % f) for f in fields] 299 historyFields = ','.join(historyFields) 300 q = self.clearsel % (historyFields, userid, context.getId()) 301 for result in self.query(q): 302 evid = result[-1] 303 data = dict(zip(fields, map(zem.convert, fields, result[:-1]))) 304 305 # For clear events we are using the history table, so get the event details 306 # using the history table. 307 try: 308 details = dict( zem.getEventDetailFromStatusOrHistory(evid).getEventDetails() ) 309 data.update( details ) 310 except ZenEventNotFound: 311 pass 312 313 # get clear columns 314 cfields = [('clear.%s' % x) for x in fields] 315 q = self.clearEventSelect % (",".join(cfields), evid) 316 317 # convert clear columns to clear names 318 cfields = [('clear%s' % _capitalize(x)) for x in fields] 319 320 # there might not be a clear event, so set empty defaults 321 data.update({}.fromkeys(cfields, "")) 322 323 # pull in the clear event data 324 for values in self.query(q): 325 values = map(zem.convert, fields, values) 326 data.update(dict(zip(cfields, values))) 327 328 # If our event has a clearid, but we have no clear data it means 329 # that we're in a small delay before it is inserted. We'll wait 330 # until next time to deal with the clear. 331 if data.get('clearid', None) and not data.get('clearEvid', None): 332 continue 333 334 data['clearOrEventSummary'] = ( 335 data['clearSummary'] or data['summary']) 336 337 # We want to insert the ownerid and stateChange fields into the 338 # clearSummary and clearFirstTime fields in the case where an 339 # event was manually cleared by an operator. 340 if not data.get('clearSummary', False) \ 341 and data.get('ownerid', False): 342 data['clearSummary'] = data['ownerid'] 343 data['clearFirstTime'] = data.get('stateChange', '') 344 345 # add in the link to the url 346 device = self.dmd.Devices.findDevice(data.get('device', None)) 347 data['eventUrl'] = self.getEventUrl(evid, device) 348 data['undeleteUrl'] = self.getUndeleteUrl(evid, device) 349 severity = data.get('severity', -1) 350 data['severityString'] = zem.getSeverityString(severity) 351 delcmd = self.clearstate % (evid, userid, context.getId()) 352 if getattr(context, 'sendClear', True): 353 if action(context, data, True): 354 self.execute(delcmd) 355 else: 356 self.execute(delcmd)
357 358
359 - def maintenance(self, zem):
360 """Run stored procedures that maintain the events database. 361 """ 362 sql = 'call age_events(%s, %s);' % ( 363 zem.eventAgingHours, zem.eventAgingSeverity) 364 try: 365 self.execute(sql) 366 except ProgrammingError: 367 self.log.exception("problem with proc: '%s'" % sql)
368 369
370 - def deleteHistoricalEvents(self, deferred=False, force=False):
371 """ 372 Once per day delete events from history table. 373 If force then run the deletion statement regardless of when it was 374 last run (the deletion will still not run if the historyMaxAgeDays 375 setting in the event manager is not greater than zero.) 376 If deferred then we are running in a twisted reactor. Run the 377 deletion script in a non-blocking manner (if it is to be run) and 378 return a deferred (if the deletion script is run.) 379 In all cases return None if the deletion script is not run. 380 """ 381 import datetime 382 import os 383 import twisted.internet.utils 384 import Products.ZenUtils.Utils as Utils 385 import transaction 386 import subprocess 387 388 def onSuccess(unused, startTime): 389 self.log.info('Done deleting historical events in %.2f seconds' % 390 (time.time() - startTime)) 391 return None
392 def onError(error, startTime): 393 self.log.error('Error deleting historical events after ' 394 '%s seconds: %s' % (time.time()-startTime, 395 error)) 396 return None
397 398 # d is the return value. It is a deferred if the deferred argument 399 # is true and if we run the deletion script. Otherwise it is None 400 d = None 401 402 # Unless the event manager has a positive number of days for its 403 # historyMaxAgeDays setting then there is never any point in 404 # performing the deletion. 405 try: 406 maxDays = int(self.dmd.ZenEventManager.historyMaxAgeDays) 407 except ValueError: 408 maxDays = 0 409 if maxDays > 0: 410 # lastDeleteHistoricalEvents_datetime is when the deletion 411 # script was last run 412 lastRun = getattr(self.dmd, 413 'lastDeleteHistoricalEvents_datetime', None) 414 # lastDeleteHistoricalEvents_days is the value of historyMaxAgeDays 415 # the last time the deletion script was run. If this value has 416 # changed then we run the script again regardless of when it was 417 # last run. 418 lastAge = getattr(self.dmd, 419 'lastDeleteHistoricalEvents_days', None) 420 now = datetime.datetime.now() 421 if not lastRun \ 422 or now - lastRun > datetime.timedelta(1) \ 423 or lastAge != maxDays \ 424 or force: 425 self.log.info('Deleting historical events older than %s days' % 426 maxDays) 427 startTime = time.time() 428 cmd = Utils.zenPath('Products', 'ZenUtils', 429 'ZenDeleteHistory.py') 430 args = ['--numDays=%s' % maxDays] 431 if deferred: 432 # We're in a twisted reactor, so make a twisty call 433 d = twisted.internet.utils.getProcessOutput( 434 cmd, args, os.environ, errortoo=True) 435 d.addCallback(onSuccess, startTime) 436 d.addErrback(onError, startTime) 437 else: 438 # Not in a reactor, so do this in a blocking manner 439 proc = subprocess.Popen( 440 [cmd]+args, stdout=subprocess.PIPE, 441 stderr=subprocess.STDOUT, env=os.environ) 442 # Trying to mimic how twisted returns results to us 443 # sort of. 444 output, _ = proc.communicate() 445 code = proc.wait() 446 if code: 447 onError(output, startTime) 448 else: 449 onSuccess(output, startTime) 450 # Record circumstances of this run 451 self.dmd.lastDeleteHistoricalEvents_datetime = now 452 self.dmd.lastDeleteHistoricalEvents_days = maxDays 453 transaction.commit() 454 return d 455 456
457 - def fetchMonitorHostname(self, monitor='localhost'):
458 if monitor in self.monitorToHost: 459 return self.monitorToHost[monitor] 460 461 all_monitors = self.dmd.Monitors.getPerformanceMonitorNames() 462 if monitor in all_monitors: 463 hostname = self.dmd.Monitors.getPerformanceMonitor(monitor).hostname 464 else: 465 # Someone's put in something that we don't expect 466 hostname = monitor 467 468 if hostname == 'localhost': 469 hostname = self.daemonHostname 470 471 self.monitorToHost[monitor] = hostname 472 return hostname
473
474 - def heartbeatEvents(self):
475 """Create events for failed heartbeats. 476 """ 477 # build cache of existing heartbeat issues 478 q = ("SELECT monitor, component " 479 "FROM status WHERE eventClass = '%s'" % Status_Heartbeat) 480 heartbeatState = Set(self.query(q)) 481 482 # Find current heartbeat failures 483 # Note: 'device' in the heartbeat table is actually filled with the 484 # collector name 485 sel = "SELECT device, component FROM heartbeat " 486 sel += "WHERE DATE_ADD(lastTime, INTERVAL timeout SECOND) <= NOW();" 487 for monitor, comp in self.query(sel): 488 hostname = self.fetchMonitorHostname(monitor) 489 self.sendEvent( 490 Event.Event(device=hostname, component=comp, 491 eventClass=Status_Heartbeat, 492 summary="%s %s heartbeat failure" % (monitor, comp), 493 prodState=self.prodState, 494 monitor=monitor, 495 severity=Event.Error)) 496 heartbeatState.discard((monitor, comp)) 497 498 # clear heartbeats 499 for monitor, comp in heartbeatState: 500 hostname = self.fetchMonitorHostname(monitor) 501 self.sendEvent( 502 Event.Event(device=hostname, component=comp, 503 eventClass=Status_Heartbeat, 504 summary="%s %s heartbeat clear" % (monitor, comp), 505 severity=Event.Clear))
506
507 - def runEventCommand(self, cmd, data, clear = None):
508 try: 509 command = cmd.command 510 if clear: 511 command = cmd.clearCommand 512 device = self.dmd.Devices.findDevice(data.get('device', '')) 513 component = None 514 if device: 515 componentName = data.get('component') 516 for c in device.getMonitoredComponents(): 517 if c.id == componentName: 518 component = c 519 break 520 compiled = talesCompile('string:' + command) 521 environ = {'dev':device, 'component':component, 'evt':data } 522 res = compiled(getEngine().getContext(environ)) 523 if isinstance(res, Exception): 524 raise res 525 prot = EventCommandProtocol(cmd, self) 526 self.log.info('Running %s' % res) 527 reactor.spawnProcess(prot, '/bin/sh', 528 ('/bin/sh', '-c', res), 529 env=None) 530 except Exception: 531 self.log.exception('Error running command %s', cmd.id) 532 return True
533 534
535 - def eventCommands(self, zem):
536 now = time.time() 537 count = 0 538 for command in zem.commands(): 539 if command.enabled: 540 count += 1 541 self.processEvent(zem, command, self.runEventCommand) 542 self.log.info("Processed %d commands in %f", count, time.time() - now)
543 544
545 - def mainbody(self):
546 """main loop to run actions. 547 """ 548 from twisted.internet.process import reapAllProcesses 549 reapAllProcesses() 550 zem = self.dmd.ZenEventManager 551 self.loadActionRules() 552 self.eventCommands(zem) 553 self.processRules(zem) 554 self.checkVersion(zem) 555 self.maintenance(zem) 556 self.deleteHistoricalEvents(deferred=self.options.cycle) 557 self.heartbeatEvents()
558 559
560 - def runCycle(self):
561 try: 562 start = time.time() 563 self.syncdb() 564 self.mainbody() 565 self.log.info("processed %s rules in %.2f secs", 566 len(self.actions), time.time()-start) 567 self.sendHeartbeat() 568 except: 569 self.log.exception("unexpected exception") 570 reactor.callLater(self.options.cycletime, self.runCycle)
571 572
573 - def run(self):
574 self.prodState = filter(lambda x: x.split(':')[0] == 'Production', 575 self.dmd.prodStateConversions) 576 import socket 577 self.daemonHostname = socket.getfqdn() 578 self.monitorToHost = {} 579 try: 580 # eg ['Production:1000'] 581 self.prodState = int(self.prodState[0].split(':')[1]) 582 except: 583 self.prodState = 1000 584 585 if not self.options.cycle: 586 self.sendHeartbeat() 587 self.schedule.run() 588 return self.mainbody() 589 self.schedule.start() 590 self.runCycle() 591 reactor.run()
592 593
594 - def sendEvent(self, evt):
595 """Send event to the system. 596 """ 597 self.dmd.ZenEventManager.sendEvent(evt)
598 599
600 - def sendHeartbeat(self):
601 """Send a heartbeat event for this monitor. 602 """ 603 timeout = self.options.cycletime*3 604 evt = Event.EventHeartbeat(self.options.monitor, "zenactions", timeout) 605 self.sendEvent(evt) 606 self.niceDoggie(self.options.cycletime)
607 608
609 - def stop(self):
610 self.running = False 611 self.log.info("stopping") 612 self.sendEvent(Event.Event(device=self.options.monitor, 613 eventClass=App_Stop, 614 summary="zenactions stopped", 615 severity=3, component="zenactions"))
616
617 - def format(self, action, data, clear):
618 fmt = action.format 619 body = action.body 620 if clear: 621 fmt = action.clearFormat 622 body = action.clearBody 623 try: 624 fmt = fmt % data 625 except Exception, ex: 626 fmt = "Error formatting event: %s" % (str(ex),) 627 try: 628 body = body % data 629 except Exception, ex: 630 body = "Error formatting event body: %s" % (str(ex),) 631 return fmt, body
632
633 - def stripTags(self, data):
634 """A quick html => plaintext converter 635 that retains and displays anchor hrefs 636 """ 637 import re 638 tags = re.compile(r'<(.|\n)+?>', re.I|re.M) 639 aattrs = re.compile(r'<a(.|\n)+?href=["\']([^"\']*)[^>]*?>([^<>]*?)</a>', re.I|re.M) 640 anchors = re.finditer(aattrs, data) 641 for x in anchors: data = data.replace(x.group(), "%s: %s" % (x.groups()[2], x.groups()[1])) 642 data = re.sub(tags, '', data) 643 return data
644
645 - def sendPage(self, action, data, clear = None):
646 """Send and event to a pager. Return True if we think page was sent, 647 False otherwise. 648 """ 649 fmt, body = self.format(action, data, clear) 650 recipients = action.getAddresses() 651 if not recipients: 652 self.log.warning('failed to page %s on rule %s: %s', 653 action.getUser().id, action.id, 654 'Unspecified address.') 655 return True 656 657 result = False 658 for recipient in recipients: 659 success, errorMsg = Utils.sendPage(recipient, 660 fmt, 661 self.dmd.pageCommand) 662 if success: 663 self.log.info('sent page to %s: %s', recipient, fmt) 664 # return True if anyone got the page 665 result = result or success 666 else: 667 self.log.info('failed to send page to %s: %s %s', 668 recipient, 669 fmt, 670 errorMsg) 671 return result
672 673 674
675 - def sendEmail(self, action, data, clear = None):
676 """Send an event to an email address. 677 Return True if we think the email was sent, False otherwise. 678 """ 679 from email.MIMEText import MIMEText 680 from email.MIMEMultipart import MIMEMultipart 681 addr = action.getAddresses() 682 if not addr: 683 self.log.warning('failed to email %s on rule %s: %s', 684 action.getUser().id, action.id, 'Unspecified address.') 685 return True 686 687 fmt, htmlbody = self.format(action, data, clear) 688 htmlbody = htmlbody.replace('\n','<br/>\n') 689 body = self.stripTags(htmlbody) 690 plaintext = MIMEText(body) 691 692 emsg = None 693 if action.plainText: 694 emsg = plaintext 695 else: 696 emsg = MIMEMultipart('related') 697 emsgAlternative = MIMEMultipart('alternative') 698 emsg.attach( emsgAlternative ) 699 html = MIMEText(htmlbody) 700 html.set_type('text/html') 701 emsgAlternative.attach(plaintext) 702 emsgAlternative.attach(html) 703 704 emsg['Subject'] = fmt 705 emsg['From'] = self.dmd.getEmailFrom() 706 emsg['To'] = ', '.join(addr) 707 emsg['Date'] = formatdate(None, True) 708 result, errorMsg = Utils.sendEmail(emsg, self.dmd.smtpHost, 709 self.dmd.smtpPort, self.dmd.smtpUseTLS, self.dmd.smtpUser, 710 self.dmd.smtpPass) 711 if result: 712 self.log.info("rule '%s' sent email:%s to:%s", 713 action.id, fmt, addr) 714 else: 715 self.log.info("rule '%s' failed to send email to %s: %s %s", 716 action.id, ','.join(addr), fmt, errorMsg) 717 return result
718 719
720 - def buildOptions(self):
721 ZCmdBase.buildOptions(self) 722 self.parser.add_option('--cycletime', 723 dest='cycletime', default=60, type="int", 724 help="check events every cycletime seconds") 725 self.parser.add_option( 726 '--zopeurl', dest='zopeurl', 727 default='http://%s:%d' % (socket.getfqdn(), 8080), 728 help="http path to the root of the zope server") 729 self.parser.add_option("--monitor", dest="monitor", 730 default=DEFAULT_MONITOR, 731 help="Name of monitor instance to use for heartbeat " 732 " events. Default is %s." % DEFAULT_MONITOR)
733 734
735 - def sigTerm(self, signum=None, frame=None):
736 'controlled shutdown of main loop on interrupt' 737 try: 738 ZCmdBase.sigTerm(self, signum, frame) 739 except SystemExit: 740 reactor.stop()
741 742 if __name__ == "__main__": 743 za = ZenActions() 744 import logging 745 logging.getLogger('zen.Events').setLevel(20) 746 za.run() 747