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

# Source Code for Module Products.ZenEvents.Schedule

      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 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  #! /usr/bin/env python
     14
     15  __doc__='''Schedule
     16
     17  Walk through the maintenance schedule.
     18
     19  $Id$
     20  '''
     21
     22  import time
     23  import logging
     24  from twisted.internet import reactor
     25
     26  from ZODB.transact import transact
     27  from Products.ZenEvents.ZenEventClasses import Status_Update
     28  from Products.ZenEvents import Event
     29
     30 -class Schedule:
     31
     32 -    def __init__(self, options, dmd):
     33          "start executing the schedule"
     34          self.dmd = dmd
     35          self.maintenance = []
     36          self.options = options
     37          self.log = logging.getLogger("zen.Schedule")
     38          self.workList = []
     39          self.timer = None
     40
     41
     42 -    def buildOptions(self, parser):
     43          "Set options in a borrowed parser"
     44
     45
     46 -    def start(self):
     47          "Start working the schedule"
     48          self.configCycle()
     49
     50
     51 -    def configCycle(self):
     52          "Basic event-driven config loop"
     53          self.run()
     54          reactor.callLater(self.options.cycletime, self.configCycle)
     55
     56
     57 -    def sync(self):
     58          "Synch with the database"
     59          self.dmd._p_jar.sync()
     60
     61 -    def getWindows(self):
     62          result = []
     63          catalog = getattr(self.dmd, 'maintenanceWindowSearch', None)
     64          if catalog is not None:
     65              for brain in catalog():
     66                  try:
     67                      ob = brain.getObject()
     68                  except KeyError:
     69                      # We're just catching the symptom for now, but an actual
     70                      # fix will be forthcoming.
     71                      # http://dev.zenoss.org/trac/ticket/3105
     72                      pass
     73                  else:
     74                      result.append(ob)
     75          else: # Should be removed in 2.3.
     76              self.log.warn('Run zenmigrate to index your maintenance windows.')
     77              for dev in self.dmd.Devices.getSubDevices():
     78                  result.extend(dev.maintenanceWindows())
     79              for name in 'Systems', 'Locations', 'Groups', 'Devices':
     80                  organizer = getattr(self.dmd, name)
     81                  for c in organizer.getSubOrganizers():
     82                      result.extend(c.maintenanceWindows())
     83                  result.extend(organizer.maintenanceWindows())
     84              for lst in [self.dmd.ZenUsers.getAllUserSettings(),
     85                          self.dmd.ZenUsers.getAllGroupSettings()]:
     86                  for us in lst:
     87                      for ar in us.objectValues(spec="ActionRule"):
     88                          result.extend(w for w in ar.windows() if w.enabled)
     89          return result
     90
     91 -    def run(self):
     92          "Re-read work list from the database"
     93          self.sync()
     94          self.workList = self.getWindows()
     95          self.runEvents()
     96
     97
     98      @transact
     99 -    def makeWorkList(self, now, workList):
    100          """
    101          Returns the list of tuples where 0 is the next time the
    102          window should run and the 1 index is the window itself.
    103          If there is no next run and the window has started this
    104          method ends the windows.
    105
    106          This method is wrapped in a transact block because there
    107          is the chance that we could set the production state on
    108          devices if the "end" method is called.
    109          """
    110          work = [(mw.nextEvent(now), mw) for mw in workList]
    111          work.sort()
    112          # note that None is less than any number of seconds
    113          while len(work):
    114              t, mw = work[0]
    115              if t: break
    116              if mw.enabled:
    117                  self.log.debug("Never going to run Maintenance "
    118                                 "Window %s for %s again",
    119                                 mw.getId(), mw.target().getId())
    120              if mw.started:
    121                  mw.end()
    122              work.pop(0)
    123          return work
    124
    125 -    def now(self):
    126          return time.time()
    127
    128 -    def runEvents(self):
    129          "Execute all the maintanance windows at the proper time"
    130          if self.timer and not self.timer.called:
    131              self.timer.cancel()
    132
    133          # sort events by the next occurance of something to do
    134          now = self.now()
    135          work = self.makeWorkList(now, self.workList)
    136          self.workList = [mw for t, mw in work]
    137          # fire events that should be done now
    138          for next, mw in work:
    139              if next <= now:
    140                  how = {True:'stopping', False:'starting'}[bool(mw.started)]
    141                  severity = {True:Event.Clear, False:Event.Info}[bool(mw.started)]
    142                  # Note: since the MWs always return devices back to their original
    143                  #       prod state, and there may be many devices, just provide an
    144                  #       'unknown' production state for stopping
    145                  prodState = {True:-99, False:mw.startProductionState}[bool(mw.started)]
    146                  mwId = mw.getId()
    147                  devices = mw.target().getId()
    148                  msg = "Maintenance window %s %s for %s" % (how, mwId, devices)
    149                  self.log.debug(msg)
    150                  dedupid = '|'.join(["zenjobs",self.monitor,mwId,devices])
    151                  self.sendEvent(Event.Event(
    152                      component="zenjobs",
    153                      severity=severity,
    154                      dedupid=dedupid,
    155                      eventClass=Status_Update,
    156                      eventClassKey="mw_change",
    157                      summary=msg,
    158                      eventKey='|'.join([mwId,devices]),
    159                      maintenance_window=mwId,
    160                      maintenance_devices=devices,
    161                      device=self.monitor,
    162                      prodState=prodState,
    163                  ))
    164                  self.executeMaintenanceWindow(mw, next)
    165              else:
    166                  break
    167
    168          work = self.makeWorkList(now, self.workList)
    169          if work:
    170              wait = max(0, work[0][0] - now)
    171              self.log.debug("Waiting %f seconds", wait)
    172              self.timer = self.callLater(wait)
    173
    174 -    def callLater(self, seconds):
    175          return reactor.callLater(seconds, self.runEvents)
    176
    177      @transact
    178 -    def executeMaintenanceWindow(self, mw, timestamp):
    179          mw.execute(timestamp)
    180
    181  if __name__ == "__main__":
    182 -    class MySchedule(Schedule):
    183          currentTime = time.time()
    184          objs = None
    185 -        def now(self):
    186              return self.currentTime
    187 -        def callLater(self, seconds):
    188              self.currentTime += seconds
    189 -        def executeMaintenanceWindow(self, mw, timestamp):
    190              print 'executing', mw.id, time.ctime(timestamp)
    191              mw.execute(timestamp)
    192 -        def getWindows(self):
    193              if self.workList:
    194                  return self.workList
    195              return Schedule.getWindows(self)
    196 -        def commit(self):
    197              pass
    198          sync = commit
    199
    200      import Globals
    201      from Products.ZenUtils.ZCmdBase import ZCmdBase
    202
    203      cmd = ZCmdBase()
    204 -    class Options: pass
    205      s = MySchedule(Options(), cmd.dmd)
    206      # compute the schedule for 30 days
    207      end = s.currentTime + 60*60*24*30
    208      while s.currentTime < end:
    209          s.run()
    210