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

Source Code for Module Products.ZenWin.zeneventlog

  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 the Windows Event Log. Retrieved events are 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                                               IStatisticsService 
 42  from Products.ZenCollector.tasks import SimpleTaskFactory,\ 
 43                                          SimpleTaskSplitter,\ 
 44                                          TaskStates 
 45  from Products.ZenEvents.ZenEventClasses import Clear, Error, Warning, Info, \ 
 46      Debug, Status_Wmi 
 47  from Products.ZenUtils.observable import ObservableMixin 
 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.zeneventlog") 
 62   
 63   
 64  # Create an implementation of the ICollectorPreferences interface so that the 
 65  # ZenCollector framework can configure itself from our preferences. 
66 -class ZenEventLogPreferences(object):
67 zope.interface.implements(ICollectorPreferences) 68
69 - def __init__(self):
70 """ 71 Constructs a new ZenEventLogPreferences instance and provide default 72 values for needed attributes. 73 """ 74 self.collectorName = "zeneventlog" 75 self.defaultRRDCreateCommand = None 76 self.cycleInterval = 5 * 60 # seconds 77 self.configCycleInterval = 20 # minutes 78 self.options = None 79 80 # the configurationService attribute is the fully qualified class-name 81 # of our configuration service that runs within ZenHub 82 self.configurationService = 'Products.ZenWin.services.EventLogConfig' 83 84 self.wmibatchSize = 10 85 self.wmiqueryTimeout = 1000
86
87 - def buildOptions(self, parser):
88 parser.add_option('--batchSize', dest='batchSize', 89 default=None, type='int', 90 help='Number of data objects to retrieve in a ' + 91 'single WMI query.') 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 99 parser.add_option('--testClause', dest='testClause', 100 default=None, 101 help="Override the device's zWinEventlogClause" \ 102 " with this string.") 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 # add our collector's custom statistics 115 statService = zope.component.queryUtility(IStatisticsService) 116 statService.addStatistic("events", "COUNTER")
117 118 # 119 # Create an implementation of the IScheduledTask interface that will perform 120 # the actual collection work needed by this collector. In this case, we will 121 # scan Windows devices for changes to the Windows event log using a WMI 122 # notification query. These queries are open-ended queries that wait until data 123 # has been added to the WMI class specified in the query. This task will poll 124 # for any changed events with a small timeout period before returning to an 125 # idle state and trying again at the next collection interval. 126 # 127 # TODO: this is a timing bug with this approach where we can lose events in the 128 # following scenarios: 129 # 1. Anytime the daemon is shutdown and restarted. 130 # 2. Anytime we reset our WMI connection and create a new one. 131 #
132 -class ZenEventLogTask(ObservableMixin):
133 """ 134 A scheduled task that watches the event log on a single Windows device. 135 """ 136 zope.interface.implements(IScheduledTask) 137 138 EVENT_LOG_NOTIFICATION_QUERY = """ 139 SELECT * FROM __InstanceCreationEvent 140 WHERE TargetInstance ISA 'Win32_NTLogEvent' 141 AND TargetInstance.EventType <= %d 142 """ 143 144 STATE_CONNECTING = 'CONNECTING' 145 STATE_POLLING = 'POLLING' 146 STATE_PROCESSING = 'PROCESSING' 147
148 - def __init__(self, 149 deviceId, 150 taskName, 151 scheduleIntervalSeconds, 152 taskConfig):
153 """ 154 Construct a new task instance to watch for Windows Event Log changes 155 for the specified device. 156 157 @param deviceId: the Zenoss deviceId to watch 158 @type deviceId: string 159 @param taskName: the unique identifier for this task 160 @type taskName: string 161 @param scheduleIntervalSeconds: the interval at which this task will be 162 collected 163 @type scheduleIntervalSeconds: int 164 @param taskConfig: the configuration for this task 165 """ 166 super(ZenEventLogTask, self).__init__() 167 168 self.name = taskName 169 self.configId = deviceId 170 self.interval = scheduleIntervalSeconds 171 self.state = TaskStates.STATE_IDLE 172 173 self._taskConfig = taskConfig 174 self._devId = deviceId 175 self._manageIp = self._taskConfig.manageIp 176 177 self._eventService = zope.component.queryUtility(IEventService) 178 self._statService = zope.component.queryUtility(IStatisticsService) 179 self._preferences = zope.component.queryUtility(ICollectorPreferences, 180 "zeneventlog") 181 182 # if the user hasn't specified the batchSize or queryTimeout as command 183 # options then use whatever has been specified in the collector 184 # preferences 185 # TODO: convert these to zProperties 186 self._batchSize = self._preferences.options.batchSize 187 if not self._batchSize: 188 self._batchSize = self._preferences.wmibatchSize 189 self._queryTimeout = self._preferences.options.queryTimeout 190 if not self._queryTimeout: 191 self._queryTimeout = self._preferences.wmiqueryTimeout 192 193 # Create the actual query that will be used based upon the template and 194 # the devices's zWinEventlogMinSeverity zProperty. If this zProperty 195 # changes then the task will be deleted and a new one created, so it 196 # is okay to do so here in the constructor. 197 self._wmiQuery = ZenEventLogTask.EVENT_LOG_NOTIFICATION_QUERY % \ 198 int(self._taskConfig.zWinEventlogMinSeverity) 199 testClause = self._preferences.options.testClause 200 andClause = self._taskConfig.zWinEventlogClause 201 if testClause is not None: 202 if testClause.strip(): 203 self._wmiQuery += " AND " + testClause 204 elif andClause and andClause.strip(): 205 self._wmiQuery += " AND " + andClause 206 207 self._watcher = None 208 self._reset()
209
210 - def _reset(self):
211 """ 212 Reset the WMI notification query watcher connection to the device, if 213 one is presently active. 214 """ 215 if self._watcher: 216 self._watcher.close() 217 self._watcher = None
218
219 - def _makeEvent(self, lrec):
220 """ 221 Put event in the queue to be sent to the ZenEventManager. 222 223 @param lrec: log record 224 @type lrec: log record object 225 @return: dictionary with event keys and values 226 @rtype: dictionary 227 """ 228 lrec = lrec.targetinstance 229 evtkey = '%s_%s' % (lrec.sourcename, lrec.eventcode) 230 sev = Debug 231 if lrec.eventtype == 1: 232 sev = Error # error 233 elif lrec.eventtype == 2: 234 sev = Warning # warning 235 elif lrec.eventtype in (3, 4, 5): 236 sev = Info # information, security audit success & failure 237 238 log.debug( "---- log record info --------------" ) 239 for item in dir(lrec): 240 if item[0] == '_': 241 continue 242 log.debug("%s = %s" % (item, getattr(lrec, item, ''))) 243 log.debug( "---- log record info --------------" ) 244 245 ts= lrec.timegenerated 246 try: 247 date_ts = '/'.join( [ ts[0:4], ts[4:6], ts[6:8] ]) 248 time_ts = ':'.join( [ts[8:10], ts[10:12], ts[12:14] ]) 249 ts = date_ts + ' ' + time_ts 250 except: 251 pass 252 253 event_message = str(lrec.message).strip() 254 if not event_message or event_message == 'None': 255 event_message = "Message text from Windows not available." + \ 256 " See source system's event log." 257 258 evt = dict( 259 device=self._devId, 260 eventClassKey=evtkey, 261 eventGroup=lrec.logfile, 262 component=lrec.sourcename, 263 ntevid=lrec.eventcode, 264 summary=event_message, 265 agent='zeneventlog', 266 severity=sev, 267 monitor=self._preferences.options.monitor, 268 user=lrec.user, 269 categorystring=lrec.categorystring, 270 originaltime=ts, 271 computername=lrec.computername, 272 eventidentifier=lrec.eventidentifier, 273 ) 274 log.debug("Device:%s msg:'%s'", self._devId, lrec.message) 275 return evt
276
277 - def _finished(self, result):
278 """ 279 Callback activated when the task is complete so that final statistics 280 on the collection can be displayed. 281 """ 282 if not isinstance(result, Failure): 283 log.debug("Device %s [%s] scanned successfully, %d events processed", 284 self._devId, self._manageIp, self._eventsFetched) 285 stat = self._statService.getStatistic("events") 286 stat.value += self._eventsFetched 287 else: 288 log.debug("Device %s [%s] scanned failed, %s", 289 self._devId, self._manageIp, result.getErrorMessage()) 290 291 # give the result to the rest of the callback/errchain so that the 292 # ZenCollector framework can keep track of the success/failure rate 293 return result
294
295 - def _failure(self, result):
296 """ 297 Errback for an unsuccessful asynchronous connection or collection 298 request. 299 """ 300 err = result.getErrorMessage() 301 log.error("Unable to scan device %s: %s", self._devId, err) 302 303 self._reset() 304 305 if 'WBEM_E_UNPARSABLE_QUERY' in err: 306 summary = """ 307 There was an error found while processing the zWinEventlogClause 308 query: %s 309 """ % err 310 else: 311 summary = """ 312 Could not read the Windows event log (%s). Check your 313 username/password settings and verify network connectivity. 314 """ % err 315 316 self._eventService.sendEvent(dict( 317 summary=summary, 318 component='zeneventlog', 319 eventClass=Status_Wmi, 320 device=self._devId, 321 severity=Error, 322 agent='zeneventlog', 323 )) 324 325 # give the result to the rest of the errback chain 326 return result
327
328 - def _collectSuccessful(self, result):
329 """ 330 Callback for a successful fetch of events from the remote device. 331 """ 332 self.state = ZenEventLogTask.STATE_PROCESSING 333 334 log.debug("Successful collection from %s [%s], result=%s", 335 self._devId, self._manageIp, result) 336 337 events = result 338 if events: 339 # process all of the fetched events 340 for logRecord in events: 341 self._eventsFetched += 1 342 # TODO: figure out how to post this state on the cycle interval 343 self._eventService.sendEvent(self._makeEvent(logRecord)) 344 345 # schedule another immediate collection so that we'll keep eating 346 # events as long as they are ready for us; using callLater ensures 347 # it goes to the end of the immediate work-queue so that other 348 # events get processing time 349 log.debug("Queuing another fetch for %s [%s]", 350 self._devId, self._manageIp) 351 d = defer.Deferred() 352 reactor.callLater(0, d.callback, None) 353 d.addCallback(self._collectCallback) 354 return d
355
356 - def _deviceUp(self, result):
357 msg = 'WMI connection to %s up.' % self._devId 358 self._eventService.sendEvent(dict( 359 summary=msg, 360 eventClass=Status_Wmi, 361 device=self._devId, 362 severity=Clear, 363 component='zeneventlog')) 364 return result
365
366 - def _collectCallback(self, result):
367 """ 368 Callback called after a connect or previous collection so that another 369 collection can take place. 370 """ 371 log.debug("Polling for events from %s [%s]", 372 self._devId, self._manageIp) 373 374 self.state = ZenEventLogTask.STATE_POLLING 375 d = self._watcher.getEvents(self._queryTimeout, self._batchSize) 376 d.addCallbacks(self._collectSuccessful, self._failure) 377 d.addCallbacks(self._deviceUp) 378 return d
379
380 - def _connectCallback(self, result):
381 """ 382 Callback called after a successful connect to the remote Windows device. 383 """ 384 log.debug("Connected to %s [%s]", self._devId, self._manageIp)
385
386 - def _connect(self):
387 """ 388 Called when a connection needs to be created to the remote Windows 389 device. 390 """ 391 log.debug("Connecting to %s [%s]", self._devId, self._manageIp) 392 393 self.state = ZenEventLogTask.STATE_CONNECTING 394 self._watcher = Watcher(self._taskConfig, self._wmiQuery) 395 return self._watcher.connect()
396
397 - def cleanup(self):
398 return self._reset()
399
400 - def doTask(self):
401 log.debug("Scanning device %s [%s]", self._devId, self._manageIp) 402 403 self._eventsFetched = 0 404 405 # see if we need to connect first before doing any collection 406 if not self._watcher: 407 d = self._connect() 408 d.addCallbacks(self._connectCallback, self._failure) 409 else: 410 # since we don't need to bother connecting, we'll just create an 411 # empty deferred and have it run immediately so the collect callback 412 # will be fired off 413 d = defer.Deferred() 414 reactor.callLater(0, d.callback, None) 415 416 # try collecting events after a successful connect, or if we're already 417 # connected 418 d.addCallback(self._collectCallback) 419 420 # Add the _finished callback to be called in both success and error 421 # scenarios. While we don't need final error processing in this task, 422 # it is good practice to catch any final errors for diagnostic purposes. 423 d.addBoth(self._finished) 424 425 # returning a Deferred will keep the framework from assuming the task 426 # is done until the Deferred actually completes 427 return d
428 429 # 430 # Collector Daemon Main entry point 431 # 432 if __name__ == '__main__': 433 myPreferences = ZenEventLogPreferences() 434 435 myTaskFactory = SimpleTaskFactory(ZenEventLogTask) 436 myTaskSplitter = SimpleTaskSplitter(myTaskFactory) 437 daemon = CollectorDaemon(myPreferences, myTaskSplitter) 438 daemon.run() 439