1
2
3
4
5
6
7
8
9
10
11
12
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
25
26
27
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
52
53
54 from Products.ZenUtils.Utils import unused
55 from Products.ZenCollector.services.config import DeviceProxy
56 unused(DeviceProxy)
57
58
59
60
61 log = logging.getLogger("zen.zeneventlog")
62
63
64
65
67 zope.interface.implements(ICollectorPreferences)
68
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
77 self.configCycleInterval = 20
78 self.options = None
79
80
81
82 self.configurationService = 'Products.ZenWin.services.EventLogConfig'
83
84 self.wmibatchSize = 10
85 self.wmiqueryTimeout = 1000
86
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
102 logseverity = self.options.logseverity
103 if logseverity <= 5:
104 pysamba.library.DEBUGLEVEL.value = 99
105
106
107 setNTLMv2Auth(self.options)
108
109
110 statService = zope.component.queryUtility(IStatisticsService)
111 statService.addStatistic("events", "COUNTER")
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
173
174
175
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
185
186
187
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
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
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
221 elif lrec.eventtype == 2:
222 sev = Warning
223 elif lrec.eventtype in (3, 4, 5):
224 sev = Info
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
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
280
281 return result
282
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
308 return result
309
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
322 for logRecord in events:
323 self._eventsFetched += 1
324
325 self._eventService.sendEvent(self._makeEvent(logRecord))
326
327
328
329
330
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
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
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
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
370
372 log.debug("Scanning device %s [%s]", self._devId, self._manageIp)
373
374 self._eventsFetched = 0
375
376
377 if not self._watcher:
378 d = self._connect()
379 d.addCallbacks(self._connectCallback, self._failure)
380 else:
381
382
383
384 d = defer.Deferred()
385 reactor.callLater(0, d.callback, None)
386
387
388
389 d.addCallback(self._collectCallback)
390
391
392
393
394 d.addBoth(self._finished)
395
396
397
398 return d
399
400
401
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