Package Products :: Package ZenWin :: Module zenwin |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenWin.zenwin

      1  #! /usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  # ##########################################################################
      4  #
      5  # This program is part of Zenoss Core, an open source monitoring platform.
      6  # Copyright (C) 2006-2009 Zenoss Inc.
      7  #
      8  # This program is free software; you can redistribute it and/or modify it
      9  # under the terms of the GNU General Public License version 2 as published by
     10  # the Free Software Foundation.
     11  #
     12  # For complete information please visit: http://www.zenoss.com/oss/
     13  #
     14  # ##########################################################################
     15
     16  """
     17  This module provides a collector daemon that polls Windows devices for changes
     18  to Windows services. Retrieved status is then converted into Zenoss events
     19  and sent back to ZenHub for further processing.
     20  """
     21
     22  import logging
     23
     24  # IMPORTANT! The import of the pysamba.twisted.reactor module should come before
     25  # any other libraries that might possibly use twisted. This will ensure that
     26  # the proper WmiReactor is installed before anyone else grabs a reference to
     27  # the wrong reactor.
     28  import pysamba.twisted.reactor
     29
     30  import Globals
     31  import zope.component
     32  import zope.interface
     33
     34  from twisted.internet import defer, reactor
     35  from twisted.python.failure import Failure
     36
     37  from Products.ZenCollector.daemon import CollectorDaemon
     38  from Products.ZenCollector.interfaces import ICollectorPreferences,\
     39                                               IEventService,\
     40                                               IScheduledTask
     41  from Products.ZenCollector.tasks import SimpleTaskFactory,\
     42                                          SimpleTaskSplitter,\
     43                                          TaskStates
     44  from Products.ZenEvents.ZenEventClasses import Error, Clear, Status_WinService
     45  from Products.ZenUtils.observable import ObservableMixin
     46  from Products.ZenWin.WMIClient import WMIClient
     47  from Products.ZenWin.Watcher import Watcher
     48  from Products.ZenWin.utils import addNTLMv2Option, setNTLMv2Auth
     49
     50  # We retrieve our configuration data remotely via a Twisted PerspectiveBroker
     51  # connection. To do so, we need to import the class that will be used by the
     52  # configuration service to send the data over, i.e. DeviceProxy.
     53  from Products.ZenUtils.Utils import unused
     54  from Products.ZenCollector.services.config import DeviceProxy
     55  unused(DeviceProxy)
     56
     57  #
     58  # creating a logging context for this module to use
     59  #
     60  log = logging.getLogger("zen.zenwin")
     61
     62  # Create an implementation of the ICollectorPreferences interface so that the
     63  # ZenCollector framework can configure itself from our preferences.
     64 -class ZenWinPreferences(object):
     65      zope.interface.implements(ICollectorPreferences)
     66
     67 -    def __init__(self):
     68          """
     69          Construct a new ZenWinPreferences instance and provide default
     70          values for needed attributes.
     71          """
     72          self.collectorName = "zenwin"
     73          self.defaultRRDCreateCommand = None
     74          self.cycleInterval = 5 * 60 # seconds
     75          self.configCycleInterval = 20 # minutes
     76          self.options = None
     77
     78          # the configurationService attribute is the fully qualified class-name
     79          # of our configuration service that runs within ZenHub
     80          self.configurationService = 'Products.ZenWin.services.WinServiceConfig'
     81
     82          self.wmibatchSize = 10
     83          self.wmiqueryTimeout = 1000
     84
     85 -    def buildOptions(self, parser):
     86          parser.add_option('--debug', dest='debug', default=False,
     87                                 action='store_true',
     88                                 help='Increase logging verbosity.')
     89          parser.add_option('--proxywmi', dest='proxywmi',
     90                                 default=False, action='store_true',
     91                                 help='Use a process proxy to avoid long-term blocking'
     92                                 )
     93          parser.add_option('--queryTimeout', dest='queryTimeout',
     94                                 default=None, type='int',
     95                                 help='The number of milliseconds to wait for ' + \
     96                                      'WMI query to respond. Overrides the ' + \
     97                                      'server settings.')
     98          parser.add_option('--batchSize', dest='batchSize',
     99                                 default=None, type='int',
    100                                 help='Number of data objects to retrieve in a ' +
    101                                      'single WMI query.')
    102          addNTLMv2Option(parser)
    103
    104 -    def postStartup(self):
    105          # turn on low-level pysamba debug logging if requested
    106          logseverity = self.options.logseverity
    107          if logseverity <= 5:
    108              pysamba.library.DEBUGLEVEL.value = 99
    109
    110          # force NTLMv2 authentication if requested
    111          setNTLMv2Auth(self.options)
    112
    113
    114 -class ZenWinTask(ObservableMixin):
    115      zope.interface.implements(IScheduledTask)
    116
    117      STATE_WMIC_CONNECT = 'WMIC_CONNECT'
    118      STATE_WMIC_QUERY = 'WMIC_QUERY'
    119      STATE_WMIC_PROCESS = 'WMIC_PROCESS'
    120      STATE_WATCHER_CONNECT = 'WATCHER_CONNECT'
    121      STATE_WATCHER_QUERY = 'WATCHER_QUERY'
    122      STATE_WATCHER_PROCESS = 'WATCHER_PROCESS'
    123
    124 -    def __init__(self,
    125                   deviceId,
    126                   taskName,
    127                   scheduleIntervalSeconds,
    128                   taskConfig):
    129          """
    130          Construct a new task instance to watch for Windows Event Log changes
    131          for the specified device.
    132
    133          @param deviceId: the Zenoss deviceId to watch
    134          @type deviceId: string
    135          @param taskName: the unique identifier for this task
    136          @type taskName: string
    137          @param scheduleIntervalSeconds: the interval at which this task will be
    138                 collected
    139          @type scheduleIntervalSeconds: int
    140          @param taskConfig: the configuration for this task
    141          """
    142          super(ZenWinTask, self).__init__()
    143
    144          self.name = taskName
    145          self.configId = deviceId
    146          self.interval = scheduleIntervalSeconds
    147          self.state = TaskStates.STATE_IDLE
    148
    149          self._taskConfig = taskConfig
    150          self._devId = deviceId
    151          self._manageIp = self._taskConfig.manageIp
    152
    153          self._eventService = zope.component.queryUtility(IEventService)
    154          self._preferences = zope.component.queryUtility(ICollectorPreferences,
    155                                                          "zenwin")
    156
    157          # if the user hasn't specified the batchSize or queryTimeout as command
    158          # options then use whatever has been specified in the collector
    159          # preferences
    160          # TODO: convert these to zProperties
    161          self._batchSize = self._preferences.options.batchSize
    162          if not self._batchSize:
    163              self._batchSize = self._preferences.wmibatchSize
    164          self._queryTimeout = self._preferences.options.queryTimeout
    165          if not self._queryTimeout:
    166              self._queryTimeout = self._preferences.wmiqueryTimeout
    167
    168          self._wmic = None # the WMIClient
    169          self._watcher = None
    170          self._reset()
    171
    172 -    def _reset(self):
    173          """
    174          Reset the WMI client and notification query watcher connection to the
    175          device, if they are presently active.
    176          """
    177          if self._wmic:
    178              self._wmic.close()
    179          self._wmic = None
    180          if self._watcher:
    181              self._watcher.close()
    182          self._watcher = None
    183
    184 -    def _finished(self, result):
    185          """
    186          Callback activated when the task is complete so that final statistics
    187          on the collection can be displayed.
    188          """
    189          if not isinstance(result, Failure):
    190              log.debug("Device %s [%s] scanned successfully",
    191                        self._devId, self._manageIp)
    192          else:
    193              log.debug("Device %s [%s] scanned failed, %s",
    194                        self._devId, self._manageIp, result.getErrorMessage())
    195
    196          # give the result to the rest of the callback/errchain so that the
    197          # ZenCollector framework can keep track of the success/failure rate
    198          return result
    199
    200 -    def _failure(self, result):
    201          """
    202          Errback for an unsuccessful asynchronous connection or collection
    203          request.
    204          """
    205          err = result.getErrorMessage()
    206          log.error("Unable to scan device %s: %s", self._devId, err)
    207
    208          self._reset()
    209
    210          summary = """
    211              Could not read Windows services (%s). Check your
    212              username/password settings and verify network connectivity.
    213              """ % err
    214
    215          self._eventService.sendEvent(dict(
    216              summary=summary,
    217              component='zenwin',
    218              eventClass=Status_WinService,
    219              device=self._devId,
    220              severity=Error,
    221              agent='zenwin',
    222              ))
    223
    224          # give the result to the rest of the errback chain
    225          return result
    226
    227 -    def _handleResult(self, name, state):
    228          """
    229          Handle a result from the wmi query. Results from both the initial WMI
    230          client query and the watcher's notification query are processed by
    231          this method. Log running and stopped transitions. Send an event if the
    232          service is monitored.
    233          """
    234          services = self._taskConfig.services
    235          stateDct = {'running': (Clear,              log.info),
    236                      'stopped': (services.get(name), log.critical)}
    237          if state in stateDct:
    238              summary = "Windows service '%s' is %s" % (name, state)
    239              if name in services:
    240                  # monitoring is enabled
    241                  severity, writeToLog = stateDct[state]
    242                  event = {'summary': summary,
    243                           'eventClass': Status_WinService,
    244                           'device': self._devId,
    245                           'severity': severity,
    246                           'agent': 'newzenwin',
    247                           'component': name,
    248                           'eventGroup': 'StatusTest'}
    249                  self._eventService.sendEvent(event)
    250              else:
    251                  # monitoring is disabled
    252                  writeToLog = log.debug
    253              writeToLog('%s on %s' % (summary, self._devId))
    254
    255 -    def _collectSuccessful(self, results):
    256          """
    257          Callback for a successful fetch of services from the remote device.
    258          """
    259          self.state = ZenWinTask.STATE_WATCHER_PROCESS
    260
    261          log.debug("Successful collection from %s [%s], results=%s",
    262                    self._devId, self._manageIp, results)
    263
    264          if results:
    265              for result in [r.targetInstance for r in results]:
    266                  if result.state:
    267                      self._handleResult(result.name, result.state.lower())
    268
    269              # schedule another immediate collection so that we'll keep eating
    270              # events as long as they are ready for us; using callLater ensures
    271              # it goes to the end of the immediate work-queue so that other
    272              # events get processing time
    273              log.debug("Queuing another fetch for %s [%s]",
    274                        self._devId, self._manageIp)
    275              d = defer.Deferred()
    276              reactor.callLater(0, d.callback, None)
    277              d.addCallback(self._collectCallback)
    278              return d
    279
    280 -    def _collectCallback(self, result):
    281          """
    282          Callback called after a connect or previous collection so that another
    283          collection can take place.
    284          """
    285          log.debug("Polling for events from %s [%s]",
    286                    self._devId, self._manageIp)
    287
    288          self.state = ZenWinTask.STATE_WATCHER_QUERY
    289          d = self._watcher.getEvents(self._queryTimeout, self._batchSize)
    290          d.addCallbacks(self._collectSuccessful, self._failure)
    291          return d
    292
    293 -    def _connectCallback(self, result):
    294          """
    295          Callback called after a successful connect to the remote Windows device.
    296          """
    297          log.debug("Connected to %s [%s]", self._devId, self._manageIp)
    298
    299 -    def _connectWatcher(self, result):
    300          self.state = ZenWinTask.STATE_WMIC_PROCESS
    301          running = [service.name for service in result['query']]
    302          for name in running:
    303              self._handleResult(name, 'running')
    304          for name in self._taskConfig.services:
    305              if name not in running:
    306                  self._handleResult(name, 'stopped')
    307          self._wmic.close()
    308          self._wmic = None
    309          self.state = ZenWinTask.STATE_WATCHER_CONNECT
    310          wql = "SELECT * FROM __InstanceModificationEvent WITHIN 5 "\
    311                "WHERE TargetInstance ISA 'Win32_Service'"
    312          self._watcher = Watcher(self._taskConfig, wql)
    313          return self._watcher.connect()
    314
    315 -    def _initialQuery(self, result):
    316          self.state = ZenWinTask.STATE_WMIC_QUERY
    317          wql = "SELECT Name FROM Win32_Service WHERE State='Running'"
    318          d = self._wmic.query({'query': wql})
    319          d.addCallback(self._connectWatcher)
    320          return d
    321
    322 -    def _connect(self):
    323          """
    324          Called when a connection needs to be created to the remote Windows
    325          device.
    326          """
    327          log.debug("Connecting to %s [%s]", self._devId, self._manageIp)
    328          self.state = ZenWinTask.STATE_WMIC_CONNECT
    329          self._wmic = WMIClient(self._taskConfig)
    330          d = self._wmic.connect()
    331          d.addCallback(self._initialQuery)
    332          return d
    333
    334 -    def cleanup(self):
    335          return self._reset()
    336
    337 -    def doTask(self):
    338          log.debug("Scanning device %s [%s]", self._devId, self._manageIp)
    339
    340          # see if we need to connect first before doing any collection
    341          if not self._watcher:
    342              d = self._connect()
    343              d.addCallbacks(self._connectCallback, self._failure)
    344          else:
    345              # since we don't need to bother connecting, we'll just create an
    346              # empty deferred and have it run immediately so the collect callback
    347              # will be fired off
    348              d = defer.Deferred()
    349              reactor.callLater(0, d.callback, None)
    350
    351          # try collecting events after a successful connect, or if we're already
    352          # connected
    353          d.addCallback(self._collectCallback)
    354
    355          # Add the _finished callback to be called in both success and error
    356          # scenarios. While we don't need final error processing in this task,
    357          # it is good practice to catch any final errors for diagnostic purposes.
    358          d.addBoth(self._finished)
    359
    360          # returning a Deferred will keep the framework from assuming the task
    361          # is done until the Deferred actually completes
    362          return d
    363
    364
    365  #
    366  # Collector Daemon Main entry point
    367  #
    368  if __name__ == '__main__':
    369      myPreferences = ZenWinPreferences()
    370      myTaskFactory = SimpleTaskFactory(ZenWinTask)
    371      myTaskSplitter = SimpleTaskSplitter(myTaskFactory)
    372      daemon = CollectorDaemon(myPreferences, myTaskSplitter)
    373      daemon.run()
    374