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