1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__ = """PingTask
15
16 Determines the availability of a IP addresses using ping (ICMP).
17
18 """
19
20 import re
21 import time
22 import logging
23 log = logging.getLogger("zen.zenping")
24
25 from twisted.python.failure import Failure
26
27 import Globals
28 import zope.interface
29 import zope.component
30
31 from zenoss.protocols.protobufs.zep_pb2 import SEVERITY_CLEAR
32
33 from Products.ZenCollector.interfaces import ICollector, ICollectorPreferences,\
34 IDataService,\
35 IEventService,\
36 IScheduledTask
37 from Products.ZenCollector.tasks import TaskStates, BaseTask
38
39 from Products.ZenStatus.PingJob import PingJob
40 from Products.ZenStatus.PingService import PingJobError
41 from Products.ZenUtils.Utils import unused
42 from Products.ZenCollector.services.config import DeviceProxy
43 unused(DeviceProxy)
44
45 from Products.ZenEvents.ZenEventClasses import Status_Ping
46 from Products.ZenEvents import Event
47
48 from Products.ZenUtils.IpUtil import ipunwrap
49
50
51
52 COLLECTOR_NAME = "zenping"
53 TOPOLOGY_MODELER_NAME = "topology_modeler"
54 MAX_BACK_OFF_MINUTES = 20
55 MAX_IFACE_PING_JOBS = 10
56
57 STATUS_EVENT = {
58 'eventClass' : Status_Ping,
59 'component' : 'zenping',
60 'eventGroup' : 'Ping' }
61
63 zope.interface.implements(IScheduledTask)
64
65 STATE_PING_START = 'PING_START'
66 STATE_PING_STOP = 'PING_STOP'
67 STATE_STORE_PERF = 'STORE_PERF_DATA'
68 STATE_UPDATE_TOPOLOGY = 'UPDATE_TOPOLOGY'
69
70 - def __init__(self,
71 taskName,
72 deviceId,
73 scheduleIntervalSeconds,
74 taskConfig):
75 """
76 @param deviceId: the Zenoss deviceId to watch
77 @type deviceId: string
78 @param taskName: the unique identifier for this task
79 @type taskName: string
80 @param scheduleIntervalSeconds: the interval at which this task will be
81 collected
82 @type scheduleIntervalSeconds: int
83 @param taskConfig: the configuration for this task
84 """
85 super(PingCollectionTask, self).__init__(
86 taskName, deviceId,
87 scheduleIntervalSeconds, taskConfig
88 )
89
90
91 self.name = taskName
92 self.configId = deviceId
93 self.state = TaskStates.STATE_IDLE
94
95
96 self._device = taskConfig
97 self._devId = deviceId
98 self._manageIp = ipunwrap(self._device.manageIp)
99 self.interval = scheduleIntervalSeconds
100
101 self._daemon = zope.component.queryUtility(ICollector)
102 self._dataService = zope.component.queryUtility(IDataService)
103 self._eventService = zope.component.queryUtility(IEventService)
104
105 self._preferences = zope.component.queryUtility(ICollectorPreferences,
106 COLLECTOR_NAME)
107 self._maxbackoffseconds = self._preferences.options.maxbackoffminutes * 60
108
109 self.startTime = None
110 self._lastStatus = ''
111
112
113 self.config = self._device.monitoredIps[0]
114 self._iface = self.config.iface
115 self.pingjob = PingJob(ipunwrap(self.config.ip), self._devId,
116 maxtries=self.config.tries,
117 sampleSize=self.config.sampleSize,
118 iface=self._iface)
119 self.pingjob.points = self.config.points
120
121 if self.config.ipVersion == 6:
122 self._network = self._daemon.ipv6network
123 self._pinger = self._preferences.pinger6
124 else:
125 self._network = self._daemon.network
126 self._pinger = self._preferences.pinger4
127
128 self._addToTopology()
129
130 self._lastErrorMsg = ''
131
133 """
134 Update the topology with our local knowledge of how we're connected.
135 """
136 ip = self.config.ip
137 if ip not in self._network.topology:
138 self._network.topology.add_node(ip)
139 self._network.topology.node[ip]['task'] = self
140
141 internalEdge = (self._manageIp, ip)
142 if ip != self._manageIp and \
143 not self._network.topology.has_edge(*internalEdge):
144 self._network.topology.add_edge(*internalEdge)
145
147 """
148 Twisted errBack to log the exception for a single IP.
149
150 @parameter reason: explanation of the failure
151 @type reason: Twisted error instance
152 """
153
154 msg = reason.getErrorMessage()
155 if not msg:
156 msg = reason.__class__
157
158 msg = '%s %s' % (self._devId, msg)
159
160
161 if self._lastErrorMsg != msg:
162 self._lastErrorMsg = msg
163 if msg:
164 log.error(msg)
165 self._eventService.sendEvent(STATUS_EVENT,
166 device=self._devId,
167 summary=msg,
168 severity=Event.Error)
169 self._delayNextCheck()
170
171 return reason
172
174 """
175 Contact to one device and return a deferred which gathers data from
176 the device.
177
178 @return: A task to ping the device and any of its interfaces.
179 @rtype: Twisted deferred object
180 """
181
182 self.pingjob.reset()
183
184
185 self._pinger.ping(self.pingjob)
186 d = self.pingjob.deferred
187 d.addCallback(self._storeResults)
188 d.addBoth(self._updateStatus)
189 d.addCallback(self._returnToNormalSchedule)
190 d.addErrback(self._failure)
191
192
193 return d
194
196 """
197 Store the datapoint results asked for by the RRD template.
198 """
199 self.state = PingCollectionTask.STATE_STORE_PERF
200 if self.pingjob.rtt >= 0 and self.pingjob.points:
201 self.pingjob.calculateStatistics()
202 for rrdMeta in self.pingjob.points:
203 name, path, rrdType, rrdCommand, rrdMin, rrdMax = rrdMeta
204 value = getattr(self.pingjob, name, None)
205 if value is None:
206 log.debug("No datapoint '%s' found on the %s pingjob",
207 name, self.pingjob.ipaddr)
208 continue
209 self._dataService.writeRRD(path, value, rrdType,
210 rrdCommand=rrdCommand,
211 min=rrdMin, max=rrdMax)
212
213 return result
214
216 """
217 Update the modeler and handle issues
218
219 @parameter result: results of Ping or a failure
220 @type result: array of (boolean, dictionaries)
221 """
222 self.state = PingCollectionTask.STATE_UPDATE_TOPOLOGY
223
224
225 if isinstance(result, Failure) and \
226 not isinstance(result.value, PingJobError):
227 return Failure
228
229 ip = self.pingjob.ipaddr
230 if self.pingjob.rtt >= 0:
231 success = 'Success'
232 self._network.downDevices.discard(ip)
233 self.sendPingClearEvent(self.pingjob)
234 else:
235 success = 'Failed'
236 self._network.downDevices.add(ip)
237 log.warning("No ping response for %s in %d tries",
238 self.pingjob.ipaddr, self.pingjob.sent)
239 resultMsg = "%s RTT = %s sec (%s)" % (
240 ip, self.pingjob.rtt, success)
241
242 self._lastStatus = resultMsg
243 return resultMsg
244
246 """
247 Send an event based on a ping job to the event backend.
248 """
249 msg = msgTpl % self._devId
250 evt = dict(device=self._devId,
251 ipAddress=pj.ipaddr,
252 summary=msg,
253 severity=SEVERITY_CLEAR,
254 eventClass=Status_Ping,
255 eventGroup='Ping',
256 component=self._iface)
257 evstate = getattr(pj, 'eventState', None)
258 if evstate is not None:
259 evt['eventState'] = evstate
260 self._eventService.sendEvent(evt)
261
263 """
264 Called by the collector framework scheduler, and allows us to
265 see how each task is doing.
266 """
267 display = '\t'.join([self.name, "%s secs" % self.interval, self._lastStatus])
268 if self._lastErrorMsg:
269 display += "\n%s\n" % self._lastErrorMsg
270 return display
271
274