| Trees | Indices | Help |
|
|---|
|
|
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 Windows services. Retrieved status is 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 from Products.ZenCollector.tasks import SimpleTaskFactory,\
42 SimpleTaskSplitter,\
43 TaskStates
44 from Products.ZenEvents.ZenEventClasses import Error, Clear, Status_WinService, Status_Wmi
45 from Products.ZenModel.WinServiceClass import STARTMODE_AUTO
46 from Products.ZenUtils.observable import ObservableMixin
47 from Products.ZenWin.WMIClient import WMIClient
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.zenwin")
62
63 # Create an implementation of the ICollectorPreferences interface so that the
64 # ZenCollector framework can configure itself from our preferences.
66 zope.interface.implements(ICollectorPreferences)
67
69 """
70 Construct a new ZenWinPreferences instance and provide default
71 values for needed attributes.
72 """
73 self.collectorName = "zenwin"
74 self.defaultRRDCreateCommand = None
75 self.cycleInterval = 5 * 60 # seconds
76 self.configCycleInterval = 20 # minutes
77 self.options = None
78
79 # the configurationService attribute is the fully qualified class-name
80 # of our configuration service that runs within ZenHub
81 self.configurationService = 'Products.ZenWin.services.WinServiceConfig'
82
83 self.wmibatchSize = 10
84 self.wmiqueryTimeout = 1000
85
87 parser.add_option('--debug', dest='debug', default=False,
88 action='store_true',
89 help='Increase logging verbosity.')
90 parser.add_option('--proxywmi', dest='proxywmi',
91 default=False, action='store_true',
92 help='Use a process proxy to avoid long-term blocking'
93 )
94 parser.add_option('--queryTimeout', dest='queryTimeout',
95 default=None, type='int',
96 help='The number of milliseconds to wait for ' + \
97 'WMI query to respond. Overrides the ' + \
98 'server settings.')
99 parser.add_option('--batchSize', dest='batchSize',
100 default=None, type='int',
101 help='Number of data objects to retrieve in a ' +
102 'single WMI query.')
103 addNTLMv2Option(parser)
104
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
116 zope.interface.implements(IScheduledTask)
117
118 STATE_WMIC_CONNECT = 'WMIC_CONNECT'
119 STATE_WMIC_QUERY = 'WMIC_QUERY'
120 STATE_WMIC_PROCESS = 'WMIC_PROCESS'
121 STATE_WATCHER_CONNECT = 'WATCHER_CONNECT'
122 STATE_WATCHER_QUERY = 'WATCHER_QUERY'
123 STATE_WATCHER_PROCESS = 'WATCHER_PROCESS'
124
125 # windows service states from wmi queries (lowercased)
126 RUNNING = "running"
127 STOPPED = "stopped"
128
134 """
135 Construct a new task instance to watch for Windows Event Log changes
136 for the specified device.
137
138 @param deviceId: the Zenoss deviceId to watch
139 @type deviceId: string
140 @param taskName: the unique identifier for this task
141 @type taskName: string
142 @param scheduleIntervalSeconds: the interval at which this task will be
143 collected
144 @type scheduleIntervalSeconds: int
145 @param taskConfig: the configuration for this task
146 """
147 super(ZenWinTask, self).__init__()
148
149 self.name = taskName
150 self.configId = deviceId
151 self.interval = scheduleIntervalSeconds
152 self.state = TaskStates.STATE_IDLE
153
154 self._taskConfig = taskConfig
155 self._devId = deviceId
156 self._manageIp = self._taskConfig.manageIp
157
158 self._eventService = zope.component.queryUtility(IEventService)
159 self._preferences = zope.component.queryUtility(ICollectorPreferences,
160 "zenwin")
161
162 # if the user hasn't specified the batchSize or queryTimeout as command
163 # options then use whatever has been specified in the collector
164 # preferences
165 # TODO: convert these to zProperties
166 self._batchSize = self._preferences.options.batchSize
167 if not self._batchSize:
168 self._batchSize = self._preferences.wmibatchSize
169 self._queryTimeout = self._preferences.options.queryTimeout
170 if not self._queryTimeout:
171 self._queryTimeout = self._preferences.wmiqueryTimeout
172
173 self._wmic = None # the WMIClient
174 self._watcher = None
175 self._reset()
176
178 """
179 Reset the WMI client and notification query watcher connection to the
180 device, if they are presently active.
181 """
182 if self._wmic:
183 self._wmic.close()
184 self._wmic = None
185 if self._watcher:
186 self._watcher.close()
187 self._watcher = None
188
190 """
191 Callback activated when the task is complete so that final statistics
192 on the collection can be displayed.
193 """
194 if not isinstance(result, Failure):
195 log.debug("Device %s [%s] scanned successfully",
196 self._devId, self._manageIp)
197 else:
198 log.debug("Device %s [%s] scanned failed, %s",
199 self._devId, self._manageIp, result.getErrorMessage())
200
201 # give the result to the rest of the callback/errchain so that the
202 # ZenCollector framework can keep track of the success/failure rate
203 return result
204
206 """
207 Errback for an unsuccessful asynchronous connection or collection
208 request.
209 """
210 err = result.getErrorMessage()
211 log.error("Unable to scan device %s: %s", self._devId, err)
212
213 self._reset()
214
215 summary = """
216 Could not read Windows services (%s). Check your
217 username/password settings and verify network connectivity.
218 """ % err
219
220 self._eventService.sendEvent(dict(
221 summary=summary,
222 component='zenwin',
223 eventClass=Status_Wmi,
224 device=self._devId,
225 severity=Error,
226 ))
227
228 # give the result to the rest of the errback chain
229 return result
230
232 event = {'summary': summary,
233 'eventClass': Status_WinService,
234 'device': self._devId,
235 'severity': severity,
236 'agent': 'zenwin',
237 'component': name,
238 'eventGroup': 'StatusTest'}
239 self._eventService.sendEvent(event)
240
242 """
243 Handle a result from the wmi query. Results from both the initial WMI
244 client query and the watcher's notification query are processed by
245 this method. Log running and stopped transitions. Send an event if the
246 service is monitored.
247 """
248 state = state.lower()
249 summary = "Windows service '%s' is %s" % (name, state)
250 logLevel = logging.DEBUG
251 if name in self._taskConfig.services:
252 was_running, stoppedSeverity, oldStartMode, monitoredStartModes = \
253 self._taskConfig.services[name]
254
255 running = (state == self.RUNNING)
256 service_was_important = (oldStartMode in monitoredStartModes)
257 service_is_important = (startMode in monitoredStartModes)
258
259 logLevel = logging.INFO
260 if service_is_important:
261 if running:
262 self._sendWinServiceEvent(name, summary, Clear)
263 else:
264 self._sendWinServiceEvent(name, summary, stoppedSeverity)
265 logLevel = logging.CRITICAL
266 else:
267 # if a down service was changed from important to unimportant,
268 # emit a clear event
269 if service_was_important and not running:
270 self._sendWinServiceEvent(name, summary, Clear)
271
272 self._taskConfig.services[name] = (
273 running, stoppedSeverity, startMode, monitoredStartModes)
274
275 log.log(logLevel, '%s on %s', summary, self._devId)
276
278 """
279 Callback for a successful fetch of services from the remote device.
280 """
281 self.state = ZenWinTask.STATE_WATCHER_PROCESS
282
283 log.debug("Successful collection from %s [%s], results=%s",
284 self._devId, self._manageIp, results)
285
286 # make a local copy of monitored services list
287 services = self._taskConfig.services.copy()
288 if results:
289 for result in [r.targetInstance for r in results]:
290 if result.state:
291 if result.name in services:
292 # remove service from local copy
293 del services[result.name]
294 self._handleResult(
295 result.name, result.state, result.startmode)
296 # send events for the services that did not show up in results
297 for name, data in services.items():
298 running, failSeverity, startMode, monitoredStartModes = data
299 if running:
300 state = self.RUNNING
301 else:
302 state = self.STOPPED
303 self._handleResult(name, state, startMode)
304 if results:
305 # schedule another immediate collection so that we'll keep eating
306 # events as long as they are ready for us; using callLater ensures
307 # it goes to the end of the immediate work-queue so that other
308 # events get processing time
309 log.debug("Queuing another fetch for %s [%s]",
310 self._devId, self._manageIp)
311 d = defer.Deferred()
312 reactor.callLater(0, d.callback, None)
313 d.addCallback(self._collectCallback)
314 return d
315
317 msg = 'WMI connection to %s up.' % self._devId
318 self._eventService.sendEvent(dict(
319 summary=msg,
320 eventClass=Status_Wmi,
321 device=self._devId,
322 severity=Clear,
323 component='zenwin'))
324 return result
325
327 """
328 Callback called after a connect or previous collection so that another
329 collection can take place.
330 """
331 log.debug("Polling for events from %s [%s]",
332 self._devId, self._manageIp)
333
334 self.state = ZenWinTask.STATE_WATCHER_QUERY
335 d = self._watcher.getEvents(self._queryTimeout, self._batchSize)
336 d.addCallbacks(self._collectSuccessful, self._failure)
337 d.addCallbacks(self._deviceUp)
338 return d
339
341 """
342 Callback called after a successful connect to the remote Windows device.
343 """
344 log.debug("Connected to %s [%s]", self._devId, self._manageIp)
345
347 self.state = ZenWinTask.STATE_WMIC_PROCESS
348 for service in result['query']:
349 self._handleResult(service.name, service.state, service.startmode)
350 self._wmic.close()
351 self._wmic = None
352 self.state = ZenWinTask.STATE_WATCHER_CONNECT
353 wql = "SELECT * FROM __InstanceModificationEvent WITHIN 5 "\
354 "WHERE TargetInstance ISA 'Win32_Service'"
355 self._watcher = Watcher(self._taskConfig, wql)
356 return self._watcher.connect()
357
359 self.state = ZenWinTask.STATE_WMIC_QUERY
360 wql = "SELECT Name, State, StartMode FROM Win32_Service"
361 d = self._wmic.query({'query': wql})
362 d.addCallback(self._connectWatcher)
363 return d
364
366 """
367 Called when a connection needs to be created to the remote Windows
368 device.
369 """
370 log.debug("Connecting to %s [%s]", self._devId, self._manageIp)
371 self.state = ZenWinTask.STATE_WMIC_CONNECT
372 self._wmic = WMIClient(self._taskConfig)
373 d = self._wmic.connect()
374 d.addCallback(self._initialQuery)
375 return d
376
378 return self._reset()
379
381 log.debug("Scanning device %s [%s]", self._devId, self._manageIp)
382
383 # see if we need to connect first before doing any collection
384 if not self._watcher:
385 d = self._connect()
386 d.addCallbacks(self._connectCallback, self._failure)
387 else:
388 # since we don't need to bother connecting, we'll just create an
389 # empty deferred and have it run immediately so the collect callback
390 # will be fired off
391 d = defer.Deferred()
392 reactor.callLater(0, d.callback, None)
393
394 # try collecting events after a successful connect, or if we're already
395 # connected
396 d.addCallback(self._collectCallback)
397
398 # Add the _finished callback to be called in both success and error
399 # scenarios. While we don't need final error processing in this task,
400 # it is good practice to catch any final errors for diagnostic purposes.
401 d.addBoth(self._finished)
402
403 # returning a Deferred will keep the framework from assuming the task
404 # is done until the Deferred actually completes
405 return d
406
407
408 #
409 # Collector Daemon Main entry point
410 #
411 if __name__ == '__main__':
412 myPreferences = ZenWinPreferences()
413 myTaskFactory = SimpleTaskFactory(ZenWinTask)
414 myTaskSplitter = SimpleTaskSplitter(myTaskFactory)
415 daemon = CollectorDaemon(myPreferences, myTaskSplitter)
416 daemon.run()
417
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1.1812 on Tue Oct 11 12:51:50 2011 | http://epydoc.sourceforge.net |