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