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