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

# Source Code for Module Products.ZenEvents.Schedule

      1  #! /usr/bin/env python
      2  ##############################################################################
      3  #
      4  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      5  #
      6  # This content is made available according to terms specified in
      7  # License.zenoss under the directory where your Zenoss product is installed.
      8  #
      9  ##############################################################################
     10
     11
     12  __doc__='''Schedule
     13
     14  Walk through the maintenance schedule.
     15
     16  $Id$
     17  '''
     18
     19  import time
     20  import logging
     21  from twisted.internet import reactor
     22
     23  from ZODB.transact import transact
     24  from Products.ZenEvents.ZenEventClasses import Status_Update
     25  from Products.ZenEvents import Event
     26
     27 -class Schedule:
     28
     29 -    def __init__(self, options, dmd):
     30          "start executing the schedule"
     31          self.dmd = dmd
     32          self.maintenance = []
     33          self.options = options
     34          self.log = logging.getLogger("zen.Schedule")
     35          self.workList = []
     36          self.timer = None
     37
     38
     39 -    def buildOptions(self, parser):
     40          "Set options in a borrowed parser"
     41
     42
     43 -    def start(self):
     44          "Start working the schedule"
     45          self.configCycle()
     46
     47
     48 -    def configCycle(self):
     49          "Basic event-driven config loop"
     50          try:
     51              self.run()
     52          except Exception:
     53              self.log.exception("Error processing maintenance windows - will try again in % seconds", self.options.maintenceWindowCycletime)
     54          reactor.callLater(self.options.maintenceWindowCycletime, 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              self.timer = None
    133
    134          # sort events by the next occurance of something to do
    135          now = self.now()
    136          work = self.makeWorkList(now, self.workList)
    137          self.workList = [mw for t, mw in work]
    138          # fire events that should be done now
    139          for next, mw in work:
    140              if next <= now:
    141                  how = {True:'stopping', False:'starting'}[bool(mw.started)]
    142                  severity = {True:Event.Clear, False:Event.Info}[bool(mw.started)]
    143                  # Note: since the MWs always return devices back to their original
    144                  #       prod state, and there may be many devices, just provide an
    145                  #       'unknown' production state for stopping
    146                  prodState = {True:-99, False:mw.startProductionState}[bool(mw.started)]
    147                  mwId = mw.getId()
    148                  devices = mw.target().getId()
    149                  msg = "Maintenance window %s %s for %s" % (how, mwId, devices)
    150                  self.log.debug(msg)
    151                  dedupid = '|'.join(["zenjobs",self.monitor,mwId,devices])
    152                  self.sendEvent(Event.Event(
    153                      component="zenjobs",
    154                      severity=severity,
    155                      dedupid=dedupid,
    156                      eventClass=Status_Update,
    157                      eventClassKey="mw_change",
    158                      summary=msg,
    159                      eventKey='|'.join([mwId,devices]),
    160                      maintenance_window=mwId,
    161                      maintenance_devices=devices,
    162                      device=self.monitor,
    163                      prodState=prodState,
    164                  ))
    165                  self.executeMaintenanceWindow(mw, next)
    166              else:
    167                  break
    168
    169          work = self.makeWorkList(now, self.workList)
    170          if work:
    171              wait = max(0, work[0][0] - now)
    172              self.log.debug("Waiting %f seconds", wait)
    173              self.timer = self.callLater(wait)
    174
    175 -    def callLater(self, seconds):
    176          return reactor.callLater(seconds, self.runEvents)
    177
    178      @transact
    179 -    def executeMaintenanceWindow(self, mw, timestamp):
    180          mw.execute(timestamp)
    181
    182  if __name__ == "__main__":
    183 -    class MySchedule(Schedule):
    184          currentTime = time.time()
    185          objs = None
    186 -        def now(self):
    187              return self.currentTime
    188 -        def callLater(self, seconds):
    189              self.currentTime += seconds
    190 -        def executeMaintenanceWindow(self, mw, timestamp):
    191              print 'executing', mw.id, time.ctime(timestamp)
    192              mw.execute(timestamp)
    193 -        def getWindows(self):
    194              if self.workList:
    195                  return self.workList
    196              return Schedule.getWindows(self)
    197 -        def commit(self):
    198              pass
    199          sync = commit
    200
    201      import Globals
    202      from Products.ZenUtils.ZCmdBase import ZCmdBase
    203
    204      cmd = ZCmdBase()
    205 -    class Options: pass
    206      s = MySchedule(Options(), cmd.dmd)
    207      # compute the schedule for 30 days
    208      end = s.currentTime + 60*60*24*30
    209      while s.currentTime < end:
    210          s.run()
    211