Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk_base / core_config.py
blobe2d4854353df26034e066245a1c8f9a0e24bddce
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 abc
28 import numbers
29 import os
30 import sys
31 from typing import Any, List # pylint: disable=unused-import
33 import cmk.utils.paths
34 import cmk.utils.tty as tty
35 import cmk.utils.password_store
36 from cmk.utils.exceptions import MKGeneralException
38 import cmk_base.console as console
39 import cmk_base.config as config
40 import cmk_base.ip_lookup as ip_lookup
43 class MonitoringCore(object):
44 __metaclass__ = abc.ABCMeta
46 @abc.abstractmethod
47 def create_config(self):
48 pass
50 @abc.abstractmethod
51 def precompile(self):
52 pass
55 _ignore_ip_lookup_failures = False
56 _failed_ip_lookups = []
59 # .--Warnings------------------------------------------------------------.
60 # | __ __ _ |
61 # | \ \ / /_ _ _ __ _ __ (_)_ __ __ _ ___ |
62 # | \ \ /\ / / _` | '__| '_ \| | '_ \ / _` / __| |
63 # | \ V V / (_| | | | | | | | | | | (_| \__ \ |
64 # | \_/\_/ \__,_|_| |_| |_|_|_| |_|\__, |___/ |
65 # | |___/ |
66 # +----------------------------------------------------------------------+
67 # | Managing of warning messages occuring during configuration building |
68 # '----------------------------------------------------------------------'
70 g_configuration_warnings = [] # type: List[Any]
73 def initialize_warnings():
74 global g_configuration_warnings
75 g_configuration_warnings = []
78 def warning(text):
79 g_configuration_warnings.append(text)
80 console.warning("\n%s", text, stream=sys.stdout)
83 def get_configuration_warnings():
84 num_warnings = len(g_configuration_warnings)
86 if num_warnings > 10:
87 warnings = g_configuration_warnings[:10] + \
88 [ "%d further warnings have been omitted" % (num_warnings - 10) ]
89 else:
90 warnings = g_configuration_warnings
92 return warnings
95 # TODO: Cleanup the hostcheck_commands_to_define, custom_commands_to_define thing
96 def host_check_command(hostname,
97 ip,
98 is_clust,
99 hostcheck_commands_to_define=None,
100 custom_commands_to_define=None):
101 # Check dedicated host check command
102 values = config.host_extra_conf(hostname, config.host_check_commands)
103 if values:
104 value = values[0]
105 elif config.is_no_ip_host(hostname):
106 value = "ok"
107 elif config.monitoring_core == "cmc":
108 value = "smart"
109 else:
110 value = "ping"
112 if config.monitoring_core != "cmc" and value == "smart":
113 value = "ping" # avoid problems when switching back to nagios core
115 if value == "smart" and not is_clust:
116 return "check-mk-host-smart"
118 elif value in ["ping", "smart"]: # Cluster host
119 ping_args = check_icmp_arguments_of(hostname)
121 if is_clust and ip: # Do check cluster IP address if one is there
122 return "check-mk-host-ping!%s" % ping_args
123 elif ping_args and is_clust: # use check_icmp in cluster mode
124 return "check-mk-host-ping-cluster!%s" % ping_args
125 elif ping_args: # use special arguments
126 return "check-mk-host-ping!%s" % ping_args
128 return None
130 elif value == "ok":
131 return "check-mk-host-ok"
133 elif value == "agent" or value[0] == "service":
134 service = "Check_MK" if value == "agent" else value[1]
136 if config.monitoring_core == "cmc":
137 return "check-mk-host-service!" + service
139 command = "check-mk-host-custom-%d" % (len(hostcheck_commands_to_define) + 1)
140 hostcheck_commands_to_define.append(
141 (command, 'echo "$SERVICEOUTPUT:%s:%s$" && exit $SERVICESTATEID:%s:%s$' %
142 (hostname, service.replace('$HOSTNAME$', hostname), hostname,
143 service.replace('$HOSTNAME$', hostname))))
144 return command
146 elif value[0] == "tcp":
147 return "check-mk-host-tcp!" + str(value[1])
149 elif value[0] == "custom":
150 try:
151 custom_commands_to_define.add("check-mk-custom")
152 except:
153 pass # not needed and not available with CMC
154 return "check-mk-custom!" + autodetect_plugin(value[1])
156 raise MKGeneralException(
157 "Invalid value %r for host_check_command of host %s." % (value, hostname))
160 def autodetect_plugin(command_line):
161 plugin_name = command_line.split()[0]
162 if command_line[0] not in ['$', '/']:
163 try:
164 for directory in ["/local", ""]:
165 path = cmk.utils.paths.omd_root + directory + "/lib/nagios/plugins/"
166 if os.path.exists(path + plugin_name):
167 command_line = path + command_line
168 break
169 except:
170 pass
171 return command_line
174 def icons_and_actions_of(what, hostname, svcdesc=None, checkname=None, params=None):
175 if what == 'host':
176 return list(set(config.host_extra_conf(hostname, config.host_icons_and_actions)))
177 else:
178 actions = set(
179 config.service_extra_conf(hostname, svcdesc, config.service_icons_and_actions))
181 # Some WATO rules might register icons on their own
182 if checkname:
183 checkgroup = config.check_info[checkname]["group"]
184 if checkgroup in ['ps', 'services'] and isinstance(params, dict):
185 icon = params.get('icon')
186 if icon:
187 actions.add(icon)
189 return list(actions)
192 def check_icmp_arguments_of(hostname, add_defaults=True, family=None):
193 values = config.host_extra_conf(hostname, config.ping_levels)
194 levels = {}
195 for value in values[::-1]: # make first rules have precedence
196 levels.update(value)
197 if not add_defaults and not levels:
198 return ""
200 if family is None:
201 family = 6 if config.is_ipv6_primary(hostname) else 4
203 args = []
205 if family == 6:
206 args.append("-6")
208 rta = 200, 500
209 loss = 80, 100
210 for key, value in levels.items():
211 if key == "timeout":
212 args.append("-t %d" % value)
213 elif key == "packets":
214 args.append("-n %d" % value)
215 elif key == "rta":
216 rta = value
217 elif key == "loss":
218 loss = value
219 args.append("-w %.2f,%.2f%%" % (rta[0], loss[0]))
220 args.append("-c %.2f,%.2f%%" % (rta[1], loss[1]))
221 return " ".join(args)
225 # .--Core Config---------------------------------------------------------.
226 # | ____ ____ __ _ |
227 # | / ___|___ _ __ ___ / ___|___ _ __ / _(_) __ _ |
228 # | | | / _ \| '__/ _ \ | | / _ \| '_ \| |_| |/ _` | |
229 # | | |__| (_) | | | __/ | |__| (_) | | | | _| | (_| | |
230 # | \____\___/|_| \___| \____\___/|_| |_|_| |_|\__, | |
231 # | |___/ |
232 # +----------------------------------------------------------------------+
233 # | Code for managing the core configuration creation. |
234 # '----------------------------------------------------------------------'
237 # TODO: Move to modes?
238 def do_create_config(core, with_agents):
239 console.output("Generating configuration for core (type %s)..." % config.monitoring_core)
240 create_core_config(core)
241 console.output(tty.ok + "\n")
243 if with_agents:
244 try:
245 import cmk_base.cee.agent_bakery
246 cmk_base.cee.agent_bakery.bake_on_restart()
247 except ImportError:
248 pass
251 def create_core_config(core):
252 initialize_warnings()
254 _verify_non_duplicate_hosts()
255 _verify_non_deprecated_checkgroups()
256 core.create_config()
257 cmk.utils.password_store.save(config.stored_passwords)
259 return get_configuration_warnings()
262 # Verify that the user has no deprecated check groups configured.
263 def _verify_non_deprecated_checkgroups():
264 groups = config.checks_by_checkgroup()
266 for checkgroup in config.checkgroup_parameters:
267 if checkgroup not in groups:
268 warning(
269 "Found configured rules of deprecated check group \"%s\". These rules are not used "
270 "by any check. Maybe this check group has been renamed during an update, "
271 "in this case you will have to migrate your configuration to the new ruleset manually. "
272 "Please check out the release notes of the involved versions. "
273 "You may use the page \"Deprecated rules\" in WATO to view your rules and move them to "
274 "the new rulesets." % checkgroup)
277 def _verify_non_duplicate_hosts():
278 duplicates = config.duplicate_hosts()
279 if duplicates:
280 warning("The following host names have duplicates: %s. "
281 "This might lead to invalid/incomplete monitoring for these hosts." %
282 ", ".join(duplicates))
285 def do_update(core, with_precompile):
286 try:
287 do_create_config(core, with_agents=with_precompile)
288 if with_precompile:
289 core.precompile()
291 except Exception as e:
292 console.error("Configuration Error: %s\n" % e)
293 if cmk.utils.debug.enabled():
294 raise
295 sys.exit(1)
299 # .--Active Checks-------------------------------------------------------.
300 # | _ _ _ ____ _ _ |
301 # | / \ ___| |_(_)_ _____ / ___| |__ ___ ___| | _____ |
302 # | / _ \ / __| __| \ \ / / _ \ | | | '_ \ / _ \/ __| |/ / __| |
303 # | / ___ \ (__| |_| |\ V / __/ | |___| | | | __/ (__| <\__ \ |
304 # | /_/ \_\___|\__|_| \_/ \___| \____|_| |_|\___|\___|_|\_\___/ |
305 # | |
306 # +----------------------------------------------------------------------+
307 # | Active check specific functions |
308 # '----------------------------------------------------------------------'
311 def active_check_arguments(hostname, description, args):
312 if not isinstance(args, (str, unicode, list)):
313 raise MKGeneralException(
314 "The check argument function needs to return either a list of arguments or a "
315 "string of the concatenated arguments (Host: %s, Service: %s)." % (hostname,
316 description))
318 return config.prepare_check_command(args, hostname, description)
322 # .--HostAttributes------------------------------------------------------.
323 # | _ _ _ _ _ _ _ _ _ |
324 # || | | | ___ ___| |_ / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___ |
325 # || |_| |/ _ \/ __| __| / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __| |
326 # || _ | (_) \__ \ |_ / ___ \ |_| |_| | | | |_) | |_| | || __/\__ \ |
327 # ||_| |_|\___/|___/\__/_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/ |
328 # | |
329 # +----------------------------------------------------------------------+
330 # | Managing of host attributes |
331 # '----------------------------------------------------------------------'
334 def get_host_attributes(hostname, tags):
335 attrs = _extra_host_attributes(hostname)
337 attrs["_TAGS"] = " ".join(tags)
339 if "alias" not in attrs:
340 attrs["alias"] = config.alias_of(hostname, hostname)
342 # Now lookup configured IP addresses
343 if config.is_ipv4_host(hostname):
344 attrs["_ADDRESS_4"] = ip_address_of(hostname, 4)
345 if attrs["_ADDRESS_4"] is None:
346 attrs["_ADDRESS_4"] = ""
347 else:
348 attrs["_ADDRESS_4"] = ""
350 if config.is_ipv6_host(hostname):
351 attrs["_ADDRESS_6"] = ip_address_of(hostname, 6)
352 if attrs["_ADDRESS_6"] is None:
353 attrs["_ADDRESS_6"] = ""
354 else:
355 attrs["_ADDRESS_6"] = ""
357 ipv6_primary = config.is_ipv6_primary(hostname)
358 if ipv6_primary:
359 attrs["address"] = attrs["_ADDRESS_6"]
360 attrs["_ADDRESS_FAMILY"] = "6"
361 else:
362 attrs["address"] = attrs["_ADDRESS_4"]
363 attrs["_ADDRESS_FAMILY"] = "4"
365 add_ipv4addrs, add_ipv6addrs = config.get_additional_ipaddresses_of(hostname)
366 if add_ipv4addrs:
367 attrs["_ADDRESSES_4"] = " ".join(add_ipv4addrs)
368 for nr, ipv4_address in enumerate(add_ipv4addrs):
369 key = "_ADDRESS_4_%s" % (nr + 1)
370 attrs[key] = ipv4_address
372 if add_ipv6addrs:
373 attrs["_ADDRESSES_6"] = " ".join(add_ipv6addrs)
374 for nr, ipv6_address in enumerate(add_ipv6addrs):
375 key = "_ADDRESS_6_%s" % (nr + 1)
376 attrs[key] = ipv6_address
378 # Add the optional WATO folder path
379 path = config.host_paths.get(hostname)
380 if path:
381 attrs["_FILENAME"] = path
383 # Add custom user icons and actions
384 actions = icons_and_actions_of("host", hostname)
385 if actions:
386 attrs["_ACTIONS"] = ",".join(actions)
388 if cmk.is_managed_edition():
389 attrs["_CUSTOMER"] = config.current_customer # pylint: disable=no-member
391 return attrs
394 def _extra_host_attributes(hostname):
395 attrs = {}
396 for key, conflist in config.extra_host_conf.items():
397 values = config.host_extra_conf(hostname, conflist)
398 if values:
399 if key[0] == "_":
400 key = key.upper()
402 if values[0] is not None:
403 attrs[key] = values[0]
404 return attrs
407 def get_cluster_attributes(hostname, nodes):
408 sorted_nodes = sorted(nodes)
410 attrs = {
411 "_NODENAMES": " ".join(sorted_nodes),
413 node_ips_4 = []
414 if config.is_ipv4_host(hostname):
415 for h in sorted_nodes:
416 addr = ip_address_of(h, 4)
417 if addr is not None:
418 node_ips_4.append(addr)
419 else:
420 node_ips_4.append(fallback_ip_for(hostname, 4))
422 node_ips_6 = []
423 if config.is_ipv6_host(hostname):
424 for h in sorted_nodes:
425 addr = ip_address_of(h, 6)
426 if addr is not None:
427 node_ips_6.append(addr)
428 else:
429 node_ips_6.append(fallback_ip_for(hostname, 6))
431 if config.is_ipv6_primary(hostname):
432 node_ips = node_ips_6
433 else:
434 node_ips = node_ips_4
436 for suffix, val in [("", node_ips), ("_4", node_ips_4), ("_6", node_ips_6)]:
437 attrs["_NODEIPS%s" % suffix] = " ".join(val)
439 return attrs
442 def get_cluster_nodes_for_config(hostname):
443 _verify_cluster_address_family(hostname)
445 nodes = config.nodes_of(hostname)[:]
446 for node in nodes:
447 if node not in config.all_active_realhosts():
448 warning("Node '%s' of cluster '%s' is not a monitored host in this site." % (node,
449 hostname))
450 nodes.remove(node)
451 return nodes
454 def _verify_cluster_address_family(hostname):
455 cluster_host_family = "IPv6" if config.is_ipv6_primary(hostname) else "IPv4"
457 address_families = [
458 "%s: %s" % (hostname, cluster_host_family),
461 address_family = cluster_host_family
462 mixed = False
463 for nodename in config.nodes_of(hostname):
464 family = "IPv6" if config.is_ipv6_primary(nodename) else "IPv4"
465 address_families.append("%s: %s" % (nodename, family))
466 if address_family is None:
467 address_family = family
468 elif address_family != family:
469 mixed = True
471 if mixed:
472 warning("Cluster '%s' has different primary address families: %s" %
473 (hostname, ", ".join(address_families)))
476 def ip_address_of(hostname, family=None):
477 try:
478 return ip_lookup.lookup_ip_address(hostname, family)
479 except Exception as e:
480 if config.is_cluster(hostname):
481 return ""
482 else:
483 _failed_ip_lookups.append(hostname)
484 if not _ignore_ip_lookup_failures:
485 warning("Cannot lookup IP address of '%s' (%s). "
486 "The host will not be monitored correctly." % (hostname, e))
487 return fallback_ip_for(hostname, family)
490 def ignore_ip_lookup_failures():
491 global _ignore_ip_lookup_failures
492 _ignore_ip_lookup_failures = True
495 def failed_ip_lookups():
496 return _failed_ip_lookups
499 def fallback_ip_for(hostname, family=None):
500 if family is None:
501 family = 6 if config.is_ipv6_primary(hostname) else 4
503 if family == 4:
504 return "0.0.0.0"
506 return "::"
509 def get_host_macros_from_attributes(hostname, attrs):
510 macros = {
511 "$HOSTNAME$": hostname,
512 "$HOSTADDRESS$": attrs['address'],
513 "$HOSTALIAS$": attrs['alias'],
516 # Add custom macros
517 for macro_name, value in attrs.items():
518 if macro_name[0] == '_':
519 macros["$HOST" + macro_name + "$"] = value
520 # Be compatible to nagios making $_HOST<VARNAME>$ out of the config _<VARNAME> configs
521 macros["$_HOST" + macro_name[1:] + "$"] = value
523 return macros
526 def replace_macros(s, macros):
527 for key, value in macros.items():
528 if isinstance(value, (numbers.Integral, float)):
529 value = str(value) # e.g. in _EC_SL (service level)
531 # TODO: Clean this up
532 try:
533 s = s.replace(key, value)
534 except: # Might have failed due to binary UTF-8 encoding in value
535 try:
536 s = s.replace(key, value.decode("utf-8"))
537 except:
538 # If this does not help, do not replace
539 if cmk.utils.debug.enabled():
540 raise
542 return s