Package ZenWin :: Module WinCollector
[hide private]
[frames] | no frames]

Source Code for Module ZenWin.WinCollector

  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) 2007-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  __doc__ = """WinCollector 
 17  PB daemon-izable base class for creating WMI collectors 
 18  """ 
 19   
 20  import logging 
 21  import time 
 22   
 23  import Globals 
 24  from Products.ZenHub.PBDaemon import PBDaemon, FakeRemote 
 25  from Products.ZenEvents.ZenEventClasses import App_Start, Clear, \ 
 26      Status_Wmi 
 27  from Products.ZenEvents.Event import Error 
 28  from Products.ZenUtils.Driver import drive, driveLater 
 29  from Products.ZenUtils.Utils import unused 
 30   
 31  # needed by pb 
 32  from Products.DataCollector import DeviceProxy 
 33  from Products.DataCollector.Plugins import PluginLoader 
 34  unused(DeviceProxy, PluginLoader) 
 35   
 36  from twisted.internet import reactor, defer 
 37  from twisted.python.failure import Failure 
 38  from pysamba.library import DEBUGLEVEL 
 39   
 40   
41 -class WinCollector(PBDaemon):
42 """ 43 Base class to be sub-classed by WMI daemons 44 """ 45 configCycleInterval = 20 46 wmibatchSize = 10 47 wmiqueryTimeout = 1000 48 49 # Short text description of what this collector does: set in sub-classes 50 whatIDo = 'Override whatIDo in a subclass' 51 52 initialServices = PBDaemon.initialServices\ 53 + ['Products.ZenWin.services.WmiConfig'] 54 55 attributes = ('configCycleInterval', 'wmibatchSize', 'wmiqueryTimeout') 56 deviceAttributes = ( 57 'manageIp', 'zWinPassword', 'zWinUser', 'zWmiMonitorIgnore') 58
59 - def __init__(self):
60 self.wmiprobs = [] 61 self.devices = [] 62 self.watchers = {} 63 PBDaemon.__init__(self) 64 self.reconfigureTimeout = None 65 if self.options.logseverity <= logging.DEBUG: 66 DEBUGLEVEL.value = 99
67
69 """ 70 Called from zenhub to push new configs down. 71 """ 72 self.log.info('Async config notification') 73 if self.reconfigureTimeout and \ 74 not self.reconfigureTimeout.called: 75 self.reconfigureTimeout.cancel() 76 self.reconfigureTimeout = reactor.callLater( 77 self.cycleInterval() / 2, drive, self.reconfigure)
78
79 - def stopScan(self, unused=None):
80 """ 81 Stop (reactor.stop()) the collection 82 83 @param unused: unused 84 @type unused: string 85 """ 86 self.stop()
87
88 - def scanCycle(self, driver):
89 """ 90 Generator function to collect data in one scan cycle 91 92 @param driver: driver 93 @type driver: string 94 @return: defered task 95 @rtype: Twisted defered or DeferredList 96 """ 97 now = time.time() 98 cycle = self.cycleInterval() 99 try: 100 yield self.eventService().callRemote('getWmiConnIssues') 101 self.wmiprobs = [e[0] for e in driver.next()] 102 self.log.debug('Wmi Probs %r', self.wmiprobs) 103 devices = [] 104 if self.devices is not None: 105 for device in self.devices: 106 if not device.plugins: 107 continue 108 if self.options.device and device.id\ 109 != self.options.device: 110 continue 111 if device.id in self.wmiprobs: 112 self.log.debug('WMI problems on %s: skipping' 113 % device.id) 114 continue 115 devices.append(device) 116 yield self.processLoop(devices, cycle) 117 driver.next() 118 if not self.options.cycle: 119 self.stopScan() 120 else: 121 self.heartbeat() 122 count = len(self.devices) 123 if self.options.device: 124 count = 1 125 delay = time.time() - now 126 self.sendEvents(self.rrdStats.gauge('cycleTime', cycle, 127 delay) + self.rrdStats.gauge('devices', 128 cycle, count)) 129 self.log.info('Scanned %d devices in %.1f seconds', 130 count, delay) 131 except (Failure, Exception), ex: 132 self.log.exception('Error processing main loop') 133 134 # Schedule the next scan even if there was an error this time. 135 if self.options.cycle: 136 delay = time.time() - now 137 driveLater(max(0, cycle - delay), self.scanCycle)
138 139
140 - def processLoop(self, devices, timeoutSecs):
141 """ 142 Cycle through the list of devices and collect from them. 143 144 @param devices: device object list 145 @type devices: list 146 @param timeoutSecs: timeoutSecs 147 @type timeoutSecs: int 148 @return: list of defereds to processDevice() 149 @rtype: Twisted DeferredList 150 """ 151 deferreds = [] 152 for device in devices: 153 deferreds.append(self.processDevice(device, timeoutSecs)) 154 return defer.DeferredList(deferreds)
155
156 - def processDevice(self, device, timeoutSecs):
157 """ 158 Perform a collection service on a device 159 160 @param device: device to query 161 @type device: object 162 @param timeoutSecs: timeoutSecs 163 @type timeoutSecs: int 164 @return: defered to complete the processing 165 @rtype: Twisted defered 166 """ 167 raise NotImplementedError('You must override this method.')
168
169 - def cycleInterval(self):
170 """ 171 Return the length of time in a scan cycle 172 173 Method which must be overridden 174 175 @return: number of seconds in a cycle 176 @rtype: int 177 """ 178 raise NotImplementedError('You must override this method')
179
180 - def configService(self):
181 """ 182 Gather this daemons WMI configuration 183 184 @return: zenhub configuration information 185 @rtype: WmiConfig 186 """ 187 return self.services.get('Products.ZenWin.services.WmiConfig', 188 FakeRemote())
189 190
191 - def devicesEqual(self, d1, d2):
192 for att in self.deviceAttributes: 193 if getattr(d1, att, None) != getattr(d2, att, None): 194 return False 195 return True
196 197
198 - def updateDevices(self, devices):
199 """ 200 Update device configuration 201 202 @param devices: list of devices 203 @type devices: list 204 """ 205 self.devices = devices 206 newDevices = {} 207 for d in devices: newDevices[d.id] = d 208 for (deviceName, watcher) in self.watchers.items(): 209 changed = False 210 if deviceName not in newDevices: 211 self.log.info("Stopping monitoring of %s.", deviceName) 212 changed = True 213 elif not self.devicesEqual(watcher.device, newDevices[deviceName]): 214 self.log.info("Updating configuration of %s.", deviceName) 215 changed = True 216 217 if changed: 218 del self.watchers[deviceName] 219 watcher.close()
220 221
222 - def remote_deleteDevice(self, deviceId):
223 """ 224 Function called from zenhub to remove a device from our 225 list of devices. 226 227 @param deviceId: deviceId 228 @type deviceId: string 229 """ 230 devices = [] 231 for d in self.devices: 232 if deviceId == d.id: 233 self.log.info("Stopping monitoring of %s.", deviceId) 234 else: 235 devices.append(d) 236 self.devices = devices
237 238
239 - def error(self, why):
240 """ 241 Twisted errback routine to log messages 242 243 @param why: error message 244 @type why: string 245 """ 246 self.log.error(why.getErrorMessage())
247
248 - def updateConfig(self, cfg):
249 """ 250 updateConfig 251 252 @param cfg: configuration from zenhub 253 @type cfg: WmiConfig 254 """ 255 cfg = dict(cfg) 256 for attribute in self.attributes: 257 current = getattr(self, attribute, None) 258 value = cfg.get(attribute) 259 self.log.debug( "Received %s = %r from zenhub" % ( attribute, value)) 260 if current is not None and current != value: 261 self.log.info('Setting %s to %r', attribute, value) 262 setattr(self, attribute, value) 263 self.heartbeatTimeout = self.cycleInterval() * 3
264
265 - def start(self):
266 """ 267 Startup routine 268 """ 269 self.log.info('Starting %s', self.name) 270 self.sendEvent(dict(summary='Starting %s' % self.name, 271 eventClass=App_Start, 272 device=self.options.monitor, severity=Clear, 273 component=self.name))
274
275 - def startScan(self, unused=None):
276 """ 277 Calls start() and then goes through scanCycle() until finished 278 279 @param unused: unused 280 @type unused: string 281 """ 282 self.start() 283 d = drive(self.scanCycle)
284
285 - def deviceDown(self, device, error):
286 """ 287 Method to call when a device does not respond or returns an error. 288 289 @param device: device object 290 @type device: device object 291 @param error: useful and informative error message 292 @type error: string 293 """ 294 summary = \ 295 'Could not %s (%s). Check your username/password settings and verify network connectivity.'\ 296 % (self.whatIDo, error) 297 self.sendEvent(dict( 298 summary=summary, 299 component=self.agent, 300 eventClass=Status_Wmi, 301 device=device.id, 302 severity=Error, 303 agent=self.agent, 304 )) 305 self.log.warning('Closing watcher of %s', device.id) 306 if self.watchers.has_key(device.id): 307 w = self.watchers.pop(device.id, None) 308 w.close()
309
310 - def deviceUp(self, device):
311 """ 312 Method to call when a device comes back to life. 313 314 @param device: device oject 315 @type device: device object 316 """ 317 msg = 'WMI connection to %s up.' % device.id 318 self.sendEvent(dict( 319 summary=msg, 320 eventClass=Status_Wmi, 321 device=device.id, 322 severity=Clear, 323 agent=self.agent, 324 component=self.name, 325 ))
326
327 - def reconfigure(self, driver):
328 """ 329 Gather our complete configuration information. 330 331 @param driver: driver object 332 @type driver: driver object 333 @return: defered 334 @rtype: Twisted defered 335 """ 336 try: 337 yield self.eventService().callRemote('getWmiConnIssues') 338 self.wmiprobs = [e[0] for e in driver.next()] 339 self.log.debug('Ignoring devices %r', self.wmiprobs) 340 341 yield self.configService().callRemote('getConfig') 342 self.updateConfig(driver.next()) 343 344 yield drive(self.fetchDevices) 345 driver.next() 346 347 yield self.configService().callRemote('getThresholdClasses') 348 self.remote_updateThresholdClasses(driver.next()) 349 350 yield self.configService().callRemote('getCollectorThresholds' 351 ) 352 self.rrdStats.config(self.options.monitor, self.name, 353 driver.next()) 354 except Exception, ex: 355 self.log.exception('Error fetching config')
356
357 - def startConfigCycle(self):
358 """ 359 Gather configuration and set up to re-check. 360 361 @return: defered 362 @rtype: Twisted defered 363 """ 364 def driveAgain(result): 365 """ 366 callback and errback to gather configurations 367 368 @param result: result 369 @type result: value 370 @return: defered 371 @rtype: Twisted deferrd 372 """ 373 driveLater(self.configCycleInterval * 60, self.reconfigure) 374 return result
375 376 return drive(self.reconfigure).addBoth(driveAgain)
377
378 - def connected(self):
379 """ 380 Method called after a connection to zenhub is established. 381 Calls startConfigCycle() and startScan() 382 """ 383 d = self.startConfigCycle() 384 d.addCallback(self.startScan)
385
386 - def buildOptions(self):
387 """ 388 Command-line option builder 389 """ 390 PBDaemon.buildOptions(self) 391 self.parser.add_option('-d', '--device', dest='device', 392 default=None, 393 help='The name of a single device to collect') 394 self.parser.add_option('--debug', dest='debug', default=False, 395 action='store_true', 396 help='Increase logging verbosity.') 397 self.parser.add_option('--proxywmi', dest='proxywmi', 398 default=False, action='store_true', 399 help='Use a process proxy to avoid long-term blocking' 400 ) 401 self.parser.add_option('--queryTimeout', dest='queryTimeout', 402 default=None, type='int', 403 help='The number of milliseconds to wait for ' + \ 404 'WMI query to respond. Overrides the ' + \ 405 'server settings.') 406 self.parser.add_option('--batchSize', dest='batchSize', 407 default=None, type='int', 408 help='Number of data objects to retrieve in a ' + 409 'single WMI query.')
410