GUI CSS: Decoupled styles for bi from classic theme (CMK-1171)
[check_mk.git] / cmk_base / core_config.py
blob294be8512df34f14e38ca8ae6133172ff43ee980
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 import itertools
32 from typing import Any, List # pylint: disable=unused-import
34 import cmk.utils.paths
35 import cmk.utils.tty as tty
36 import cmk.utils.password_store
37 from cmk.utils.exceptions import MKGeneralException
39 import cmk_base.console as console
40 import cmk_base.config as config
41 import cmk_base.ip_lookup as ip_lookup
44 class MonitoringCore(object):
45 __metaclass__ = abc.ABCMeta
47 @abc.abstractmethod
48 def create_config(self):
49 pass
51 @abc.abstractmethod
52 def precompile(self):
53 pass
56 _ignore_ip_lookup_failures = False
57 _failed_ip_lookups = []
60 # .--Warnings------------------------------------------------------------.
61 # | __ __ _ |
62 # | \ \ / /_ _ _ __ _ __ (_)_ __ __ _ ___ |
63 # | \ \ /\ / / _` | '__| '_ \| | '_ \ / _` / __| |
64 # | \ V V / (_| | | | | | | | | | | (_| \__ \ |
65 # | \_/\_/ \__,_|_| |_| |_|_|_| |_|\__, |___/ |
66 # | |___/ |
67 # +----------------------------------------------------------------------+
68 # | Managing of warning messages occuring during configuration building |
69 # '----------------------------------------------------------------------'
71 g_configuration_warnings = [] # type: List[Any]
74 def initialize_warnings():
75 global g_configuration_warnings
76 g_configuration_warnings = []
79 def warning(text):
80 g_configuration_warnings.append(text)
81 console.warning("\n%s", text, stream=sys.stdout)
84 def get_configuration_warnings():
85 num_warnings = len(g_configuration_warnings)
87 if num_warnings > 10:
88 warnings = g_configuration_warnings[:10] + \
89 [ "%d further warnings have been omitted" % (num_warnings - 10) ]
90 else:
91 warnings = g_configuration_warnings
93 return warnings
96 # TODO: Cleanup the hostcheck_commands_to_define, custom_commands_to_define thing
97 def host_check_command(hostname,
98 ip,
99 is_clust,
100 hostcheck_commands_to_define=None,
101 custom_commands_to_define=None):
102 # Check dedicated host check command
103 values = config.host_extra_conf(hostname, config.host_check_commands)
104 if values:
105 value = values[0]
106 elif config.is_no_ip_host(hostname):
107 value = "ok"
108 elif config.monitoring_core == "cmc":
109 value = "smart"
110 else:
111 value = "ping"
113 if config.monitoring_core != "cmc" and value == "smart":
114 value = "ping" # avoid problems when switching back to nagios core
116 if value == "smart" and not is_clust:
117 return "check-mk-host-smart"
119 elif value in ["ping", "smart"]: # Cluster host
120 ping_args = check_icmp_arguments_of(hostname)
122 if is_clust and ip: # Do check cluster IP address if one is there
123 return "check-mk-host-ping!%s" % ping_args
124 elif ping_args and is_clust: # use check_icmp in cluster mode
125 return "check-mk-host-ping-cluster!%s" % ping_args
126 elif ping_args: # use special arguments
127 return "check-mk-host-ping!%s" % ping_args
129 return None
131 elif value == "ok":
132 return "check-mk-host-ok"
134 elif value == "agent" or value[0] == "service":
135 service = "Check_MK" if value == "agent" else value[1]
137 if config.monitoring_core == "cmc":
138 return "check-mk-host-service!" + service
140 command = "check-mk-host-custom-%d" % (len(hostcheck_commands_to_define) + 1)
141 hostcheck_commands_to_define.append(
142 (command, 'echo "$SERVICEOUTPUT:%s:%s$" && exit $SERVICESTATEID:%s:%s$' %
143 (hostname, service.replace('$HOSTNAME$', hostname), hostname,
144 service.replace('$HOSTNAME$', hostname))))
145 return command
147 elif value[0] == "tcp":
148 return "check-mk-host-tcp!" + str(value[1])
150 elif value[0] == "custom":
151 try:
152 custom_commands_to_define.add("check-mk-custom")
153 except:
154 pass # not needed and not available with CMC
155 return "check-mk-custom!" + autodetect_plugin(value[1])
157 raise MKGeneralException(
158 "Invalid value %r for host_check_command of host %s." % (value, hostname))
161 def autodetect_plugin(command_line):
162 plugin_name = command_line.split()[0]
163 if command_line[0] not in ['$', '/']:
164 try:
165 for directory in ["/local", ""]:
166 path = cmk.utils.paths.omd_root + directory + "/lib/nagios/plugins/"
167 if os.path.exists(path + plugin_name):
168 command_line = path + command_line
169 break
170 except:
171 pass
172 return command_line
175 def icons_and_actions_of(what, hostname, svcdesc=None, checkname=None, params=None):
176 if what == 'host':
177 return list(set(config.host_extra_conf(hostname, config.host_icons_and_actions)))
178 else:
179 actions = set(
180 config.service_extra_conf(hostname, svcdesc, config.service_icons_and_actions))
182 # Some WATO rules might register icons on their own
183 if checkname:
184 checkgroup = config.check_info[checkname]["group"]
185 if checkgroup in ['ps', 'services'] and isinstance(params, dict):
186 icon = params.get('icon')
187 if icon:
188 actions.add(icon)
190 return list(actions)
193 def check_icmp_arguments_of(hostname, add_defaults=True, family=None):
194 values = config.host_extra_conf(hostname, config.ping_levels)
195 levels = {}
196 for value in values[::-1]: # make first rules have precedence
197 levels.update(value)
198 if not add_defaults and not levels:
199 return ""
201 if family is None:
202 family = 6 if config.is_ipv6_primary(hostname) else 4
204 args = []
206 if family == 6:
207 args.append("-6")
209 rta = 200, 500
210 loss = 80, 100
211 for key, value in levels.items():
212 if key == "timeout":
213 args.append("-t %d" % value)
214 elif key == "packets":
215 args.append("-n %d" % value)
216 elif key == "rta":
217 rta = value
218 elif key == "loss":
219 loss = value
220 args.append("-w %.2f,%.2f%%" % (rta[0], loss[0]))
221 args.append("-c %.2f,%.2f%%" % (rta[1], loss[1]))
222 return " ".join(args)
226 # .--Core Config---------------------------------------------------------.
227 # | ____ ____ __ _ |
228 # | / ___|___ _ __ ___ / ___|___ _ __ / _(_) __ _ |
229 # | | | / _ \| '__/ _ \ | | / _ \| '_ \| |_| |/ _` | |
230 # | | |__| (_) | | | __/ | |__| (_) | | | | _| | (_| | |
231 # | \____\___/|_| \___| \____\___/|_| |_|_| |_|\__, | |
232 # | |___/ |
233 # +----------------------------------------------------------------------+
234 # | Code for managing the core configuration creation. |
235 # '----------------------------------------------------------------------'
238 # TODO: Move to modes?
239 def do_create_config(core, with_agents):
240 console.output("Generating configuration for core (type %s)..." % config.monitoring_core)
241 create_core_config(core)
242 console.output(tty.ok + "\n")
244 if with_agents:
245 try:
246 import cmk_base.cee.agent_bakery
247 cmk_base.cee.agent_bakery.bake_on_restart()
248 except ImportError:
249 pass
252 def create_core_config(core):
253 initialize_warnings()
255 _verify_non_duplicate_hosts()
256 _verify_non_deprecated_checkgroups()
257 core.create_config()
258 cmk.utils.password_store.save(config.stored_passwords)
260 return get_configuration_warnings()
263 # Verify that the user has no deprecated check groups configured.
264 def _verify_non_deprecated_checkgroups():
265 groups = config.checks_by_checkgroup()
267 for checkgroup in config.checkgroup_parameters:
268 if checkgroup not in groups:
269 warning(
270 "Found configured rules of deprecated check group \"%s\". These rules are not used "
271 "by any check. Maybe this check group has been renamed during an update, "
272 "in this case you will have to migrate your configuration to the new ruleset manually. "
273 "Please check out the release notes of the involved versions. "
274 "You may use the page \"Deprecated rules\" in WATO to view your rules and move them to "
275 "the new rulesets." % checkgroup)
278 def _verify_non_duplicate_hosts():
279 duplicates = config.duplicate_hosts()
280 if duplicates:
281 warning("The following host names have duplicates: %s. "
282 "This might lead to invalid/incomplete monitoring for these hosts." %
283 ", ".join(duplicates))
286 def do_update(core, with_precompile):
287 try:
288 do_create_config(core, with_agents=with_precompile)
289 if with_precompile:
290 core.precompile()
292 except Exception as e:
293 console.error("Configuration Error: %s\n" % e)
294 if cmk.utils.debug.enabled():
295 raise
296 sys.exit(1)
300 # .--Active Checks-------------------------------------------------------.
301 # | _ _ _ ____ _ _ |
302 # | / \ ___| |_(_)_ _____ / ___| |__ ___ ___| | _____ |
303 # | / _ \ / __| __| \ \ / / _ \ | | | '_ \ / _ \/ __| |/ / __| |
304 # | / ___ \ (__| |_| |\ V / __/ | |___| | | | __/ (__| <\__ \ |
305 # | /_/ \_\___|\__|_| \_/ \___| \____|_| |_|\___|\___|_|\_\___/ |
306 # | |
307 # +----------------------------------------------------------------------+
308 # | Active check specific functions |
309 # '----------------------------------------------------------------------'
312 def active_check_arguments(hostname, description, args):
313 if not isinstance(args, (str, unicode, list)):
314 raise MKGeneralException(
315 "The check argument function needs to return either a list of arguments or a "
316 "string of the concatenated arguments (Host: %s, Service: %s)." % (hostname,
317 description))
319 return config.prepare_check_command(args, hostname, description)
323 # .--ServiceAttrs.-------------------------------------------------------.
324 # | ____ _ _ _ _ |
325 # | / ___| ___ _ ____ _(_) ___ ___ / \ | |_| |_ _ __ ___ |
326 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \ / _ \| __| __| '__/ __| |
327 # | ___) | __/ | \ V /| | (_| __// ___ \ |_| |_| | \__ \_ |
328 # | |____/ \___|_| \_/ |_|\___\___/_/ \_\__|\__|_| |___(_) |
329 # | |
330 # +----------------------------------------------------------------------+
331 # | Management of service attributes |
332 # '----------------------------------------------------------------------'
335 def get_service_attributes(hostname, description, config_cache, checkname=None, params=None):
336 attrs = _extra_service_attributes(hostname, description, config_cache, checkname, params)
337 attrs.update(_get_tag_attributes(config_cache.tags_of_service(hostname, description)))
338 return attrs
341 def _extra_service_attributes(hostname, description, config_cache, checkname, params):
342 attrs = {}
344 # Add service custom_variables. Name conflicts are prevented by the GUI, but just
345 # to be sure, add them first. The other definitions will override the custom attributes.
346 for varname, value in custom_service_attributes_of(hostname, description).iteritems():
347 attrs["_%s" % varname.upper()] = value
349 for key, conflist in config.extra_service_conf.items():
350 values = config_cache.service_extra_conf(hostname, description, conflist)
351 if values:
352 if key[0] == "_":
353 key = key.upper()
354 attrs[key] = values[0]
356 # Add explicit custom_variables
357 for varname, value in config.get_explicit_service_custom_variables(hostname,
358 description).iteritems():
359 attrs["_%s" % varname.upper()] = value
361 # Add custom user icons and actions
362 actions = icons_and_actions_of("service", hostname, description, checkname, params)
363 if actions:
364 attrs["_ACTIONS"] = ','.join(actions)
365 return attrs
368 # TODO: Hand over config_cache and use it instead of config.service_extra_conf
369 def custom_service_attributes_of(hostname, service_description):
370 return dict(
371 itertools.chain(*config.service_extra_conf(hostname, service_description,
372 config.custom_service_attributes)))
376 # .--HostAttributes------------------------------------------------------.
377 # | _ _ _ _ _ _ _ _ _ |
378 # || | | | ___ ___| |_ / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___ |
379 # || |_| |/ _ \/ __| __| / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __| |
380 # || _ | (_) \__ \ |_ / ___ \ |_| |_| | | | |_) | |_| | || __/\__ \ |
381 # ||_| |_|\___/|___/\__/_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/ |
382 # | |
383 # +----------------------------------------------------------------------+
384 # | Managing of host attributes |
385 # '----------------------------------------------------------------------'
388 def get_host_attributes(hostname, config_cache):
389 attrs = _extra_host_attributes(hostname)
391 # Pre 1.6 legacy attribute. We have changed our whole code to use the
392 # livestatus column "tags" which is populated by all attributes starting with
393 # "__TAG_" instead. We may deprecate this is one day.
394 attrs["_TAGS"] = " ".join(sorted(config_cache.tag_list_of_host(hostname)))
396 attrs.update(_get_tag_attributes(config_cache.tags_of_host(hostname)))
398 if "alias" not in attrs:
399 attrs["alias"] = config.alias_of(hostname, hostname)
401 # Now lookup configured IP addresses
402 if config.is_ipv4_host(hostname):
403 attrs["_ADDRESS_4"] = ip_address_of(hostname, 4)
404 if attrs["_ADDRESS_4"] is None:
405 attrs["_ADDRESS_4"] = ""
406 else:
407 attrs["_ADDRESS_4"] = ""
409 if config.is_ipv6_host(hostname):
410 attrs["_ADDRESS_6"] = ip_address_of(hostname, 6)
411 if attrs["_ADDRESS_6"] is None:
412 attrs["_ADDRESS_6"] = ""
413 else:
414 attrs["_ADDRESS_6"] = ""
416 ipv6_primary = config.is_ipv6_primary(hostname)
417 if ipv6_primary:
418 attrs["address"] = attrs["_ADDRESS_6"]
419 attrs["_ADDRESS_FAMILY"] = "6"
420 else:
421 attrs["address"] = attrs["_ADDRESS_4"]
422 attrs["_ADDRESS_FAMILY"] = "4"
424 add_ipv4addrs, add_ipv6addrs = config.get_additional_ipaddresses_of(hostname)
425 if add_ipv4addrs:
426 attrs["_ADDRESSES_4"] = " ".join(add_ipv4addrs)
427 for nr, ipv4_address in enumerate(add_ipv4addrs):
428 key = "_ADDRESS_4_%s" % (nr + 1)
429 attrs[key] = ipv4_address
431 if add_ipv6addrs:
432 attrs["_ADDRESSES_6"] = " ".join(add_ipv6addrs)
433 for nr, ipv6_address in enumerate(add_ipv6addrs):
434 key = "_ADDRESS_6_%s" % (nr + 1)
435 attrs[key] = ipv6_address
437 # Add the optional WATO folder path
438 path = config.host_paths.get(hostname)
439 if path:
440 attrs["_FILENAME"] = path
442 # Add custom user icons and actions
443 actions = icons_and_actions_of("host", hostname)
444 if actions:
445 attrs["_ACTIONS"] = ",".join(actions)
447 if cmk.is_managed_edition():
448 attrs["_CUSTOMER"] = config.current_customer # pylint: disable=no-member
450 return attrs
453 def _get_tag_attributes(tags):
454 return {u"__TAG_%s" % k: unicode(v) for k, v in tags.iteritems()}
457 def _extra_host_attributes(hostname):
458 attrs = {}
459 for key, conflist in config.extra_host_conf.items():
460 values = config.host_extra_conf(hostname, conflist)
461 if values:
462 if key[0] == "_":
463 key = key.upper()
465 if values[0] is not None:
466 attrs[key] = values[0]
467 return attrs
470 def get_cluster_attributes(hostname, nodes):
471 sorted_nodes = sorted(nodes)
473 attrs = {
474 "_NODENAMES": " ".join(sorted_nodes),
476 node_ips_4 = []
477 if config.is_ipv4_host(hostname):
478 for h in sorted_nodes:
479 addr = ip_address_of(h, 4)
480 if addr is not None:
481 node_ips_4.append(addr)
482 else:
483 node_ips_4.append(fallback_ip_for(hostname, 4))
485 node_ips_6 = []
486 if config.is_ipv6_host(hostname):
487 for h in sorted_nodes:
488 addr = ip_address_of(h, 6)
489 if addr is not None:
490 node_ips_6.append(addr)
491 else:
492 node_ips_6.append(fallback_ip_for(hostname, 6))
494 if config.is_ipv6_primary(hostname):
495 node_ips = node_ips_6
496 else:
497 node_ips = node_ips_4
499 for suffix, val in [("", node_ips), ("_4", node_ips_4), ("_6", node_ips_6)]:
500 attrs["_NODEIPS%s" % suffix] = " ".join(val)
502 return attrs
505 def get_cluster_nodes_for_config(hostname):
506 _verify_cluster_address_family(hostname)
508 nodes = config.nodes_of(hostname)[:]
509 for node in nodes:
510 if node not in config.all_active_realhosts():
511 warning("Node '%s' of cluster '%s' is not a monitored host in this site." % (node,
512 hostname))
513 nodes.remove(node)
514 return nodes
517 def _verify_cluster_address_family(hostname):
518 cluster_host_family = "IPv6" if config.is_ipv6_primary(hostname) else "IPv4"
520 address_families = [
521 "%s: %s" % (hostname, cluster_host_family),
524 address_family = cluster_host_family
525 mixed = False
526 for nodename in config.nodes_of(hostname):
527 family = "IPv6" if config.is_ipv6_primary(nodename) else "IPv4"
528 address_families.append("%s: %s" % (nodename, family))
529 if address_family is None:
530 address_family = family
531 elif address_family != family:
532 mixed = True
534 if mixed:
535 warning("Cluster '%s' has different primary address families: %s" %
536 (hostname, ", ".join(address_families)))
539 def ip_address_of(hostname, family=None):
540 try:
541 return ip_lookup.lookup_ip_address(hostname, family)
542 except Exception as e:
543 if config.is_cluster(hostname):
544 return ""
545 else:
546 _failed_ip_lookups.append(hostname)
547 if not _ignore_ip_lookup_failures:
548 warning("Cannot lookup IP address of '%s' (%s). "
549 "The host will not be monitored correctly." % (hostname, e))
550 return fallback_ip_for(hostname, family)
553 def ignore_ip_lookup_failures():
554 global _ignore_ip_lookup_failures
555 _ignore_ip_lookup_failures = True
558 def failed_ip_lookups():
559 return _failed_ip_lookups
562 def fallback_ip_for(hostname, family=None):
563 if family is None:
564 family = 6 if config.is_ipv6_primary(hostname) else 4
566 if family == 4:
567 return "0.0.0.0"
569 return "::"
572 def get_host_macros_from_attributes(hostname, attrs):
573 macros = {
574 "$HOSTNAME$": hostname,
575 "$HOSTADDRESS$": attrs['address'],
576 "$HOSTALIAS$": attrs['alias'],
579 # Add custom macros
580 for macro_name, value in attrs.items():
581 if macro_name[0] == '_':
582 macros["$HOST" + macro_name + "$"] = value
583 # Be compatible to nagios making $_HOST<VARNAME>$ out of the config _<VARNAME> configs
584 macros["$_HOST" + macro_name[1:] + "$"] = value
586 return macros
589 def replace_macros(s, macros):
590 for key, value in macros.items():
591 if isinstance(value, (numbers.Integral, float)):
592 value = str(value) # e.g. in _EC_SL (service level)
594 # TODO: Clean this up
595 try:
596 s = s.replace(key, value)
597 except: # Might have failed due to binary UTF-8 encoding in value
598 try:
599 s = s.replace(key, value.decode("utf-8"))
600 except:
601 # If this does not help, do not replace
602 if cmk.utils.debug.enabled():
603 raise
605 return s