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 or (at your 
 10  # option) any later version as published by 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, Status_Wmi 
 45  from Products.ZenModel.WinServiceClass import STARTMODE_AUTO 
 46  from Products.ZenUtils.observable import ObservableMixin 
 47  from Products.ZenWin.WMIClient import WMIClient 
 48  from Products.ZenWin.Watcher import Watcher 
 49  from Products.ZenWin.utils import addNTLMv2Option, setNTLMv2Auth 
 50   
 51  # We retrieve our configuration data remotely via a Twisted PerspectiveBroker 
 52  # connection. To do so, we need to import the class that will be used by the 
 53  # configuration service to send the data over, i.e. DeviceProxy. 
 54  from Products.ZenUtils.Utils import unused 
 55  from Products.ZenCollector.services.config import DeviceProxy 
 56  unused(DeviceProxy) 
 57   
 58  # 
 59  # creating a logging context for this module to use 
 60  # 
 61  log = logging.getLogger("zen.zenwin") 
 62   
 63  # Create an implementation of the ICollectorPreferences interface so that the 
 64  # ZenCollector framework can configure itself from our preferences. 
65 -class ZenWinPreferences(object):
66 zope.interface.implements(ICollectorPreferences) 67
68 - def __init__(self):
69 """ 70 Construct a new ZenWinPreferences instance and provide default 71 values for needed attributes. 72 """ 73 self.collectorName = "zenwin" 74 self.defaultRRDCreateCommand = None 75 self.cycleInterval = 5 * 60 # seconds 76 self.configCycleInterval = 20 # minutes 77 self.options = None 78 79 # the configurationService attribute is the fully qualified class-name 80 # of our configuration service that runs within ZenHub 81 self.configurationService = 'Products.ZenWin.services.WinServiceConfig' 82 83 self.wmibatchSize = 10 84 self.wmiqueryTimeout = 1000
85
86 - def buildOptions(self, parser):
87 parser.add_option('--debug', dest='debug', default=False, 88 action='store_true', 89 help='Increase logging verbosity.') 90 parser.add_option('--proxywmi', dest='proxywmi', 91 default=False, action='store_true', 92 help='Use a process proxy to avoid long-term blocking' 93 ) 94 parser.add_option('--queryTimeout', dest='queryTimeout', 95 default=None, type='int', 96 help='The number of milliseconds to wait for ' + \ 97 'WMI query to respond. Overrides the ' + \ 98 'server settings.') 99 parser.add_option('--batchSize', dest='batchSize', 100 default=None, type='int', 101 help='Number of data objects to retrieve in a ' + 102 'single WMI query.') 103 addNTLMv2Option(parser)
104
105 - def postStartup(self):
106 # turn on low-level pysamba debug logging if requested 107 logseverity = self.options.logseverity 108 if logseverity <= 5: 109 pysamba.library.DEBUGLEVEL.value = 99 110 111 # force NTLMv2 authentication if requested 112 setNTLMv2Auth(self.options)
113 114
115 -class ZenWinTask(ObservableMixin):
116 zope.interface.implements(IScheduledTask) 117 118 STATE_WMIC_CONNECT = 'WMIC_CONNECT' 119 STATE_WMIC_QUERY = 'WMIC_QUERY' 120 STATE_WMIC_PROCESS = 'WMIC_PROCESS' 121 STATE_WATCHER_CONNECT = 'WATCHER_CONNECT' 122 STATE_WATCHER_QUERY = 'WATCHER_QUERY' 123 STATE_WATCHER_PROCESS = 'WATCHER_PROCESS' 124 125 # windows service states from wmi queries (lowercased) 126 RUNNING = "running" 127 STOPPED = "stopped" 128
129 - def __init__(self, 130 deviceId, 131 taskName, 132 scheduleIntervalSeconds, 133 taskConfig):
134 """ 135 Construct a new task instance to watch for Windows Event Log changes 136 for the specified device. 137 138 @param deviceId: the Zenoss deviceId to watch 139 @type deviceId: string 140 @param taskName: the unique identifier for this task 141 @type taskName: string 142 @param scheduleIntervalSeconds: the interval at which this task will be 143 collected 144 @type scheduleIntervalSeconds: int 145 @param taskConfig: the configuration for this task 146 """ 147 super(ZenWinTask, self).__init__() 148 149 self.name = taskName 150 self.configId = deviceId 151 self.interval = scheduleIntervalSeconds 152 self.state = TaskStates.STATE_IDLE 153 154 self._taskConfig = taskConfig 155 self._devId = deviceId 156 self._manageIp = self._taskConfig.manageIp 157 158 self._eventService = zope.component.queryUtility(IEventService) 159 self._preferences = zope.component.queryUtility(ICollectorPreferences, 160 "zenwin") 161 162 # if the user hasn't specified the batchSize or queryTimeout as command 163 # options then use whatever has been specified in the collector 164 # preferences 165 # TODO: convert these to zProperties 166 self._batchSize = self._preferences.options.batchSize 167 if not self._batchSize: 168 self._batchSize = self._preferences.wmibatchSize 169 self._queryTimeout = self._preferences.options.queryTimeout 170 if not self._queryTimeout: 171 self._queryTimeout = self._preferences.wmiqueryTimeout 172 173 self._wmic = None # the WMIClient 174 self._watcher = None 175 self._reset()
176
177 - def _reset(self):
178 """ 179 Reset the WMI client and notification query watcher connection to the 180 device, if they are presently active. 181 """ 182 if self._wmic: 183 self._wmic.close() 184 self._wmic = None 185 if self._watcher: 186 self._watcher.close() 187 self._watcher = None
188
189 - def _finished(self, result):
190 """ 191 Callback activated when the task is complete so that final statistics 192 on the collection can be displayed. 193 """ 194 if not isinstance(result, Failure): 195 log.debug("Device %s [%s] scanned successfully", 196 self._devId, self._manageIp) 197 else: 198 log.debug("Device %s [%s] scanned failed, %s", 199 self._devId, self._manageIp, result.getErrorMessage()) 200 201 # give the result to the rest of the callback/errchain so that the 202 # ZenCollector framework can keep track of the success/failure rate 203 return result
204
205 - def _failure(self, result):
206 """ 207 Errback for an unsuccessful asynchronous connection or collection 208 request. 209 """ 210 err = result.getErrorMessage() 211 log.error("Unable to scan device %s: %s", self._devId, err) 212 213 self._reset() 214 215 summary = """ 216 Could not read Windows services (%s). Check your 217 username/password settings and verify network connectivity. 218 """ % err 219 220 self._eventService.sendEvent(dict( 221 summary=summary, 222 component='zenwin', 223 eventClass=Status_Wmi, 224 device=self._devId, 225 severity=Error, 226 )) 227 228 # give the result to the rest of the errback chain 229 return result
230
231 - def _sendWinServiceEvent(self, name, summary, severity):
232 event = {'summary': summary, 233 'eventClass': Status_WinService, 234 'device': self._devId, 235 'severity': severity, 236 'agent': 'zenwin', 237 'component': name, 238 'eventGroup': 'StatusTest'} 239 self._eventService.sendEvent(event)
240
241 - def _handleResult(self, name, state, startMode):
242 """ 243 Handle a result from the wmi query. Results from both the initial WMI 244 client query and the watcher's notification query are processed by 245 this method. Log running and stopped transitions. Send an event if the 246 service is monitored. 247 """ 248 state = state.lower() 249 summary = "Windows service '%s' is %s" % (name, state) 250 logLevel = logging.DEBUG 251 if name in self._taskConfig.services: 252 was_running, stoppedSeverity, oldStartMode, monitoredStartModes = \ 253 self._taskConfig.services[name] 254 255 running = (state == self.RUNNING) 256 service_was_important = (oldStartMode in monitoredStartModes) 257 service_is_important = (startMode in monitoredStartModes) 258 259 logLevel = logging.INFO 260 if service_is_important: 261 if running: 262 self._sendWinServiceEvent(name, summary, Clear) 263 else: 264 self._sendWinServiceEvent(name, summary, stoppedSeverity) 265 logLevel = logging.CRITICAL 266 else: 267 # if a down service was changed from important to unimportant, 268 # emit a clear event 269 if service_was_important and not running: 270 self._sendWinServiceEvent(name, summary, Clear) 271 272 self._taskConfig.services[name] = ( 273 running, stoppedSeverity, startMode, monitoredStartModes) 274 275 log.log(logLevel, '%s on %s', summary, self._devId)
276
277 - def _collectSuccessful(self, results):
278 """ 279 Callback for a successful fetch of services from the remote device. 280 """ 281 self.state = ZenWinTask.STATE_WATCHER_PROCESS 282 283 log.debug("Successful collection from %s [%s], results=%s", 284 self._devId, self._manageIp, results) 285 286 # make a local copy of monitored services list 287 services = self._taskConfig.services.copy() 288 if results: 289 for result in [r.targetInstance for r in results]: 290 if result.state: 291 if result.name in services: 292 # remove service from local copy 293 del services[result.name] 294 self._handleResult( 295 result.name, result.state, result.startmode) 296 # send events for the services that did not show up in results 297 for name, data in services.items(): 298 running, failSeverity, startMode, monitoredStartModes = data 299 if running: 300 state = self.RUNNING 301 else: 302 state = self.STOPPED 303 self._handleResult(name, state, startMode) 304 if results: 305 # schedule another immediate collection so that we'll keep eating 306 # events as long as they are ready for us; using callLater ensures 307 # it goes to the end of the immediate work-queue so that other 308 # events get processing time 309 log.debug("Queuing another fetch for %s [%s]", 310 self._devId, self._manageIp) 311 d = defer.Deferred() 312 reactor.callLater(0, d.callback, None) 313 d.addCallback(self._collectCallback) 314 return d
315
316 - def _deviceUp(self, result):
317 msg = 'WMI connection to %s up.' % self._devId 318 self._eventService.sendEvent(dict( 319 summary=msg, 320 eventClass=Status_Wmi, 321 device=self._devId, 322 severity=Clear, 323 component='zenwin')) 324 return result
325
326 - def _collectCallback(self, result):
327 """ 328 Callback called after a connect or previous collection so that another 329 collection can take place. 330 """ 331 log.debug("Polling for events from %s [%s]", 332 self._devId, self._manageIp) 333 334 self.state = ZenWinTask.STATE_WATCHER_QUERY 335 d = self._watcher.getEvents(self._queryTimeout, self._batchSize) 336 d.addCallbacks(self._collectSuccessful, self._failure) 337 d.addCallbacks(self._deviceUp) 338 return d
339
340 - def _connectCallback(self, result):
341 """ 342 Callback called after a successful connect to the remote Windows device. 343 """ 344 log.debug("Connected to %s [%s]", self._devId, self._manageIp)
345
346 - def _connectWatcher(self, result):
347 self.state = ZenWinTask.STATE_WMIC_PROCESS 348 for service in result['query']: 349 self._handleResult(service.name, service.state, service.startmode) 350 self._wmic.close() 351 self._wmic = None 352 self.state = ZenWinTask.STATE_WATCHER_CONNECT 353 wql = "SELECT * FROM __InstanceModificationEvent WITHIN 5 "\ 354 "WHERE TargetInstance ISA 'Win32_Service'" 355 self._watcher = Watcher(self._taskConfig, wql) 356 return self._watcher.connect()
357
358 - def _initialQuery(self, result):
359 self.state = ZenWinTask.STATE_WMIC_QUERY 360 wql = "SELECT Name, State, StartMode FROM Win32_Service" 361 d = self._wmic.query({'query': wql}) 362 d.addCallback(self._connectWatcher) 363 return d
364
365 - def _connect(self):
366 """ 367 Called when a connection needs to be created to the remote Windows 368 device. 369 """ 370 log.debug("Connecting to %s [%s]", self._devId, self._manageIp) 371 self.state = ZenWinTask.STATE_WMIC_CONNECT 372 self._wmic = WMIClient(self._taskConfig) 373 d = self._wmic.connect() 374 d.addCallback(self._initialQuery) 375 return d
376
377 - def cleanup(self):
378 return self._reset()
379
380 - def doTask(self):
381 log.debug("Scanning device %s [%s]", self._devId, self._manageIp) 382 383 # see if we need to connect first before doing any collection 384 if not self._watcher: 385 d = self._connect() 386 d.addCallbacks(self._connectCallback, self._failure) 387 else: 388 # since we don't need to bother connecting, we'll just create an 389 # empty deferred and have it run immediately so the collect callback 390 # will be fired off 391 d = defer.Deferred() 392 reactor.callLater(0, d.callback, None) 393 394 # try collecting events after a successful connect, or if we're already 395 # connected 396 d.addCallback(self._collectCallback) 397 398 # Add the _finished callback to be called in both success and error 399 # scenarios. While we don't need final error processing in this task, 400 # it is good practice to catch any final errors for diagnostic purposes. 401 d.addBoth(self._finished) 402 403 # returning a Deferred will keep the framework from assuming the task 404 # is done until the Deferred actually completes 405 return d
406 407 408 # 409 # Collector Daemon Main entry point 410 # 411 if __name__ == '__main__': 412 myPreferences = ZenWinPreferences() 413 myTaskFactory = SimpleTaskFactory(ZenWinTask) 414 myTaskSplitter = SimpleTaskSplitter(myTaskFactory) 415 daemon = CollectorDaemon(myPreferences, myTaskSplitter) 416 daemon.run() 417