Package Products :: Package ZenStatus :: Module PingTask
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenStatus.PingTask

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, 2010 Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 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  # Try a circular import? 
 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   
62 -class PingCollectionTask(BaseTask):
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 # Needed for interface 91 self.name = taskName 92 self.configId = deviceId 93 self.state = TaskStates.STATE_IDLE 94 95 # The taskConfig corresponds to a DeviceProxy 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 # Split up so that every interface's IP gets its own ping job 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
132 - def _addToTopology(self):
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
146 - def _failure(self, reason):
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 # Decode the exception 154 msg = reason.getErrorMessage() 155 if not msg: # Sometimes we get blank error messages 156 msg = reason.__class__ 157 158 msg = '%s %s' % (self._devId, msg) 159 160 # Leave 'reason' alone to generate a traceback 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
173 - def doTask(self):
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 # Reset statistics for this run of data collection 182 self.pingjob.reset() 183 184 # Start the ping job 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 # Wait until the Deferred actually completes 193 return d
194
195 - def _storeResults(self, result):
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
215 - def _updateStatus(self, result):
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 # Catch ping job errors 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
245 - def sendPingClearEvent(self, pj, msgTpl='%s is UP!'):
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
262 - def displayStatistics(self):
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
272 - def cleanup(self):
273 self.sendPingClearEvent(self.pingjob, "No longer testing device %s")
274