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