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

# Source Code for Module Products.ZenStatus.zenping

      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) 2007, 2010 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  __doc__ = """zenping
     17
     18  Determines the availability of a IP addresses using ping (ICMP).
     19
     20  """
     21
     22  import os
     23  import os.path
     24  import sys
     25  import re
     26  from socket import gethostbyname, getfqdn, gaierror
     27  import time
     28  import logging
     29  log = logging.getLogger("zen.zenping")
     30
     31  # Zenoss custom ICMP library
     32  from icmpecho.Ping import Ping4, Ping6
     33
     34  import Globals
     35  import zope.interface
     36  import zope.component
     37  from Products.Five import zcml
     38  import Products
     39  import Products.Five
     40  import Products.ZenStatus
     41
     42  from Products.ZenCollector.daemon import CollectorDaemon
     43  from Products.ZenCollector.interfaces import ICollector, ICollectorPreferences,\
     44                                               IConfigurationListener
     45  from Products.ZenCollector.tasks import SimpleTaskFactory,\
     46                                          SubConfigurationTaskSplitter
     47  from Products.ZenStatus.interfaces import IPingCorrelatorTaskFactory
     48
     49  from Products.ZenUtils.IpUtil import ipunwrap
     50
     51  from Products.ZenStatus.PingService import PingService
     52  from Products.ZenStatus.TestPing import Ping as TestPing
     53  from Products.ZenStatus.TracerouteTask import TracerouteTask
     54  from Products.ZenStatus.PingTask import PingCollectionTask
     55  from Products.ZenStatus.NetworkModel import NetworkModel
     56
     57  from Products.ZenUtils.Utils import unused
     58  from Products.ZenCollector.services.config import DeviceProxy
     59  unused(DeviceProxy)
     60  from Products.ZenHub.services.PingPerformanceConfig import PingPerformanceConfig
     61  unused(PingPerformanceConfig)
     62
     63
     64  COLLECTOR_NAME = "zenping"
     65  TOPOLOGY_MODELER_NAME = "topology_modeler"
     66  TOPOLOGY_CORRELATOR_NAME = "topology_correlator"
     67  MAX_BACK_OFF_MINUTES = 20
     68  MAX_IFACE_PING_JOBS = 10
     69
     70
     71 -class PingCollectionPreferences(object):
     72      zope.interface.implements(ICollectorPreferences)
     73
     74 -    def __init__(self):
     75          """
     76          Constructs a new PingCollectionPreferences instance and
     77          provides default values for needed attributes.
     78          """
     79          self.collectorName = COLLECTOR_NAME
     80          self.defaultRRDCreateCommand = None
     81          self.configCycleInterval = 20 # minutes
     82          self.cycleInterval = 5 * 60 # seconds
     83          self.pauseUnreachableDevices = False
     84
     85          # The configurationService attribute is the fully qualified class-name
     86          # of our configuration service that runs within ZenHub
     87          self.configurationService = 'Products.ZenHub.services.PingPerformanceConfig'
     88
     89          # Will be filled in based on buildOptions
     90          self.options = None
     91
     92          self.pingTimeOut = 1.5
     93          self.pingTries = 2
     94          self.pingChunk = 75
     95          self.pingCycleInterval = 60
     96          self.configCycleInterval = 20*60
     97          self.maxPingFailures = 2
     98          self.pinger4 = None
     99          self.pinger6 = None
    100
    101          self.topologySaveTime = time.time()
    102
    103 -    def buildOptions(self, parser):
    104          parser.add_option('--showrawresults',
    105                            dest='showrawresults',
    106                            action="store_true",
    107                            default=False,
    108                            help="Show the raw RRD values. For debugging purposes only.")
    109          parser.add_option('--name',
    110                            dest='name',
    111                            default=findIp(),
    112                            help=("Host that roots the ping dependency "
    113                                  "tree: typically the collecting hosts' "
    114                                  "name; defaults to our fully qualified "
    115                                  "domain name (%default)"))
    116          parser.add_option('--test',
    117                            dest='test',
    118                            default=False,
    119                            action="store_true",
    120                            help="Run in test mode: doesn't really ping,"
    121                                 " but reads the list of IP Addresses that "
    122                                 " are up from /tmp/testping")
    123          parser.add_option('--maxbackoffminutes',
    124                            dest='maxbackoffminutes',
    125                            default=MAX_BACK_OFF_MINUTES,
    126                            type='int',
    127                            help="When a device fails to respond, increase the time to" \
    128                                 " check on the device until this limit.")
    129          parser.add_option('--tracetimeoutseconds',
    130                            dest='tracetimeoutseconds',
    131                            default=40,
    132                            type='int',
    133                            help="Wait for a maximum of this time before stopping"
    134                                 " the traceroute")
    135          parser.add_option('--tracechunk',
    136                            dest='tracechunk',
    137                            default=5,
    138                            type='int',
    139                            help="Number of devices to traceroute at once.")
    140          parser.add_option('--ipv4topofile',
    141                            dest='ipv4topofile',
    142                            default='',
    143                            help="Use this file rather than the default for IPv4.")
    144          parser.add_option('--ipv6topofile',
    145                            dest='ipv6topofile',
    146                            default='',
    147                            help="Use this file rather than the default for IPv6.")
    148          parser.add_option('--savetopominutes',
    149                            dest='savetopominutes',
    150                            default=45,
    151                            type='int',
    152                            help="When cycling, save the topology this number of minutes.")
    153          parser.add_option('--showroute',
    154                            dest='showroute',
    155                            default='',
    156                            help="Show the route from the collector to the end point.")
    157
    158 -    def postStartup(self):
    159          daemon = zope.component.getUtility(ICollector)
    160
    161          daemon.network = NetworkModel()
    162          daemon.ipv6network = NetworkModel(version=6)
    163          if self.options.showroute:
    164              targetNode = self.options.showroute
    165              if daemon.network.resolveName(targetNode):
    166                  daemon.network.showRoute(targetNode)
    167
    168              elif daemon.ipv6network.resolveName(targetNode):
    169                  daemon.ipv6network.showRoute(targetNode)
    170              else:
    171                  log.error("The IP address %s does not exist in IPv4 or IPv6 topologies.",
    172                            targetNode)
    173              sys.exit(0)
    174
    175          # Initialize our connection to the ping socket
    176          self._getPinger()
    177
    178          # Start modeling network topology
    179          task = TracerouteTask(TOPOLOGY_MODELER_NAME + ' IPv4',
    180                                     configId=TOPOLOGY_MODELER_NAME,
    181                                     taskConfig=daemon._prefs)
    182          daemon._scheduler.addTask(task, now=True)
    183
    184          task = TracerouteTask(TOPOLOGY_MODELER_NAME + ' IPv6',
    185                                     configId=TOPOLOGY_MODELER_NAME,
    186                                     taskConfig=daemon._prefs)
    187          daemon._scheduler.addTask(task, now=True)
    188
    189          self.addCorrelatorTasks(daemon)
    190
    191 -    def addCorrelatorTasks(self, daemon):
    192          # Load up interface stuff
    193          zcml.load_config('meta.zcml', Products.Five)
    194          zcml.load_config('configure.zcml', Products.ZenStatus)
    195
    196          factory = zope.component.queryUtility(IPingCorrelatorTaskFactory)
    197
    198          factory.name = TOPOLOGY_CORRELATOR_NAME + ' IPv4'
    199          factory.configId = TOPOLOGY_CORRELATOR_NAME
    200          factory.config = daemon._prefs
    201          factory.interval = 60
    202
    203          task = factory.build()
    204          daemon._scheduler.addTask(task, now=True)
    205
    206          factory.name = TOPOLOGY_CORRELATOR_NAME + ' IPv6'
    207          task = factory.build()
    208          daemon._scheduler.addTask(task, now=True)
    209
    210 -    def preShutdown(self):
    211          daemon = zope.component.getUtility(ICollector)
    212
    213          # If we're running as a daemon, save the topology
    214          daemon.network._saveTopology()
    215          daemon.ipv6network._saveTopology()
    216
    217 -    def _getPinger(self):
    218          if self.pinger4:
    219              self.pinger4.reconfigure(self.pingTimeOut)
    220              self.pinger6.reconfigure(self.pingTimeOut)
    221          else:
    222              if self.options.test:
    223                  self.pinger4 = TestPing(self.pingTimeOut)
    224                  self.pinger6 = TestPing(self.pingTimeOut)
    225              else:
    226                  # pyraw inserts these magic values
    227                  if IPV4_SOCKET is not None:
    228                    protocol = Ping4(IPV4_SOCKET)
    229                    self.pinger4 = PingService(protocol)
    230                  else:
    231                    self.pinger4 = None
    232                  if IPV6_SOCKET is not None:
    233                    protocol = Ping6(IPV6_SOCKET)
    234                    self.pinger6 = PingService(protocol)
    235                  else:
    236                    self.pinger6 = None
    237
    238
    239 -class PerIpAddressTaskSplitter(SubConfigurationTaskSplitter):
    240      subconfigName = 'monitoredIps'
    241
    242 -    def makeConfigKey(self, config, subconfig):
    243          return (config.id, subconfig.cycleTime, ipunwrap(subconfig.ip))
    244
    245
    246 -class TopologyUpdater(object):
    247      """
    248      This updates the link between a device and the topology.
    249      """
    250      zope.interface.implements(IConfigurationListener)
    251
    252 -    def deleted(self, configurationId):
    253          """
    254          Called when a configuration is deleted from the collector
    255          If the device was down and we don't remove from topology,
    256          we will send out a bogus 'device down' event.
    257          """
    258          daemon = zope.component.getUtility(ICollector)
    259          if daemon.network.topology is None:
    260              return
    261
    262          tasks = daemon._scheduler.getTasksForConfig(configurationId)
    263          if not tasks:
    264              log.debug("No tasks found to delete for device '%s'",
    265                            configurationId)
    266          for task in tasks:
    267              ipAddress = task._manageIp
    268              if ipAddress in daemon.network.topology:
    269                  daemon.network.removeDevice(ipAddress)
    270
    271              elif ipAddress in daemon.ipv6network.topology:
    272                  daemon.ipv6network.removeDevice(ipAddress)
    273
    274              else:
    275                  log.debug("%s IP address %s not in topology",
    276                            configurationId, ipAddress)
    277
    278 -    def added(self, configuration):
    279          """
    280          Called when a configuration is added to the collector.
    281          This links the schedulable tasks to the topology so that we can
    282          check on the status of the network devices.
    283          """
    284          log.debug("zenhub asked us to add device: %s (%s)",
    285                        configuration.id, configuration.manageIp)
    286
    287 -    def updated(self, newConfiguration):
    288          """
    289          Called when a configuration is updated in collector
    290          """
    291          log.debug("zenhub asked us to update device: %s (%s)",
    292                        newConfiguration.id, newConfiguration.manageIp)
    293          pass
    294
    295
    296 -def findIp():
    297      try:
    298          return gethostbyname(getfqdn())
    299      except gaierror:
    300          # find the first non-loopback interface address
    301          ifconfigs = ['/sbin/ifconfig',
    302                       '/usr/sbin/ifconfig',
    303                       '/usr/bin/ifconfig',
    304                       '/bin/ifconfig']
    305          ifconfig = filter(os.path.exists, ifconfigs)[0]
    306          fp = os.popen(ifconfig + ' -a')
    307          config = fp.read().split('\n\n')
    308          fp.close()
    309          digits = r'[0-9]{1,3}'
    310          pat = r'(addr:|inet) *(%s\.%s\.%s\.%s)[^0-9]' % ((digits,)*4)
    311          parse = re.compile(pat)
    312          results = []
    313          for c in config:
    314              addr = parse.search(c)
    315              if addr:
    316                  results.append(addr.group(2))
    317          try:
    318              results.remove('127.0.0.1')
    319          except ValueError:
    320              pass
    321          if results:
    322              return results[0]
    323      return '127.0.0.1'
    324
    325
    326  if __name__=='__main__':
    327      myPreferences = PingCollectionPreferences()
    328      myTaskFactory = SimpleTaskFactory(PingCollectionTask)
    329      myTaskSplitter = PerIpAddressTaskSplitter(myTaskFactory)
    330      myListener = TopologyUpdater()
    331      daemon = CollectorDaemon(myPreferences, myTaskSplitter,
    332                              configurationLister=myListener,
    333                              stoppingCallback=myPreferences.preShutdown)
    334      daemon.run()
    335