Pylint no-else-return refactoring: tenth (and so far last) set of files
[check_mk.git] / cmk_base / parent_scan.py
blob5a175b5d8a74dff386a3f587b5af493111096382
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 import os
28 import sys
29 import socket
30 import subprocess
31 import time
32 import pprint
34 import cmk.tty as tty
35 import cmk.paths
36 from cmk.exceptions import MKGeneralException
38 import cmk_base.utils
39 import cmk_base.console as console
40 import cmk_base.config as config
41 import cmk_base.ip_lookup as ip_lookup
44 def do_scan_parents(hosts):
45 if not hosts:
46 hosts = [h
47 for h in config.all_active_realhosts()
48 if config.in_binary_hostlist(h, config.scanparent_hosts)]
50 parent_hosts = []
51 parent_ips = {}
52 parent_rules = []
53 gateway_hosts = set([])
55 if config.max_num_processes < 1:
56 config.max_num_processes = 1
58 outfilename = cmk.paths.check_mk_config_dir + "/parents.mk"
60 if not traceroute_available():
61 raise MKGeneralException(
62 'The program "traceroute" was not found.\n'
63 'The parent scan needs this program.\n'
64 'Please install it and try again.')
66 if os.path.exists(outfilename):
67 first_line = file(outfilename, "r").readline()
68 if not first_line.startswith('# Automatically created by --scan-parents at'):
69 raise MKGeneralException("conf.d/parents.mk seems to be created manually.\n\n"
70 "The --scan-parents function would overwrite this file.\n"
71 "Please rename it to keep the configuration or delete "
72 "the file and try again.")
74 console.output("Scanning for parents (%d processes)..." % config.max_num_processes)
75 while len(hosts) > 0:
76 chunk = []
77 while len(chunk) < config.max_num_processes and len(hosts) > 0:
78 host = hosts[0]
79 del hosts[0]
80 # skip hosts that already have a parent
81 if config.parents_of(host):
82 console.verbose("(manual parent) ")
83 continue
84 chunk.append(host)
86 gws = scan_parents_of(chunk)
88 for host, (gw, _unused_state, _unused_ping_fails, _unused_message) in zip(chunk, gws):
89 if gw:
90 gateway, gateway_ip, dns_name = gw
91 if not gateway: # create artificial host
92 if dns_name:
93 gateway = dns_name
94 else:
95 gateway = "gw-%s" % (gateway_ip.replace(".", "-"))
96 if gateway not in gateway_hosts:
97 gateway_hosts.add(gateway)
98 parent_hosts.append("%s|parent|ping" % gateway)
99 parent_ips[gateway] = gateway_ip
100 if config.monitoring_host:
101 parent_rules.append( (config.monitoring_host, [gateway]) ) # make Nagios a parent of gw
102 parent_rules.append( (gateway, [host]) )
103 elif host != config.monitoring_host and config.monitoring_host:
104 # make monitoring host the parent of all hosts without real parent
105 parent_rules.append( (config.monitoring_host, [host]) )
107 with file(outfilename, "w") as out:
108 out.write("# Automatically created by --scan-parents at %s\n\n" % time.asctime())
109 out.write("# Do not edit this file. If you want to convert an\n")
110 out.write("# artificial gateway host into a permanent one, then\n")
111 out.write("# move its definition into another *.mk file\n")
113 out.write("# Parents which are not listed in your all_hosts:\n")
114 out.write("all_hosts += %s\n\n" % pprint.pformat(parent_hosts))
116 out.write("# IP addresses of parents not listed in all_hosts:\n")
117 out.write("ipaddresses.update(%s)\n\n" % pprint.pformat(parent_ips))
119 out.write("# Parent definitions\n")
120 out.write("parents += %s\n\n" % pprint.pformat(parent_rules))
121 console.output("\nWrote %s\n" % outfilename)
124 def traceroute_available():
125 for path in os.environ['PATH'].split(os.pathsep):
126 f = path + '/traceroute'
127 if os.path.exists(f) and os.access(f, os.X_OK):
128 return f
131 def scan_parents_of(hosts, silent=False, settings=None):
132 if settings is None:
133 settings = {}
135 if config.monitoring_host:
136 nagios_ip = ip_lookup.lookup_ipv4_address(config.monitoring_host)
137 else:
138 nagios_ip = None
140 os.putenv("LANG", "")
141 os.putenv("LC_ALL", "")
143 # Start processes in parallel
144 procs = []
145 for host in hosts:
146 console.verbose("%s " % host)
147 try:
148 ip = ip_lookup.lookup_ipv4_address(host)
149 command = [ "traceroute",
150 "-w", "%d" % settings.get("timeout", 8),
151 "-q", "%d" % settings.get("probes", 2),
152 "-m", "%d" % settings.get("max_ttl", 10),
153 "-n", ip ]
154 console.vverbose("Running '%s'\n" % subprocess.list2cmdline(command))
156 procs.append((host, ip, subprocess.Popen(command,
157 stdout=subprocess.PIPE,
158 stderr=subprocess.STDOUT,
159 close_fds=True)))
160 except Exception, e:
161 if cmk.debug.enabled():
162 raise
163 procs.append((host, None, "ERROR: %s" % e))
165 # Output marks with status of each single scan
166 def dot(color, dot='o'):
167 if not silent:
168 console.output(tty.bold + color + dot + tty.normal)
170 # Now all run and we begin to read the answers. For each host
171 # we add a triple to gateways: the gateway, a scan state and a diagnostic output
172 gateways = []
173 for host, ip, proc_or_error in procs:
174 if type(proc_or_error) in [ str, unicode ]:
175 lines = [ proc_or_error ]
176 exitstatus = 1
177 else:
178 exitstatus = proc_or_error.wait()
179 lines = [ l.strip() for l in proc_or_error.stdout.readlines() ]
181 if exitstatus:
182 dot(tty.red, '*')
183 gateways.append((None, "failed", 0, "Traceroute failed with exit code %d" % (exitstatus & 255)))
184 continue
186 if len(lines) == 1 and lines[0].startswith("ERROR:"):
187 message = lines[0][6:].strip()
188 console.verbose("%s: %s\n", host, message, stream=sys.stderr)
189 dot(tty.red, "D")
190 gateways.append((None, "dnserror", 0, message))
191 continue
193 elif len(lines) == 0:
194 if cmk.debug.enabled():
195 raise MKGeneralException("Cannot execute %s. Is traceroute installed? Are you root?" % command)
196 else:
197 dot(tty.red, '!')
198 continue
200 elif len(lines) < 2:
201 if not silent:
202 console.error("%s: %s\n" % (host, ' '.join(lines)))
203 gateways.append((None, "garbled", 0, "The output of traceroute seem truncated:\n%s" %
204 ("".join(lines))))
205 dot(tty.blue)
206 continue
208 # Parse output of traceroute:
209 # traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 40 byte packets
210 # 1 * * *
211 # 2 10.0.0.254 0.417 ms 0.459 ms 0.670 ms
212 # 3 172.16.0.254 0.967 ms 1.031 ms 1.544 ms
213 # 4 217.0.116.201 23.118 ms 25.153 ms 26.959 ms
214 # 5 217.0.76.134 32.103 ms 32.491 ms 32.337 ms
215 # 6 217.239.41.106 32.856 ms 35.279 ms 36.170 ms
216 # 7 74.125.50.149 45.068 ms 44.991 ms *
217 # 8 * 66.249.94.86 41.052 ms 66.249.94.88 40.795 ms
218 # 9 209.85.248.59 43.739 ms 41.106 ms 216.239.46.240 43.208 ms
219 # 10 216.239.48.53 45.608 ms 47.121 ms 64.233.174.29 43.126 ms
220 # 11 209.85.255.245 49.265 ms 40.470 ms 39.870 ms
221 # 12 8.8.8.8 28.339 ms 28.566 ms 28.791 ms
222 routes = []
223 for line in lines[1:]:
224 parts = line.split()
225 route = parts[1]
226 if route.count('.') == 3:
227 routes.append(route)
228 elif route == '*':
229 routes.append(None) # No answer from this router
230 else:
231 if not silent:
232 console.error("%s: invalid output line from traceroute: '%s'\n" %
233 (host, line))
235 if len(routes) == 0:
236 error = "incomplete output from traceroute. No routes found."
237 console.error("%s: %s\n" % (host, error))
238 gateways.append((None, "garbled", 0, error))
239 dot(tty.red)
240 continue
242 # Only one entry -> host is directly reachable and gets nagios as parent -
243 # if nagios is not the parent itself. Problem here: How can we determine
244 # if the host in question is the monitoring host? The user must configure
245 # this in monitoring_host.
246 elif len(routes) == 1:
247 if ip == nagios_ip:
248 gateways.append( (None, "root", 0, "") ) # We are the root-monitoring host
249 dot(tty.white, 'N')
250 elif config.monitoring_host:
251 gateways.append( ((config.monitoring_host, nagios_ip, None), "direct", 0, "") )
252 dot(tty.cyan, 'L')
253 else:
254 gateways.append( (None, "direct", 0, "") )
255 continue
257 # Try far most route which is not identical with host itself
258 ping_probes = settings.get("ping_probes", 5)
259 skipped_gateways = 0
260 route = None
261 for r in routes[::-1]:
262 if not r or (r == ip):
263 continue
264 # Do (optional) PING check in order to determine if that
265 # gateway can be monitored via the standard host check
266 if ping_probes:
267 if not gateway_reachable_via_ping(r, ping_probes):
268 console.verbose("(not using %s, not reachable)\n", r, stream=sys.stderr)
269 skipped_gateways += 1
270 continue
271 route = r
272 break
273 if not route:
274 error = "No usable routing information"
275 if not silent:
276 console.error("%s: %s\n" % (host, error))
277 gateways.append((None, "notfound", 0, error))
278 dot(tty.blue)
279 continue
281 # TTLs already have been filtered out)
282 gateway_ip = route
283 gateway = _ip_to_hostname(route)
284 if gateway:
285 console.verbose("%s(%s) ", gateway, gateway_ip)
286 else:
287 console.verbose("%s ", gateway_ip)
289 # Try to find DNS name of host via reverse DNS lookup
290 dns_name = _ip_to_dnsname(gateway_ip)
291 gateways.append( ((gateway, gateway_ip, dns_name), "gateway", skipped_gateways, "") )
292 dot(tty.green, 'G')
293 return gateways
296 def gateway_reachable_via_ping(ip, probes):
297 return subprocess.call(["ping", "-q", "-i", "0.2", "-l", "3", "-c",
298 "%d" % probes, "-W", "5", ip ],
299 stdout=open(os.devnull, "w"),
300 stderr=subprocess.STDOUT, close_fds=True) == 0
303 # find hostname belonging to an ip address. We must not use
304 # reverse DNS but the Check_MK mechanisms, since we do not
305 # want to find the DNS name but the name of a matching host
306 # from all_hosts
307 def _ip_to_hostname(ip):
308 if not cmk_base.config_cache.exists("ip_to_hostname"):
309 cache = cmk_base.config_cache.get_dict("ip_to_hostname")
311 for host in config.all_active_realhosts():
312 try:
313 cache[ip_lookup.lookup_ipv4_address(host)] = host
314 except:
315 pass
316 else:
317 cache = cmk_base.config_cache.get_dict("ip_to_hostname")
319 return cache.get(ip)
322 def _ip_to_dnsname(ip):
323 try:
324 return socket.gethostbyaddr(ip)[0]
325 except:
326 return None