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