Add specific visualization for labels depending on their source
[check_mk.git] / cmk_base / config.py
blob9fdc120d607da1777f2ab2e221f4c93f02165cb9
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 from collections import OrderedDict
28 import ast
29 import copy
30 import inspect
31 import marshal
32 import numbers
33 import os
34 import py_compile
35 import struct
36 import sys
37 from typing import Any, Callable, Dict, List, Tuple, Union, Optional # pylint: disable=unused-import
39 import six
41 import cmk.utils.debug
42 import cmk.utils.paths
43 from cmk.utils.regex import regex, is_regex
44 import cmk.utils.translations
45 import cmk.utils.store as store
46 import cmk.utils
47 from cmk.utils.exceptions import MKGeneralException, MKTerminate
49 import cmk_base
50 import cmk_base.console as console
51 import cmk_base.default_config as default_config
52 import cmk_base.check_utils
53 import cmk_base.utils
54 import cmk_base.check_api_utils as check_api_utils
55 import cmk_base.cleanup
56 import cmk_base.piggyback as piggyback
57 from cmk_base.discovered_host_labels import DiscoveredHostLabelsStore
59 # TODO: Prefix helper functions with "_".
61 # This is mainly needed for pylint to detect all available
62 # configuration options during static analysis. The defaults
63 # are loaded later with load_default_config() again.
64 from cmk_base.default_config import * # pylint: disable=wildcard-import,unused-wildcard-import
66 service_service_levels = [] # type: ignore
67 host_service_levels = [] # type: ignore
70 class TimespecificParamList(list):
71 pass
74 def get_variable_names():
75 """Provides the list of all known configuration variables."""
76 return [k for k in default_config.__dict__ if k[0] != "_"]
79 def get_default_config():
80 """Provides a dictionary containing the Check_MK default configuration"""
81 cfg = {}
82 for key in get_variable_names():
83 value = getattr(default_config, key)
85 if isinstance(value, (dict, list)):
86 value = copy.deepcopy(value)
88 cfg[key] = value
89 return cfg
92 def load_default_config():
93 globals().update(get_default_config())
96 def register(name, default_value):
97 """Register a new configuration variable within Check_MK base."""
98 setattr(default_config, name, default_value)
101 def _add_check_variables_to_default_config():
102 """Add configuration variables registered by checks to config module"""
103 default_config.__dict__.update(get_check_variable_defaults())
106 def _clear_check_variables_from_default_config(variable_names):
107 """Remove previously registered check variables from the config module"""
108 for varname in variable_names:
109 try:
110 delattr(default_config, varname)
111 except AttributeError:
112 pass
115 # Load user configured values of check related configuration variables
116 # into the check module to make it available during checking.
118 # In the same step we remove the check related configuration settings from the
119 # config module because they are not needed there anymore.
121 # And also remove it from the default config (in case it was present)
122 def set_check_variables_for_checks():
123 global_dict = globals()
124 cvn = check_variable_names()
126 check_variables = {}
127 for varname in cvn:
128 check_variables[varname] = global_dict.pop(varname)
130 set_check_variables(check_variables)
131 _clear_check_variables_from_default_config(cvn)
135 # .--Read Config---------------------------------------------------------.
136 # | ____ _ ____ __ _ |
137 # | | _ \ ___ __ _ __| | / ___|___ _ __ / _(_) __ _ |
138 # | | |_) / _ \/ _` |/ _` | | | / _ \| '_ \| |_| |/ _` | |
139 # | | _ < __/ (_| | (_| | | |__| (_) | | | | _| | (_| | |
140 # | |_| \_\___|\__,_|\__,_| \____\___/|_| |_|_| |_|\__, | |
141 # | |___/ |
142 # +----------------------------------------------------------------------+
143 # | Code for reading the configuration files. |
144 # '----------------------------------------------------------------------'
147 def load(with_conf_d=True, validate_hosts=True, exclude_parents_mk=False):
148 _initialize_config()
150 vars_before_config = all_nonfunction_vars()
152 _load_config(with_conf_d, exclude_parents_mk)
153 _transform_mgmt_config_vars_from_140_to_150()
154 _initialize_derived_config_variables()
156 _perform_post_config_loading_actions()
158 if validate_hosts:
159 _verify_non_duplicate_hosts()
161 # Such validation only makes sense when all checks have been loaded
162 if all_checks_loaded():
163 verify_non_invalid_variables(vars_before_config)
164 _verify_no_deprecated_check_rulesets()
166 verify_snmp_communities_type()
169 def load_packed_config():
170 """Load the configuration for the CMK helpers of CMC
172 These files are written by PackedConfig().
174 Should have a result similar to the load() above. With the exception that the
175 check helpers would only need check related config variables.
177 The validations which are performed during load() also don't need to be performed.
179 PackedConfig().load()
182 def _initialize_config():
183 _add_check_variables_to_default_config()
184 load_default_config()
187 def _perform_post_config_loading_actions():
188 """These tasks must be performed after loading the Check_MK base configuration"""
189 # First cleanup things (needed for e.g. reloading the config)
190 cmk_base.config_cache.clear_all()
192 get_config_cache().initialize()
194 # In case the checks are not loaded yet it seems the current mode
195 # is not working with the checks. In this case also don't load the
196 # static checks into the configuration.
197 if any_check_loaded():
198 add_wato_static_checks_to_checks()
199 initialize_check_caches()
200 set_check_variables_for_checks()
203 def _load_config(with_conf_d, exclude_parents_mk):
204 helper_vars = {
205 "FOLDER_PATH": None,
208 global_dict = globals()
209 global_dict.update(helper_vars)
211 for _f in _get_config_file_paths(with_conf_d):
212 # During parent scan mode we must not read in old version of parents.mk!
213 if exclude_parents_mk and _f.endswith("/parents.mk"):
214 continue
216 try:
217 _hosts_before = set(all_hosts)
218 _clusters_before = set(clusters.keys())
220 # Make the config path available as a global variable to
221 # be used within the configuration file
222 if _f.startswith(cmk.utils.paths.check_mk_config_dir + "/"):
223 _file_path = _f[len(cmk.utils.paths.check_mk_config_dir) + 1:]
224 global_dict.update({
225 "FOLDER_PATH": os.path.dirname(_file_path),
227 else:
228 global_dict.update({
229 "FOLDER_PATH": None,
232 execfile(_f, global_dict, global_dict)
234 _new_hosts = set(all_hosts).difference(_hosts_before)
235 _new_clusters = set(clusters.keys()).difference(_clusters_before)
237 set_folder_paths(_new_hosts.union(_new_clusters), _f)
238 except Exception as e:
239 if cmk.utils.debug.enabled():
240 raise
241 elif sys.stderr.isatty():
242 console.error("Cannot read in configuration file %s: %s\n", _f, e)
243 sys.exit(1)
245 # Cleanup global helper vars
246 for helper_var in helper_vars:
247 del global_dict[helper_var]
250 def _transform_mgmt_config_vars_from_140_to_150():
251 #FIXME We have to transform some configuration variables from host attributes
252 # to cmk_base configuration variables because during the migration step from
253 # 1.4.0 to 1.5.0 some config variables are not known in cmk_base. These variables
254 # are 'management_protocol' and 'management_snmp_community'.
255 # Clean this up one day!
256 for hostname, attributes in host_attributes.iteritems():
257 for name, var in [
258 ('management_protocol', management_protocol),
259 ('management_snmp_community', management_snmp_credentials),
261 if attributes.get(name):
262 var.setdefault(hostname, attributes[name])
265 # Create list of all files to be included during configuration loading
266 def _get_config_file_paths(with_conf_d):
267 if with_conf_d:
268 list_of_files = sorted(
269 reduce(lambda a, b: a + b,
270 [["%s/%s" % (d, f)
271 for f in fs
272 if f.endswith(".mk")]
273 for d, _unused_sb, fs in os.walk(cmk.utils.paths.check_mk_config_dir)], []),
274 cmp=cmk.utils.cmp_config_paths)
275 list_of_files = [cmk.utils.paths.main_config_file] + list_of_files
276 else:
277 list_of_files = [cmk.utils.paths.main_config_file]
279 for path in [cmk.utils.paths.final_config_file, cmk.utils.paths.local_config_file]:
280 if os.path.exists(path):
281 list_of_files.append(path)
283 return list_of_files
286 def _initialize_derived_config_variables():
287 global service_service_levels, host_service_levels
288 service_service_levels = extra_service_conf.get("_ec_sl", [])
289 host_service_levels = extra_host_conf.get("_ec_sl", [])
292 def get_derived_config_variable_names():
293 """These variables are computed from other configuration variables and not configured directly.
295 The origin variable (extra_service_conf) should not be exported to the helper config. Only
296 the service levels are needed."""
297 return set(["service_service_levels", "host_service_levels"])
300 def _verify_non_duplicate_hosts():
301 duplicates = duplicate_hosts()
302 if duplicates:
303 # TODO: Raise an exception
304 console.error("Error in configuration: duplicate hosts: %s\n", ", ".join(duplicates))
305 sys.exit(3)
308 # Add WATO-configured explicit checks to (possibly empty) checks
309 # statically defined in checks.
310 def add_wato_static_checks_to_checks():
311 global checks
313 static = []
314 for entries in static_checks.values():
315 for entry in entries:
316 entry, rule_options = get_rule_options(entry)
317 if rule_options.get("disabled"):
318 continue
320 # Parameters are optional
321 if len(entry[0]) == 2:
322 checktype, item = entry[0]
323 params = None
324 else:
325 checktype, item, params = entry[0]
326 if len(entry) == 3:
327 taglist, hostlist = entry[1:3]
328 else:
329 hostlist = entry[1]
330 taglist = []
332 # Do not process manual checks that are related to not existing or have not
333 # loaded check files
334 try:
335 check_plugin_info = check_info[checktype]
336 except KeyError:
337 continue
339 # Make sure, that for dictionary based checks
340 # at least those keys defined in the factory
341 # settings are present in the parameters
342 if isinstance(params, dict):
343 def_levels_varname = check_plugin_info.get("default_levels_variable")
344 if def_levels_varname:
345 for key, value in factory_settings.get(def_levels_varname, {}).items():
346 if key not in params:
347 params[key] = value
349 static.append((taglist, hostlist, checktype, item, params))
351 # Note: We need to reverse the order of the static_checks. This is because
352 # users assume that earlier rules have precedence over later ones. For static
353 # checks that is important if there are two rules for a host with the same
354 # combination of check type and item. When the variable 'checks' is evaluated,
355 # *later* rules have precedence. This is not consistent with the rest, but a
356 # result of this "historic implementation".
357 static.reverse()
359 # Now prepend to checks. That makes that checks variable have precedence
360 # over WATO.
361 checks = static + checks
364 def initialize_check_caches():
365 single_host_checks = cmk_base.config_cache.get_dict("single_host_checks")
366 multi_host_checks = cmk_base.config_cache.get_list("multi_host_checks")
368 for entry in checks:
369 if len(entry) == 4 and isinstance(entry[0], str):
370 single_host_checks.setdefault(entry[0], []).append(entry)
371 else:
372 multi_host_checks.append(entry)
375 def set_folder_paths(new_hosts, filename):
376 if not filename.startswith(cmk.utils.paths.check_mk_config_dir):
377 return
379 path = filename[len(cmk.utils.paths.check_mk_config_dir):]
381 for hostname in strip_tags(new_hosts):
382 host_paths[hostname] = path
385 def verify_non_invalid_variables(vars_before_config):
386 # Check for invalid configuration variables
387 vars_after_config = all_nonfunction_vars()
388 ignored_variables = set([
389 'vars_before_config', 'parts', 'seen_hostnames', 'taggedhost', 'hostname',
390 'service_service_levels', 'host_service_levels'
393 found_invalid = 0
394 for name in vars_after_config:
395 if name not in ignored_variables and name not in vars_before_config:
396 console.error("Invalid configuration variable '%s'\n", name)
397 found_invalid += 1
399 if found_invalid:
400 console.error("--> Found %d invalid variables\n" % found_invalid)
401 console.error("If you use own helper variables, please prefix them with _.\n")
402 sys.exit(1)
405 def verify_snmp_communities_type():
406 # Special handling for certain deprecated variables
407 if isinstance(snmp_communities, dict):
408 console.error("ERROR: snmp_communities cannot be a dict any more.\n")
409 sys.exit(1)
412 def _verify_no_deprecated_check_rulesets():
413 deprecated_rulesets = [
414 ("services", "inventory_services"),
415 ("domino_tasks", "inv_domino_tasks"),
416 ("ps", "inventory_processes"),
417 ("logwatch", "logwatch_patterns"),
419 for check_plugin_name, varname in deprecated_rulesets:
420 check_context = get_check_context(check_plugin_name)
421 if check_context[varname]:
422 console.warning(
423 "Found rules for deprecated ruleset %r. These rules are not applied "
424 "anymore. In case you still need them, you need to migrate them by hand. "
425 "Otherwise you can remove them from your configuration." % varname)
428 def all_nonfunction_vars():
429 return set(
430 [name for name, value in globals().items() if name[0] != '_' and not callable(value)])
433 class PackedConfig(object):
434 """The precompiled host checks and the CMC Check_MK helpers use a
435 "precompiled" part of the Check_MK configuration during runtime.
437 a) They must not use the live config from etc/check_mk during
438 startup. They are only allowed to load the config activated by
439 the user.
441 b) They must not load the whole Check_MK config. Because they only
442 need the options needed for checking
445 # These variables are part of the Check_MK configuration, but are not needed
446 # by the Check_MK keepalive mode, so exclude them from the packed config
447 _skipped_config_variable_names = [
448 "define_contactgroups",
449 "define_hostgroups",
450 "define_servicegroups",
451 "service_contactgroups",
452 "host_contactgroups",
453 "service_groups",
454 "host_groups",
455 "contacts",
456 "timeperiods",
457 "extra_service_conf",
458 "extra_nagios_conf",
461 def __init__(self):
462 super(PackedConfig, self).__init__()
463 self._path = os.path.join(cmk.utils.paths.var_dir, "base", "precompiled_check_config.mk")
465 def save(self):
466 self._write(self._pack())
468 def _pack(self):
469 helper_config = ("#!/usr/bin/env python\n"
470 "# encoding: utf-8\n"
471 "# Created by Check_MK. Dump of the currently active configuration\n\n")
473 # These functions purpose is to filter out hosts which are monitored on different sites
474 active_hosts = all_active_hosts()
475 active_clusters = all_active_clusters()
477 def filter_all_hosts(all_hosts_orig):
478 all_hosts_red = []
479 for host_entry in all_hosts_orig:
480 hostname = host_entry.split("|", 1)[0]
481 if hostname in active_hosts:
482 all_hosts_red.append(host_entry)
483 return all_hosts_red
485 def filter_clusters(clusters_orig):
486 clusters_red = {}
487 for cluster_entry, cluster_nodes in clusters_orig.items():
488 clustername = cluster_entry.split("|", 1)[0]
489 if clustername in active_clusters:
490 clusters_red[cluster_entry] = cluster_nodes
491 return clusters_red
493 def filter_hostname_in_dict(values):
494 values_red = {}
495 for hostname, attributes in values.items():
496 if hostname in active_hosts:
497 values_red[hostname] = attributes
498 return values_red
500 filter_var_functions = {
501 "all_hosts": filter_all_hosts,
502 "clusters": filter_clusters,
503 "host_attributes": filter_hostname_in_dict,
504 "ipaddresses": filter_hostname_in_dict,
505 "ipv6addresses": filter_hostname_in_dict,
506 "explicit_snmp_communities": filter_hostname_in_dict,
507 "hosttags": filter_hostname_in_dict
511 # Add modified Check_MK base settings
514 variable_defaults = get_default_config()
515 derived_config_variable_names = get_derived_config_variable_names()
517 global_variables = globals()
519 for varname in get_variable_names() + list(derived_config_variable_names):
520 if varname in self._skipped_config_variable_names:
521 continue
523 val = global_variables[varname]
525 if varname not in derived_config_variable_names and val == variable_defaults[varname]:
526 continue
528 if not self._packable(varname, val):
529 continue
531 if varname in filter_var_functions:
532 val = filter_var_functions[varname](val)
534 helper_config += "\n%s = %r\n" % (varname, val)
537 # Add modified check specific Check_MK base settings
540 check_variable_defaults = get_check_variable_defaults()
542 for varname, val in get_check_variables().items():
543 if val == check_variable_defaults[varname]:
544 continue
546 if not self._packable(varname, val):
547 continue
549 helper_config += "\n%s = %r\n" % (varname, val)
551 return helper_config
553 def _packable(self, varname, val):
554 """Checks whether or not a variable can be written to the config.mk
555 and read again from it."""
556 if isinstance(val, six.string_types + (int, bool)) or not val:
557 return True
559 try:
560 eval(repr(val))
561 return True
562 except:
563 return False
565 def _write(self, helper_config):
566 store.makedirs(os.path.dirname(self._path))
568 store.save_file(self._path + ".orig", helper_config + "\n")
570 code = compile(helper_config, '<string>', 'exec')
571 with open(self._path + ".compiled", "w") as compiled_file:
572 marshal.dump(code, compiled_file)
574 os.rename(self._path + ".compiled", self._path)
576 def load(self):
577 _initialize_config()
578 exec (marshal.load(open(self._path)), globals())
579 _perform_post_config_loading_actions()
583 # .--Host tags-----------------------------------------------------------.
584 # | _ _ _ _ |
585 # | | | | | ___ ___| |_ | |_ __ _ __ _ ___ |
586 # | | |_| |/ _ \/ __| __| | __/ _` |/ _` / __| |
587 # | | _ | (_) \__ \ |_ | || (_| | (_| \__ \ |
588 # | |_| |_|\___/|___/\__| \__\__,_|\__, |___/ |
589 # | |___/ |
590 # +----------------------------------------------------------------------+
591 # | Helper functions for dealing with host tags |
592 # '----------------------------------------------------------------------'
595 def strip_tags(tagged_hostlist):
596 cache = cmk_base.config_cache.get_dict("strip_tags")
598 cache_id = tuple(tagged_hostlist)
599 try:
600 return cache[cache_id]
601 except KeyError:
602 result = [h.split('|', 1)[0] for h in tagged_hostlist]
603 cache[cache_id] = result
604 return result
608 # .--HostCollections-----------------------------------------------------.
609 # | _ _ _ ____ _ _ _ _ |
610 # || | | | ___ ___| |_ / ___|___ | | | ___ ___| |_(_) ___ _ __ ___ |
611 # || |_| |/ _ \/ __| __| | / _ \| | |/ _ \/ __| __| |/ _ \| '_ \/ __| |
612 # || _ | (_) \__ \ |_| |__| (_) | | | __/ (__| |_| | (_) | | | \__ \ |
613 # ||_| |_|\___/|___/\__|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|___/ |
614 # | |
615 # +----------------------------------------------------------------------+
616 # | |
617 # '----------------------------------------------------------------------'
620 # Returns a set of all active hosts
621 def all_active_hosts():
622 cache = cmk_base.config_cache.get_set("all_active_hosts")
623 if not cache.is_populated():
624 cache.update(all_active_realhosts(), all_active_clusters())
625 cache.set_populated()
626 return cache
629 # Returns a set of all host names to be handled by this site
630 # hosts of other sitest or disabled hosts are excluded
631 def all_active_realhosts():
632 active_realhosts = cmk_base.config_cache.get_set("active_realhosts")
634 if not active_realhosts.is_populated():
635 active_realhosts.update(filter_active_hosts(all_configured_realhosts()))
636 active_realhosts.set_populated()
638 return active_realhosts
641 # Returns a set of all cluster host names to be handled by
642 # this site hosts of other sitest or disabled hosts are excluded
643 def all_active_clusters():
644 active_clusters = cmk_base.config_cache.get_set("active_clusters")
646 if not active_clusters.is_populated():
647 active_clusters.update(filter_active_hosts(all_configured_clusters()))
648 active_clusters.set_populated()
650 return active_clusters
653 # Returns a set of all hosts, regardless if currently
654 # disabled or monitored on a remote site.
655 def all_configured_hosts():
656 cache = cmk_base.config_cache.get_set("all_configured_hosts")
657 if not cache.is_populated():
658 cache.update(all_configured_realhosts(), all_configured_clusters())
659 cache.set_populated()
660 return cache
663 # Returns a set of all host names, regardless if currently
664 # disabled or monitored on a remote site. Does not return
665 # cluster hosts.
666 def all_configured_realhosts():
667 cache = cmk_base.config_cache.get_set("all_configured_realhosts")
668 if not cache.is_populated():
669 cache.update(strip_tags(all_hosts))
670 cache.set_populated()
671 return cache
674 # Returns a set of all cluster names, regardless if currently
675 # disabled or monitored on a remote site. Does not return
676 # normal hosts.
677 def all_configured_clusters():
678 cache = cmk_base.config_cache.get_set("all_configured_clusters")
679 if not cache.is_populated():
680 cache.update(strip_tags(clusters.keys()))
681 cache.set_populated()
682 return cache
685 # This function should only be used during duplicate host check! It has to work like
686 # all_active_hosts() but with the difference that duplicates are not removed.
687 def all_active_hosts_with_duplicates():
688 # Only available with CEE
689 if "shadow_hosts" in globals():
690 shadow_host_entries = shadow_hosts.keys()
691 else:
692 shadow_host_entries = []
694 return filter_active_hosts(strip_tags(all_hosts) \
695 + strip_tags(clusters.keys()) \
696 + strip_tags(shadow_host_entries), keep_duplicates=True)
699 # Returns a set of active hosts for this site
700 def filter_active_hosts(hostlist, keep_offline_hosts=False, keep_duplicates=False):
701 if only_hosts is None and distributed_wato_site is None:
702 active_hosts = hostlist
704 elif only_hosts is None:
705 active_hosts = [
706 hostname for hostname in hostlist
707 if host_is_member_of_site(hostname, distributed_wato_site)
710 elif distributed_wato_site is None:
711 if keep_offline_hosts:
712 active_hosts = hostlist
713 else:
714 active_hosts = [
715 hostname for hostname in hostlist if in_binary_hostlist(hostname, only_hosts)
718 else:
719 active_hosts = [
720 hostname for hostname in hostlist
721 if (keep_offline_hosts or in_binary_hostlist(hostname, only_hosts)) and
722 host_is_member_of_site(hostname, distributed_wato_site)
725 if keep_duplicates:
726 return active_hosts
728 return set(active_hosts)
731 def duplicate_hosts():
732 seen_hostnames = set([])
733 duplicates = set([])
735 for hostname in all_active_hosts_with_duplicates():
736 if hostname in seen_hostnames:
737 duplicates.add(hostname)
738 else:
739 seen_hostnames.add(hostname)
741 return sorted(list(duplicates))
744 # Returns a list of all hosts which are associated with this site,
745 # but have been removed by the "only_hosts" rule. Normally these
746 # are the hosts which have the tag "offline".
748 # This is not optimized for performance, so use in specific situations.
749 def all_offline_hosts():
750 hostlist = filter_active_hosts(
751 all_configured_realhosts().union(all_configured_clusters()), keep_offline_hosts=True)
753 return [hostname for hostname in hostlist if not in_binary_hostlist(hostname, only_hosts)]
756 def all_configured_offline_hosts():
757 hostlist = all_configured_realhosts().union(all_configured_clusters())
759 return set([hostname for hostname in hostlist if not in_binary_hostlist(hostname, only_hosts)])
763 # .--Hosts---------------------------------------------------------------.
764 # | _ _ _ |
765 # | | | | | ___ ___| |_ ___ |
766 # | | |_| |/ _ \/ __| __/ __| |
767 # | | _ | (_) \__ \ |_\__ \ |
768 # | |_| |_|\___/|___/\__|___/ |
769 # | |
770 # +----------------------------------------------------------------------+
771 # | Helper functions for dealing with hosts. |
772 # '----------------------------------------------------------------------'
775 def host_is_member_of_site(hostname, site):
776 for tag in get_config_cache().get_host_config(hostname).tags:
777 if tag.startswith("site:"):
778 return site == tag[5:]
779 # hosts without a site: tag belong to all sites
780 return True
783 def alias_of(hostname, fallback):
784 aliases = host_extra_conf(hostname, extra_host_conf.get("alias", []))
785 if len(aliases) == 0:
786 if fallback:
787 return fallback
789 return hostname
791 return aliases[0]
794 def get_additional_ipaddresses_of(hostname):
795 #TODO Regarding the following configuration variables from WATO
796 # there's no inheritance, thus we use 'host_attributes'.
797 # Better would be to use cmk_base configuration variables,
798 # eg. like 'management_protocol'.
799 return (host_attributes.get(hostname, {}).get("additional_ipv4addresses", []),
800 host_attributes.get(hostname, {}).get("additional_ipv6addresses", []))
803 def parents_of(hostname):
804 par = host_extra_conf(hostname, parents)
805 # Use only those parents which are defined and active in
806 # all_hosts.
807 used_parents = []
808 for p in par:
809 ps = p.split(",")
810 for pss in ps:
811 if pss in all_active_realhosts():
812 used_parents.append(pss)
813 return used_parents
816 # If host is node of one or more clusters, return a list of the cluster host names.
817 # If not, return an empty list.
818 # TODO: Replace call sites with HostConfig access and remove this
819 def clusters_of(hostname):
820 return get_config_cache().get_host_config(hostname).part_of_clusters
824 # IPv4/IPv6
828 # TODO: Replace call sites with HostConfig access and remove this
829 def is_ipv6_primary(hostname):
830 return get_config_cache().get_host_config(hostname).is_ipv6_primary
833 # TODO: Replace call sites with HostConfig access and remove this
834 def is_ipv4v6_host(hostname):
835 return get_config_cache().get_host_config(hostname).is_ipv4v6_host
838 # TODO: Replace call sites with HostConfig access and remove this
839 def is_ipv6_host(hostname):
840 return get_config_cache().get_host_config(hostname).is_ipv6_host
843 # TODO: Replace call sites with HostConfig access and remove this
844 def is_ipv4_host(hostname):
845 return get_config_cache().get_host_config(hostname).is_ipv4_host
848 # TODO: Replace call sites with HostConfig access and remove this
849 def is_no_ip_host(hostname):
850 return get_config_cache().get_host_config(hostname).is_no_ip_host
854 # Management board
858 def management_address_of(hostname):
859 attributes_of_host = host_attributes.get(hostname, {})
860 if attributes_of_host.get("management_address"):
861 return attributes_of_host["management_address"]
863 return ipaddresses.get(hostname)
866 def management_credentials_of(hostname):
867 protocol = get_config_cache().get_host_config(hostname).management_protocol
868 if protocol == "snmp":
869 credentials_variable, default_value = management_snmp_credentials, snmp_default_community
870 elif protocol == "ipmi":
871 credentials_variable, default_value = management_ipmi_credentials, None
872 elif protocol is None:
873 return None
874 else:
875 raise NotImplementedError()
877 # First try to use the explicit configuration of the host
878 # (set directly for a host or via folder inheritance in WATO)
879 try:
880 return credentials_variable[hostname]
881 except KeyError:
882 pass
884 # If a rule matches, use the first rule for the management board protocol of the host
885 rule_settings = host_extra_conf(hostname, management_board_config)
886 for rule_protocol, credentials in rule_settings:
887 if rule_protocol == protocol:
888 return credentials
890 return default_value
894 # Agent communication
898 def agent_port_of(hostname):
899 ports = host_extra_conf(hostname, agent_ports)
900 if len(ports) == 0:
901 return agent_port
903 return ports[0]
906 def tcp_connect_timeout_of(hostname):
907 timeouts = host_extra_conf(hostname, tcp_connect_timeouts)
908 if len(timeouts) == 0:
909 return tcp_connect_timeout
911 return timeouts[0]
914 def agent_encryption_of(hostname):
915 settings = host_extra_conf(hostname, agent_encryption)
916 if settings:
917 return settings[0]
919 return {'use_regular': 'disable', 'use_realtime': 'enforce'}
922 def agent_target_version(hostname):
923 agent_target_versions = host_extra_conf(hostname, check_mk_agent_target_versions)
924 if agent_target_versions:
925 spec = agent_target_versions[0]
926 if spec == "ignore":
927 return None
928 elif spec == "site":
929 return cmk.__version__
930 elif isinstance(spec, str):
931 # Compatibility to old value specification format (a single version string)
932 return spec
933 elif spec[0] == 'specific':
934 return spec[1]
936 return spec # return the whole spec in case of an "at least version" config
940 # Explicit custom variables
942 def get_explicit_service_custom_variables(hostname, description):
943 try:
944 return explicit_service_custom_variables[(hostname, description)]
945 except KeyError:
946 return {}
950 # SNMP
954 # Determine SNMP community for a specific host. It the host is found
955 # int the map snmp_communities, that community is returned. Otherwise
956 # the snmp_default_community is returned (wich is preset with
957 # "public", but can be overridden in main.mk
958 def snmp_credentials_of(hostname):
959 try:
960 return explicit_snmp_communities[hostname]
961 except KeyError:
962 pass
964 communities = host_extra_conf(hostname, snmp_communities)
965 if len(communities) > 0:
966 return communities[0]
968 # nothing configured for this host -> use default
969 return snmp_default_community
972 def snmp_character_encoding_of(hostname):
973 entries = host_extra_conf(hostname, snmp_character_encodings)
974 if len(entries) > 0:
975 return entries[0]
978 def snmp_timing_of(hostname):
979 timing = host_extra_conf(hostname, snmp_timing)
980 if len(timing) > 0:
981 return timing[0]
982 return {}
985 def snmpv3_contexts_of(hostname):
986 return host_extra_conf(hostname, snmpv3_contexts)
989 def oid_range_limits_of(hostname):
990 return host_extra_conf(hostname, snmp_limit_oid_range)
993 def snmp_port_of(hostname):
994 # type: (str) -> int
995 ports = host_extra_conf(hostname, snmp_ports)
996 if len(ports) == 0:
997 return 161
998 return ports[0]
1001 def is_bulkwalk_host(hostname):
1002 # type: (str) -> bool
1003 if bulkwalk_hosts:
1004 return in_binary_hostlist(hostname, bulkwalk_hosts)
1006 return False
1009 def bulk_walk_size_of(hostname):
1010 bulk_sizes = host_extra_conf(hostname, snmp_bulk_size)
1011 if not bulk_sizes:
1012 return 10
1014 return bulk_sizes[0]
1017 def is_snmpv2or3_without_bulkwalk_host(hostname):
1018 return in_binary_hostlist(hostname, snmpv2c_hosts)
1021 # TODO: Replace call sites with HostConfig access and remove this
1022 def is_usewalk_host(hostname):
1023 return get_config_cache().get_host_config(hostname).is_usewalk_host
1026 def is_inline_snmp_host(hostname):
1027 # TODO: Better use "inline_snmp" once we have moved the code to an own module
1028 has_inline_snmp = "netsnmp" in sys.modules
1029 return has_inline_snmp and use_inline_snmp \
1030 and not in_binary_hostlist(hostname, non_inline_snmp_hosts)
1034 # Groups
1038 def hostgroups_of(hostname):
1039 return host_extra_conf(hostname, host_groups)
1042 def summary_hostgroups_of(hostname):
1043 return host_extra_conf(hostname, summary_host_groups)
1046 def contactgroups_of(hostname):
1047 cgrs = []
1049 # host_contactgroups may take single values as well as
1050 # lists as item value. Of all list entries only the first
1051 # one is used. The single-contact-groups entries are all
1052 # recognized.
1053 first_list = True
1054 for entry in host_extra_conf(hostname, host_contactgroups):
1055 if isinstance(entry, list) and first_list:
1056 cgrs += entry
1057 first_list = False
1058 else:
1059 cgrs.append(entry)
1061 if monitoring_core == "nagios" and enable_rulebased_notifications:
1062 cgrs.append("check-mk-notify")
1064 return list(set(cgrs))
1068 # Misc
1072 def exit_code_spec(hostname, data_source_id=None):
1073 spec = {}
1074 specs = host_extra_conf(hostname, check_mk_exit_status)
1075 for entry in specs[::-1]:
1076 spec.update(entry)
1077 return _get_exit_code_spec(spec, data_source_id)
1080 def _get_exit_code_spec(spec, data_source_id):
1081 if data_source_id is not None:
1082 try:
1083 return spec["individual"][data_source_id]
1084 except KeyError:
1085 pass
1087 try:
1088 return spec["overall"]
1089 except KeyError:
1090 pass
1092 # Old configuration format
1093 return spec
1096 def check_period_of(hostname, service):
1097 periods = service_extra_conf(hostname, service, check_periods)
1098 if periods:
1099 period = periods[0]
1100 if period == "24X7":
1101 return None
1103 return period
1105 return None
1108 def check_interval_of(hostname, section_name):
1109 if not cmk_base.cmk_base.check_utils.is_snmp_check(section_name):
1110 return # no values at all for non snmp checks
1112 # Previous to 1.5 "match" could be a check name (including subchecks) instead of
1113 # only main check names -> section names. This has been cleaned up, but we still
1114 # need to be compatible. Strip of the sub check part of "match".
1115 for match, minutes in host_extra_conf(hostname, snmp_check_interval):
1116 if match is None or match.split(".")[0] == section_name:
1117 return minutes # use first match
1121 # .--Cluster-------------------------------------------------------------.
1122 # | ____ _ _ |
1123 # | / ___| |_ _ ___| |_ ___ _ __ |
1124 # | | | | | | | / __| __/ _ \ '__| |
1125 # | | |___| | |_| \__ \ || __/ | |
1126 # | \____|_|\__,_|___/\__\___|_| |
1127 # | |
1128 # +----------------------------------------------------------------------+
1129 # | Code dealing with clusters (virtual hosts that are used to deal with |
1130 # | services that can move between physical nodes. |
1131 # '----------------------------------------------------------------------'
1134 # Checks whether or not the given host is a cluster host
1135 def is_cluster(hostname):
1136 # all_configured_clusters() needs to be used, because this function affects
1137 # the agent bakery, which needs all configured hosts instead of just the hosts
1138 # of this site
1139 return hostname in all_configured_clusters()
1142 # Returns the nodes of a cluster, or None if hostname is not a cluster
1143 def nodes_of(hostname):
1144 return get_config_cache().nodes_of(hostname)
1147 # Determine weather a service (found on a physical host) is a clustered
1148 # service and - if yes - return the cluster host of the service. If
1149 # no, returns the hostname of the physical host.
1150 def host_of_clustered_service(hostname, servicedesc, part_of_clusters=None):
1151 return get_config_cache().host_of_clustered_service(
1152 hostname, servicedesc, part_of_clusters=part_of_clusters)
1156 # .--Services------------------------------------------------------------.
1157 # | ____ _ |
1158 # | / ___| ___ _ ____ _(_) ___ ___ ___ |
1159 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \/ __| |
1160 # | ___) | __/ | \ V /| | (_| __/\__ \ |
1161 # | |____/ \___|_| \_/ |_|\___\___||___/ |
1162 # | |
1163 # +----------------------------------------------------------------------+
1164 # | Service related helper functions |
1165 # '----------------------------------------------------------------------'
1167 # Renaming of service descriptions while keeping backward compatibility with
1168 # existing installations.
1169 # Synchronize with htdocs/wato.py and plugins/wato/check_mk_configuration.py!
1172 # Cleanup! .. some day
1173 def _get_old_cmciii_temp_description(item):
1174 if "Temperature" in item:
1175 return False, item # old item format, no conversion
1177 parts = item.split(" ")
1178 if parts[0] == "Ambient":
1179 return False, "%s Temperature" % parts[1]
1181 elif len(parts) == 2:
1182 return False, "%s %s.Temperature" % (parts[1], parts[0])
1184 else:
1185 if parts[1] == "LCP":
1186 parts[1] = "Liquid_Cooling_Package"
1187 return False, "%s %s.%s-Temperature" % (parts[1], parts[0], parts[2])
1190 _old_service_descriptions = {
1191 "df": "fs_%s",
1192 "df_netapp": "fs_%s",
1193 "df_netapp32": "fs_%s",
1194 "esx_vsphere_datastores": "fs_%s",
1195 "hr_fs": "fs_%s",
1196 "vms_diskstat.df": "fs_%s",
1197 "zfsget": "fs_%s",
1198 "ps": "proc_%s",
1199 "ps.perf": "proc_%s",
1200 "wmic_process": "proc_%s",
1201 "services": "service_%s",
1202 "logwatch": "LOG %s",
1203 "logwatch.groups": "LOG %s",
1204 "hyperv_vm": "hyperv_vms",
1205 "ibm_svc_mdiskgrp": "MDiskGrp %s",
1206 "ibm_svc_system": "IBM SVC Info",
1207 "ibm_svc_systemstats.diskio": "IBM SVC Throughput %s Total",
1208 "ibm_svc_systemstats.iops": "IBM SVC IOPS %s Total",
1209 "ibm_svc_systemstats.disk_latency": "IBM SVC Latency %s Total",
1210 "ibm_svc_systemstats.cache": "IBM SVC Cache Total",
1211 "mknotifyd": "Notification Spooler %s",
1212 "mknotifyd.connection": "Notification Connection %s",
1213 "casa_cpu_temp": "Temperature %s",
1214 "cmciii.temp": _get_old_cmciii_temp_description,
1215 "cmciii.psm_current": "%s",
1216 "cmciii_lcp_airin": "LCP Fanunit Air IN",
1217 "cmciii_lcp_airout": "LCP Fanunit Air OUT",
1218 "cmciii_lcp_water": "LCP Fanunit Water %s",
1219 "etherbox.temp": "Sensor %s",
1220 # While using the old description, don't append the item, even when discovered
1221 # with the new check which creates an item.
1222 "liebert_bat_temp": lambda item: (False, "Battery Temp"),
1223 "nvidia.temp": "Temperature NVIDIA %s",
1224 "ups_bat_temp": "Temperature Battery %s",
1225 "innovaphone_temp": lambda item: (False, "Temperature"),
1226 "enterasys_temp": lambda item: (False, "Temperature"),
1227 "raritan_emx": "Rack %s",
1228 "raritan_pdu_inlet": "Input Phase %s",
1229 "postfix_mailq": lambda item: (False, "Postfix Queue"),
1230 "nullmailer_mailq": lambda item: (False, "Nullmailer Queue"),
1231 "barracuda_mailqueues": lambda item: (False, "Mail Queue"),
1232 "qmail_stats": lambda item: (False, "Qmail Queue"),
1233 "mssql_backup": "%s Backup",
1234 "mssql_counters.cache_hits": "%s",
1235 "mssql_counters.transactions": "%s Transactions",
1236 "mssql_counters.locks": "%s Locks",
1237 "mssql_counters.sqlstats": "%s",
1238 "mssql_counters.pageactivity": "%s Page Activity",
1239 "mssql_counters.locks_per_batch": "%s Locks per Batch",
1240 "mssql_counters.file_sizes": "%s File Sizes",
1241 "mssql_databases": "%s Database",
1242 "mssql_datafiles": "Datafile %s",
1243 "mssql_tablespaces": "%s Sizes",
1244 "mssql_transactionlogs": "Transactionlog %s",
1245 "mssql_versions": "%s Version",
1246 "mssql_blocked_sessions": lambda item: (False, "MSSQL Blocked Sessions"),
1250 def service_description(hostname, check_plugin_name, item):
1251 if check_plugin_name not in check_info:
1252 if item:
1253 return "Unimplemented check %s / %s" % (check_plugin_name, item)
1254 return "Unimplemented check %s" % check_plugin_name
1256 # use user-supplied service description, if available
1257 add_item = True
1258 descr_format = service_descriptions.get(check_plugin_name)
1259 if not descr_format:
1260 # handle renaming for backward compatibility
1261 if check_plugin_name in _old_service_descriptions and \
1262 check_plugin_name not in use_new_descriptions_for:
1264 # Can be a fucntion to generate the old description more flexible.
1265 old_descr = _old_service_descriptions[check_plugin_name]
1266 if callable(old_descr):
1267 add_item, descr_format = old_descr(item)
1268 else:
1269 descr_format = old_descr
1271 else:
1272 descr_format = check_info[check_plugin_name]["service_description"]
1274 if isinstance(descr_format, str):
1275 descr_format = descr_format.decode("utf-8")
1277 # Note: we strip the service description (remove spaces).
1278 # One check defines "Pages %s" as a description, but the item
1279 # can by empty in some cases. Nagios silently drops leading
1280 # and trailing spaces in the configuration file.
1281 if add_item and isinstance(item, six.string_types + (numbers.Integral,)):
1282 if "%s" not in descr_format:
1283 descr_format += " %s"
1284 descr = descr_format % (item,)
1285 else:
1286 descr = descr_format
1288 if "%s" in descr:
1289 raise MKGeneralException("Found '%%s' in service description (Host: %s, Check type: %s, Item: %s). "
1290 "Please try to rediscover the service to fix this issue." % \
1291 (hostname, check_plugin_name, item))
1293 return get_final_service_description(hostname, descr)
1296 _old_active_check_service_descriptions = {
1297 "http": lambda params: (params[0][1:] if params[0].startswith("^") else "HTTP %s" % params[0])
1301 def active_check_service_description(hostname, active_check_name, params):
1302 if active_check_name not in active_check_info:
1303 return "Unimplemented check %s" % active_check_name
1305 if (active_check_name in _old_active_check_service_descriptions and
1306 active_check_name not in use_new_descriptions_for):
1307 description = _old_active_check_service_descriptions[active_check_name](params)
1308 else:
1309 act_info = active_check_info[active_check_name]
1310 description = act_info["service_description"](params)
1312 description = description.replace('$HOSTNAME$', hostname)
1314 return get_final_service_description(hostname, description)
1317 def get_final_service_description(hostname, description):
1318 translations = get_service_translations(hostname)
1319 if translations:
1320 # Translate
1321 description = cmk.utils.translations.translate_service_description(
1322 translations, description)
1324 # Sanitize; Remove illegal characters from a service description
1325 description = description.strip()
1326 cache = cmk_base.config_cache.get_dict("final_service_description")
1327 try:
1328 new_description = cache[description]
1329 except KeyError:
1330 new_description = "".join(
1331 [c for c in description if c not in nagios_illegal_chars]).rstrip("\\")
1332 cache[description] = new_description
1334 return new_description
1337 def service_ignored(hostname, check_plugin_name, description):
1338 if check_plugin_name and check_plugin_name in ignored_checktypes:
1339 return True
1341 if check_plugin_name and _checktype_ignored_for_host(hostname, check_plugin_name):
1342 return True
1344 if description is not None \
1345 and in_boolean_serviceconf_list(hostname, description, ignored_services):
1346 return True
1348 return False
1351 def _checktype_ignored_for_host(host, checktype):
1352 if checktype in ignored_checktypes:
1353 return True
1354 ignored = host_extra_conf(host, ignored_checks)
1355 for e in ignored:
1356 if checktype == e or (isinstance(e, list) and checktype in e):
1357 return True
1358 return False
1361 # TODO: Make this use the generic "rulesets" functions
1362 # a) This function has never been configurable via WATO (see https://mathias-kettner.de/checkmk_service_dependencies.html)
1363 # b) It only affects the Nagios core - CMC does not implement service dependencies
1364 # c) This function implements some specific regex replacing match+replace which makes it incompatible to
1365 # regular service rulesets. Therefore service_extra_conf() can not easily be used :-/
1366 def service_depends_on(hostname, servicedesc):
1367 """Return a list of services this services depends upon"""
1368 deps = []
1369 config_cache = get_config_cache()
1370 for entry in service_dependencies:
1371 entry, rule_options = get_rule_options(entry)
1372 if rule_options.get("disabled"):
1373 continue
1375 if len(entry) == 3:
1376 depname, hostlist, patternlist = entry
1377 tags = []
1378 elif len(entry) == 4:
1379 depname, tags, hostlist, patternlist = entry
1380 else:
1381 raise MKGeneralException("Invalid entry '%r' in service dependencies: "
1382 "must have 3 or 4 entries" % entry)
1384 if hosttags_match_taglist(config_cache.tag_list_of_host(hostname), tags) and \
1385 in_extraconf_hostlist(hostlist, hostname):
1386 for pattern in patternlist:
1387 matchobject = regex(pattern).search(servicedesc)
1388 if matchobject:
1389 try:
1390 item = matchobject.groups()[-1]
1391 deps.append(depname % item)
1392 except:
1393 deps.append(depname)
1394 return deps
1398 # .--Misc Helpers--------------------------------------------------------.
1399 # | __ __ _ _ _ _ |
1400 # | | \/ (_)___ ___ | | | | ___| |_ __ ___ _ __ ___ |
1401 # | | |\/| | / __|/ __| | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
1402 # | | | | | \__ \ (__ | _ | __/ | |_) | __/ | \__ \ |
1403 # | |_| |_|_|___/\___| |_| |_|\___|_| .__/ \___|_| |___/ |
1404 # | |_| |
1405 # +----------------------------------------------------------------------+
1406 # | Different helper functions |
1407 # '----------------------------------------------------------------------'
1410 def is_cmc():
1411 """Whether or not the site is currently configured to use the Microcore."""
1412 return monitoring_core == "cmc"
1415 def decode_incoming_string(s, encoding="utf-8"):
1416 try:
1417 return s.decode(encoding)
1418 except:
1419 return s.decode(fallback_agent_output_encoding)
1422 def translate_piggyback_host(sourcehost, backedhost):
1423 translation = _get_piggyback_translations(sourcehost)
1425 # To make it possible to match umlauts we need to change the hostname
1426 # to a unicode string which can then be matched with regexes etc.
1427 # We assume the incoming name is correctly encoded in UTF-8
1428 backedhost = decode_incoming_string(backedhost)
1430 translated = cmk.utils.translations.translate_hostname(translation, backedhost)
1432 return translated.encode('utf-8') # change back to UTF-8 encoded string
1435 def _get_piggyback_translations(hostname):
1436 """Get a dict that specifies the actions to be done during the hostname translation"""
1437 rules = host_extra_conf(hostname, piggyback_translation)
1438 translations = {}
1439 for rule in rules[::-1]:
1440 translations.update(rule)
1441 return translations
1444 def get_service_translations(hostname):
1445 translations_cache = cmk_base.config_cache.get_dict("service_description_translations")
1446 if hostname in translations_cache:
1447 return translations_cache[hostname]
1449 rules = host_extra_conf(hostname, service_description_translation)
1450 translations = {}
1451 for rule in rules[::-1]:
1452 for k, v in rule.items():
1453 if isinstance(v, list):
1454 translations.setdefault(k, set())
1455 translations[k] |= set(v)
1456 else:
1457 translations[k] = v
1459 translations_cache[hostname] = translations
1460 return translations
1463 def prepare_check_command(command_spec, hostname, description):
1464 """Prepares a check command for execution by Check_MK.
1466 This function either accepts a string or a list of arguments as
1467 command_spec. In case a list is given it quotes the single elements. It
1468 also prepares password store entries for the command line. These entries
1469 will be completed by the executed program later to get the password from
1470 the password store.
1472 if isinstance(command_spec, six.string_types):
1473 return command_spec
1475 if not isinstance(command_spec, list):
1476 raise NotImplementedError()
1478 passwords, formated = [], []
1479 for arg in command_spec:
1480 arg_type = type(arg)
1482 if arg_type in [int, float]:
1483 formated.append("%s" % arg)
1485 elif arg_type in [str, unicode]:
1486 formated.append(cmk_base.utils.quote_shell_string(arg))
1488 elif arg_type == tuple and len(arg) == 3:
1489 pw_ident, preformated_arg = arg[1:]
1490 try:
1491 password = stored_passwords[pw_ident]["password"]
1492 except KeyError:
1493 if hostname and description:
1494 descr = " used by service \"%s\" on host \"%s\"" % (description, hostname)
1495 elif hostname:
1496 descr = " used by host host \"%s\"" % (hostname)
1497 else:
1498 descr = ""
1500 console.warning(
1501 "The stored password \"%s\"%s does not exist (anymore)." % (pw_ident, descr))
1502 password = "%%%"
1504 pw_start_index = str(preformated_arg.index("%s"))
1505 formated.append(
1506 cmk_base.utils.quote_shell_string(preformated_arg % ("*" * len(password))))
1507 passwords.append((str(len(formated)), pw_start_index, pw_ident))
1509 else:
1510 raise MKGeneralException("Invalid argument for command line: %r" % (arg,))
1512 if passwords:
1513 formated = ["--pwstore=%s" % ",".join(["@".join(p) for p in passwords])] + formated
1515 return " ".join(formated)
1518 def get_http_proxy(http_proxy):
1519 # type: (Tuple) -> Optional[str]
1520 """Returns proxy URL to be used for HTTP requests
1522 Pass a value configured by the user using the HTTPProxyReference valuespec to this function
1523 and you will get back ether a proxy URL, an empty string to enforce no proxy usage or None
1524 to use the proxy configuration from the process environment.
1526 if not isinstance(http_proxy, tuple):
1527 return None
1529 proxy_type, value = http_proxy
1531 if proxy_type == "environment":
1532 return None
1534 if proxy_type == "global":
1535 return http_proxies.get(value, {}).get("proxy_url", None)
1537 if proxy_type == "url":
1538 return value
1540 if proxy_type == "no_proxy":
1541 return ""
1543 return None
1547 # .--Service rules-------------------------------------------------------.
1548 # | ____ _ _ |
1549 # | / ___| ___ _ ____ _(_) ___ ___ _ __ _ _| | ___ ___ |
1550 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \ | '__| | | | |/ _ \/ __| |
1551 # | ___) | __/ | \ V /| | (_| __/ | | | |_| | | __/\__ \ |
1552 # | |____/ \___|_| \_/ |_|\___\___| |_| \__,_|_|\___||___/ |
1553 # | |
1554 # +----------------------------------------------------------------------+
1555 # | Service rule set matching |
1556 # '----------------------------------------------------------------------'
1559 def service_extra_conf(hostname, service, ruleset):
1560 return get_config_cache().service_extra_conf(hostname, service, ruleset)
1563 # Compute outcome of a service rule set that just say yes/no
1564 def in_boolean_serviceconf_list(hostname, descr, ruleset):
1565 return get_config_cache().in_boolean_serviceconf_list(hostname, descr, ruleset)
1569 # .--Host rulesets-------------------------------------------------------.
1570 # | _ _ _ _ _ |
1571 # | | | | | ___ ___| |_ _ __ _ _| | ___ ___ ___| |_ ___ |
1572 # | | |_| |/ _ \/ __| __| | '__| | | | |/ _ \/ __|/ _ \ __/ __| |
1573 # | | _ | (_) \__ \ |_ | | | |_| | | __/\__ \ __/ |_\__ \ |
1574 # | |_| |_|\___/|___/\__| |_| \__,_|_|\___||___/\___|\__|___/ |
1575 # | |
1576 # +----------------------------------------------------------------------+
1577 # | Host ruleset matching |
1578 # '----------------------------------------------------------------------'
1581 def host_extra_conf(hostname, ruleset):
1582 return get_config_cache().host_extra_conf(hostname, ruleset)
1585 def host_extra_conf_merged(hostname, conf):
1586 return get_config_cache().host_extra_conf_merged(hostname, conf)
1590 # .--Host matching-------------------------------------------------------.
1591 # | _ _ _ _ _ _ |
1592 # | | | | | ___ ___| |_ _ __ ___ __ _| |_ ___| |__ (_)_ __ __ _ |
1593 # | | |_| |/ _ \/ __| __| | '_ ` _ \ / _` | __/ __| '_ \| | '_ \ / _` | |
1594 # | | _ | (_) \__ \ |_ | | | | | | (_| | || (__| | | | | | | | (_| | |
1595 # | |_| |_|\___/|___/\__| |_| |_| |_|\__,_|\__\___|_| |_|_|_| |_|\__, | |
1596 # | |___/ |
1597 # +----------------------------------------------------------------------+
1598 # | Code for calculating the host condition matching of rules |
1599 # '----------------------------------------------------------------------'
1602 def all_matching_hosts(tags, hostlist, with_foreign_hosts):
1603 return get_config_cache().all_matching_hosts(tags, hostlist, with_foreign_hosts)
1606 def in_extraconf_hostlist(hostlist, hostname):
1607 """Whether or not the given host matches the hostlist.
1609 Entries in list are hostnames that must equal the hostname.
1610 Expressions beginning with ! are negated: if they match,
1611 the item is excluded from the list.
1613 Expressions beginning with ~ are treated as regular expression.
1614 Also the three special tags '@all', '@clusters', '@physical'
1615 are allowed.
1618 # Migration help: print error if old format appears in config file
1619 # FIXME: When can this be removed?
1620 try:
1621 if hostlist[0] == "":
1622 raise MKGeneralException('Invalid empty entry [ "" ] in configuration')
1623 except IndexError:
1624 pass # Empty list, no problem.
1626 for hostentry in hostlist:
1627 if hostentry == '':
1628 raise MKGeneralException('Empty hostname in host list %r' % hostlist)
1629 negate = False
1630 use_regex = False
1631 if hostentry[0] == '@':
1632 if hostentry == '@all':
1633 return True
1634 ic = is_cluster(hostname)
1635 if hostentry == '@cluster' and ic:
1636 return True
1637 elif hostentry == '@physical' and not ic:
1638 return True
1640 # Allow negation of hostentry with prefix '!'
1641 else:
1642 if hostentry[0] == '!':
1643 hostentry = hostentry[1:]
1644 negate = True
1646 # Allow regex with prefix '~'
1647 if hostentry[0] == '~':
1648 hostentry = hostentry[1:]
1649 use_regex = True
1651 try:
1652 if not use_regex and hostname == hostentry:
1653 return not negate
1654 # Handle Regex. Note: hostname == True -> generic unknown host
1655 elif use_regex and hostname != True:
1656 if regex(hostentry).match(hostname) is not None:
1657 return not negate
1658 except MKGeneralException:
1659 if cmk.utils.debug.enabled():
1660 raise
1662 return False
1665 def in_binary_hostlist(hostname, conf):
1666 return get_config_cache().in_binary_hostlist(hostname, conf)
1669 def parse_host_rule(rule):
1670 rule, rule_options = get_rule_options(rule)
1672 num_elements = len(rule)
1673 if num_elements == 2:
1674 item, hostlist = rule
1675 tags = []
1676 elif num_elements == 3:
1677 item, tags, hostlist = rule
1678 else:
1679 raise MKGeneralException("Invalid entry '%r' in host configuration list: must "
1680 "have 2 or 3 entries" % (rule,))
1682 return item, tags, hostlist, rule_options
1685 def get_rule_options(entry):
1686 """Get the options from a rule.
1688 Pick out the option element of a rule. Currently the options "disabled"
1689 and "comments" are being honored."""
1690 if isinstance(entry[-1], dict):
1691 return entry[:-1], entry[-1]
1693 return entry, {}
1696 def hosttags_match_taglist(hosttags, required_tags):
1697 """Check if a host fulfills the requirements of a tag list.
1699 The host must have all tags in the list, except
1700 for those negated with '!'. Those the host must *not* have!
1701 A trailing + means a prefix match."""
1702 for tag in required_tags:
1703 negate, tag = _parse_negated(tag)
1704 if tag and tag[-1] == '+':
1705 tag = tag[:-1]
1706 matches = False
1707 for t in hosttags:
1708 if t.startswith(tag):
1709 matches = True
1710 break
1712 else:
1713 matches = tag in hosttags
1715 if matches == negate:
1716 return False
1718 return True
1721 def _parse_negated(pattern):
1722 # Allow negation of pattern with prefix '!'
1723 try:
1724 negate = pattern[0] == '!'
1725 if negate:
1726 pattern = pattern[1:]
1727 except IndexError:
1728 negate = False
1730 return negate, pattern
1733 # Converts a regex pattern which is used to e.g. match services within Check_MK
1734 # to a function reference to a matching function which takes one parameter to
1735 # perform the matching and returns a two item tuple where the first element
1736 # tells wether or not the pattern is negated and the second element the outcome
1737 # of the match.
1738 # This function tries to parse the pattern and return different kind of matching
1739 # functions which can then be performed faster than just using the regex match.
1740 def _convert_pattern(pattern):
1741 def is_infix_string_search(pattern):
1742 return pattern.startswith('.*') and not is_regex(pattern[2:])
1744 def is_exact_match(pattern):
1745 return pattern[-1] == '$' and not is_regex(pattern[:-1])
1747 def is_prefix_match(pattern):
1748 return pattern[-2:] == '.*' and not is_regex(pattern[:-2])
1750 if pattern == '':
1751 return False, lambda txt: True # empty patterns match always
1753 negate, pattern = _parse_negated(pattern)
1755 if is_exact_match(pattern):
1756 # Exact string match
1757 return negate, lambda txt: pattern[:-1] == txt
1759 elif is_infix_string_search(pattern):
1760 # Using regex to search a substring within text
1761 return negate, lambda txt: pattern[2:] in txt
1763 elif is_prefix_match(pattern):
1764 # prefix match with tailing .*
1765 pattern = pattern[:-2]
1766 return negate, lambda txt: txt[:len(pattern)] == pattern
1768 elif is_regex(pattern):
1769 # Non specific regex. Use real prefix regex matching
1770 return negate, lambda txt: regex(pattern).match(txt) is not None
1772 # prefix match without any regex chars
1773 return negate, lambda txt: txt[:len(pattern)] == pattern
1776 def _convert_pattern_list(patterns):
1777 return tuple([_convert_pattern(p) for p in patterns])
1780 # Slow variant of checking wether a service is matched by a list
1781 # of regexes - used e.g. by cmk --notify
1782 def in_extraconf_servicelist(servicelist, service):
1783 return _in_servicematcher_list(_convert_pattern_list(servicelist), service)
1786 def _in_servicematcher_list(service_matchers, item):
1787 for negate, func in service_matchers:
1788 result = func(item)
1789 if result:
1790 return not negate
1792 # no match in list -> negative answer
1793 return False
1797 # .--Constants-----------------------------------------------------------.
1798 # | ____ _ _ |
1799 # | / ___|___ _ __ ___| |_ __ _ _ __ | |_ ___ |
1800 # | | | / _ \| '_ \/ __| __/ _` | '_ \| __/ __| |
1801 # | | |__| (_) | | | \__ \ || (_| | | | | |_\__ \ |
1802 # | \____\___/|_| |_|___/\__\__,_|_| |_|\__|___/ |
1803 # | |
1804 # +----------------------------------------------------------------------+
1805 # | Some constants to be used in the configuration and at other places |
1806 # '----------------------------------------------------------------------'
1808 # Conveniance macros for host and service rules
1809 PHYSICAL_HOSTS = ['@physical'] # all hosts but not clusters
1810 CLUSTER_HOSTS = ['@cluster'] # all cluster hosts
1811 ALL_HOSTS = ['@all'] # physical and cluster hosts
1812 ALL_SERVICES = [""] # optical replacement"
1813 NEGATE = '@negate' # negation in boolean lists
1815 # TODO: Cleanup access to check_info[] -> replace it by different function calls
1816 # like for example check_exists(...)
1818 # BE AWARE: sync these global data structures with
1819 # _initialize_data_structures()
1820 # TODO: Refactor this.
1822 # The checks are loaded into this dictionary. Each check
1823 _check_contexts = {} # type: Dict[str, Any]
1824 # has a separate sub-dictionary, named by the check name.
1825 # It is populated with the includes and the check itself.
1827 # The following data structures will be filled by the checks
1828 # all known checks
1829 check_info = {} # type: Dict[str, Union[Tuple[Any], Dict[str, Any]]]
1830 # library files needed by checks
1831 check_includes = {} # type: Dict[str, List[Any]]
1832 # optional functions for parameter precompilation
1833 precompile_params = {} # type: Dict[str, Callable[[str, str, Dict[str, Any]], Any]]
1834 # dictionary-configured checks declare their default level variables here
1835 check_default_levels = {} # type: Dict[str, Any]
1836 # factory settings for dictionary-configured checks
1837 factory_settings = {} # type: Dict[str, Dict[str, Any]]
1838 # variables (names) in checks/* needed for check itself
1839 check_config_variables = [] # type: List[Any]
1840 # whichs OIDs to fetch for which check (for tabular information)
1841 snmp_info = {} # type: Dict[str, Union[Tuple[Any], List[Tuple[Any]]]]
1842 # SNMP autodetection
1843 snmp_scan_functions = {} # type: Dict[str, Callable[[Callable[[str], str]], bool]]
1844 # definitions of active "legacy" checks
1845 active_check_info = {} # type: Dict[str, Dict[str, Any]]
1846 special_agent_info = {
1847 } # type: Dict[str, Callable[[Dict[str, Any], str, str], Union[str, List[str]]]]
1849 # Names of variables registered in the check files. This is used to
1850 # keep track of the variables needed by each file. Those variables are then
1851 # (if available) read from the config and applied to the checks module after
1852 # reading in the configuration of the user.
1853 _check_variables = {} # type: Dict[str, List[Any]]
1854 # keeps the default values of all the check variables
1855 _check_variable_defaults = {} # type: Dict[str, Any]
1856 _all_checks_loaded = False
1858 # workaround: set of check-groups that are to be treated as service-checks even if
1859 # the item is None
1860 service_rule_groups = set(["temperature"])
1863 # .--Loading-------------------------------------------------------------.
1864 # | _ _ _ |
1865 # | | | ___ __ _ __| (_)_ __ __ _ |
1866 # | | | / _ \ / _` |/ _` | | '_ \ / _` | |
1867 # | | |__| (_) | (_| | (_| | | | | | (_| | |
1868 # | |_____\___/ \__,_|\__,_|_|_| |_|\__, | |
1869 # | |___/ |
1870 # +----------------------------------------------------------------------+
1871 # | Loading of check plugins |
1872 # '----------------------------------------------------------------------'
1875 def load_all_checks(get_check_api_context):
1876 """Load all checks and includes"""
1877 global _all_checks_loaded
1879 _initialize_data_structures()
1880 filelist = get_plugin_paths(cmk.utils.paths.local_checks_dir, cmk.utils.paths.checks_dir)
1881 load_checks(get_check_api_context, filelist)
1883 _all_checks_loaded = True
1886 def _initialize_data_structures():
1887 """Initialize some data structures which are populated while loading the checks"""
1888 global _all_checks_loaded
1889 _all_checks_loaded = False
1891 _check_variables.clear()
1892 _check_variable_defaults.clear()
1894 _check_contexts.clear()
1895 check_info.clear()
1896 check_includes.clear()
1897 precompile_params.clear()
1898 check_default_levels.clear()
1899 factory_settings.clear()
1900 del check_config_variables[:]
1901 snmp_info.clear()
1902 snmp_scan_functions.clear()
1903 active_check_info.clear()
1904 special_agent_info.clear()
1907 def get_plugin_paths(*dirs):
1908 filelist = []
1909 for directory in dirs:
1910 filelist += _plugin_pathnames_in_directory(directory)
1911 return filelist
1914 # Now read in all checks. Note: this is done *before* reading the
1915 # configuration, because checks define variables with default
1916 # values user can override those variables in his configuration.
1917 # If a check or check.include is both found in local/ and in the
1918 # normal structure, then only the file in local/ must be read!
1919 def load_checks(get_check_api_context, filelist):
1920 cmk_global_vars = set(get_variable_names())
1922 loaded_files = set()
1924 for f in filelist:
1925 if f[0] == "." or f[-1] == "~":
1926 continue # ignore editor backup / temp files
1928 file_name = os.path.basename(f)
1929 if file_name in loaded_files:
1930 continue # skip already loaded files (e.g. from local)
1932 try:
1933 check_context = new_check_context(get_check_api_context)
1935 known_vars = check_context.keys()
1936 known_checks = check_info.keys()
1937 known_active_checks = active_check_info.keys()
1939 load_check_includes(f, check_context)
1941 load_precompiled_plugin(f, check_context)
1942 loaded_files.add(file_name)
1944 except MKTerminate:
1945 raise
1947 except Exception as e:
1948 console.error("Error in plugin file %s: %s\n", f, e)
1949 if cmk.utils.debug.enabled():
1950 raise
1951 else:
1952 continue
1954 new_checks = set(check_info.keys()).difference(known_checks)
1955 new_active_checks = set(active_check_info.keys()).difference(known_active_checks)
1957 # Now store the check context for all checks found in this file
1958 for check_plugin_name in new_checks:
1959 _check_contexts[check_plugin_name] = check_context
1961 for check_plugin_name in new_active_checks:
1962 _check_contexts[check_plugin_name] = check_context
1964 # Collect all variables that the check file did introduce compared to the
1965 # default check context
1966 new_check_vars = {}
1967 for varname in set(check_context.keys()).difference(known_vars):
1968 new_check_vars[varname] = check_context[varname]
1970 # The default_levels_variable of check_info also declares use of a global
1971 # variable. Register it here for this context.
1972 for check_plugin_name in new_checks:
1973 # The check_info is not converted yet (convert_check_info()). This means we need
1974 # to deal with old style tuple configured checks
1975 if isinstance(check_info[check_plugin_name], tuple):
1976 default_levels_varname = check_default_levels.get(check_plugin_name)
1977 else:
1978 default_levels_varname = check_info[check_plugin_name].get(
1979 "default_levels_variable")
1981 if default_levels_varname:
1982 # Add the initial configuration to the check context to have a consistent state
1983 check_context[default_levels_varname] = factory_settings.get(
1984 default_levels_varname, {})
1985 new_check_vars[default_levels_varname] = check_context[default_levels_varname]
1987 # Save check variables for e.g. after config loading that the config can
1988 # be added to the check contexts
1989 for varname, value in new_check_vars.items():
1990 # Do not allow checks to override Check_MK builtin global variables. Silently
1991 # skip them here. The variables will only be locally available to the checks.
1992 if varname in cmk_global_vars:
1993 continue
1995 if varname.startswith("_"):
1996 continue
1998 if inspect.isfunction(value) or inspect.ismodule(value):
1999 continue
2001 _check_variable_defaults[varname] = value
2003 # Keep track of which variable needs to be set to which context
2004 context_ident_list = _check_variables.setdefault(varname, [])
2005 context_ident_list += new_checks
2006 context_ident_list += new_active_checks
2008 # Now convert check_info to new format.
2009 convert_check_info()
2010 verify_checkgroup_members()
2011 initialize_check_type_caches()
2014 def all_checks_loaded():
2015 """Whether or not all(!) checks have been loaded into the current process"""
2016 return _all_checks_loaded
2019 def any_check_loaded():
2020 """Whether or not some checks have been loaded into the current process"""
2021 return bool(_check_contexts)
2024 # Constructs a new check context dictionary. It contains the whole check API.
2025 def new_check_context(get_check_api_context):
2026 # Add the data structures where the checks register with Check_MK
2027 context = {
2028 "check_info": check_info,
2029 "check_includes": check_includes,
2030 "precompile_params": precompile_params,
2031 "check_default_levels": check_default_levels,
2032 "factory_settings": factory_settings,
2033 "check_config_variables": check_config_variables,
2034 "snmp_info": snmp_info,
2035 "snmp_scan_functions": snmp_scan_functions,
2036 "active_check_info": active_check_info,
2037 "special_agent_info": special_agent_info,
2039 # NOTE: For better separation it would be better to copy the values, but
2040 # this might consume too much memory, so we simply reference them.
2041 context.update(get_check_api_context())
2042 return context
2045 # Load the definitions of the required include files for this check
2046 # Working with imports when specifying the includes would be much cleaner,
2047 # sure. But we need to deal with the current check API.
2048 def load_check_includes(check_file_path, check_context):
2049 for include_file_name in cached_includes_of_plugin(check_file_path):
2050 include_file_path = check_include_file_path(include_file_name)
2051 try:
2052 load_precompiled_plugin(include_file_path, check_context)
2053 except MKTerminate:
2054 raise
2056 except Exception as e:
2057 console.error("Error in check include file %s: %s\n", include_file_path, e)
2058 if cmk.utils.debug.enabled():
2059 raise
2060 else:
2061 continue
2064 def check_include_file_path(include_file_name):
2065 local_path = os.path.join(cmk.utils.paths.local_checks_dir, include_file_name)
2066 if os.path.exists(local_path):
2067 return local_path
2068 return os.path.join(cmk.utils.paths.checks_dir, include_file_name)
2071 def cached_includes_of_plugin(check_file_path):
2072 cache_file_path = _include_cache_file_path(check_file_path)
2073 try:
2074 return _get_cached_check_includes(check_file_path, cache_file_path)
2075 except OSError:
2076 pass # No usable cache. Terminate
2078 includes = includes_of_plugin(check_file_path)
2079 _write_check_include_cache(cache_file_path, includes)
2080 return includes
2083 def _get_cached_check_includes(check_file_path, cache_file_path):
2084 check_stat = os.stat(check_file_path)
2085 cache_stat = os.stat(cache_file_path)
2087 if check_stat.st_mtime >= cache_stat.st_mtime:
2088 raise OSError("Cache is too old")
2090 # There are no includes (just the newline at the end)
2091 if cache_stat.st_size == 1:
2092 return [] # No includes
2094 # store.save_file() creates file empty for locking (in case it does not exists).
2095 # Skip loading the file.
2096 # Note: When raising here this process will also write the file. This means it
2097 # will write it another time after it was written by the other process. This
2098 # could be optimized. Since the whole caching here is a temporary(tm) soltion,
2099 # we leave it as it is.
2100 if cache_stat.st_size == 0:
2101 raise OSError("Cache generation in progress (file is locked)")
2103 x = open(cache_file_path).read().strip()
2104 if not x:
2105 return [] # Shouldn't happen. Empty files are handled above
2106 return x.split("|")
2109 def _write_check_include_cache(cache_file_path, includes):
2110 store.makedirs(os.path.dirname(cache_file_path))
2111 store.save_file(cache_file_path, "%s\n" % "|".join(includes))
2114 def _include_cache_file_path(path):
2115 is_local = path.startswith(cmk.utils.paths.local_checks_dir)
2116 return os.path.join(cmk.utils.paths.include_cache_dir, "local" if is_local else "builtin",
2117 os.path.basename(path))
2120 # Parse the check file without executing the code to find the check include
2121 # files the check uses. The following statements are extracted:
2122 # check_info[...] = { "includes": [...] }
2123 # inv_info[...] = { "includes": [...] }
2124 # check_includes[...] = [...]
2125 def includes_of_plugin(check_file_path):
2126 include_names = OrderedDict()
2128 def _load_from_check_info(node):
2129 if not isinstance(node.value, ast.Dict):
2130 return
2132 for key, val in zip(node.value.keys, node.value.values):
2133 if key.s == "includes":
2134 if isinstance(val, ast.List):
2135 for element in val.elts:
2136 include_names[element.s] = True
2137 else:
2138 raise MKGeneralException("Includes must be a list of include file names, "
2139 "found '%s'" % type(val))
2141 def _load_from_check_includes(node):
2142 if isinstance(node.value, ast.List):
2143 for element in node.value.elts:
2144 include_names[element.s] = True
2146 tree = ast.parse(open(check_file_path).read())
2147 for child in ast.iter_child_nodes(tree):
2148 if not isinstance(child, ast.Assign):
2149 continue # We only care about top level assigns
2151 # Filter out assignments to check_info dictionary
2152 for target in child.targets:
2153 if isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name):
2154 if target.value.id in ["check_info", "inv_info"]:
2155 _load_from_check_info(child)
2156 elif target.value.id == "check_includes":
2157 _load_from_check_includes(child)
2159 return include_names.keys()
2162 def _plugin_pathnames_in_directory(path):
2163 if path and os.path.exists(path):
2164 return sorted([
2165 path + "/" + f
2166 for f in os.listdir(path)
2167 if not f.startswith(".") and not f.endswith(".include")
2169 return []
2172 def load_precompiled_plugin(path, check_context):
2173 """Loads the given check or check include plugin into the given
2174 check context.
2176 To improve loading speed the files are not read directly. The files are
2177 python byte-code compiled before in case it has not been done before. In
2178 case there is already a compiled file that is newer than the current one,
2179 then the precompiled file is loaded."""
2181 precompiled_path = _precompiled_plugin_path(path)
2183 if not _is_plugin_precompiled(path, precompiled_path):
2184 console.vverbose("Precompile %s to %s\n" % (path, precompiled_path))
2185 store.makedirs(os.path.dirname(precompiled_path))
2186 py_compile.compile(path, precompiled_path, doraise=True)
2188 exec (marshal.loads(open(precompiled_path, "rb").read()[8:]), check_context)
2191 def _is_plugin_precompiled(path, precompiled_path):
2192 if not os.path.exists(precompiled_path):
2193 return False
2195 # Check precompiled file header
2196 f = open(precompiled_path, "rb")
2198 file_magic = f.read(4)
2199 if file_magic != py_compile.MAGIC:
2200 return False
2202 try:
2203 origin_file_mtime = struct.unpack("I", f.read(4))[0]
2204 except struct.error:
2205 return False
2207 if long(os.stat(path).st_mtime) != origin_file_mtime:
2208 return False
2210 return True
2213 def _precompiled_plugin_path(path):
2214 is_local = path.startswith(cmk.utils.paths.local_checks_dir)
2215 return os.path.join(cmk.utils.paths.precompiled_checks_dir, "local" if is_local else "builtin",
2216 os.path.basename(path))
2219 def check_variable_names():
2220 return _check_variables.keys()
2223 def get_check_variable_defaults():
2224 """Returns the check variable default settings. These are the settings right
2225 after loading the checks."""
2226 return _check_variable_defaults
2229 def set_check_variables(check_variables):
2230 """Update the check related config variables in the relevant check contexts"""
2231 for varname, value in check_variables.items():
2232 for context_ident in _check_variables[varname]:
2233 _check_contexts[context_ident][varname] = value
2236 def get_check_variables():
2237 """Returns the currently effective check variable settings
2239 Since the variables are only stored in the individual check contexts and not stored
2240 in a central place, this function needs to collect the values from the check contexts.
2241 We assume a single variable has the same value in all relevant contexts, which means
2242 that it is enough to get the variable from the first context."""
2243 check_config = {}
2244 for varname, context_ident_list in _check_variables.iteritems():
2245 check_config[varname] = _check_contexts[context_ident_list[0]][varname]
2246 return check_config
2249 def get_check_context(check_plugin_name):
2250 """Returns the context dictionary of the given check plugin"""
2251 return _check_contexts[check_plugin_name]
2254 # FIXME: Clear / unset all legacy variables to prevent confusions in other code trying to
2255 # use the legacy variables which are not set by newer checks.
2256 def convert_check_info():
2257 check_info_defaults = {
2258 "check_function": None,
2259 "inventory_function": None,
2260 "parse_function": None,
2261 "group": None,
2262 "snmp_info": None,
2263 "snmp_scan_function": None,
2264 "handle_empty_info": False,
2265 "handle_real_time_checks": False,
2266 "default_levels_variable": None,
2267 "node_info": False,
2268 "extra_sections": [],
2269 "service_description": None,
2270 "has_perfdata": False,
2271 "management_board": None,
2274 for check_plugin_name, info in check_info.items():
2275 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
2277 if not isinstance(info, dict):
2278 # Convert check declaration from old style to new API
2279 check_function, descr, has_perfdata, inventory_function = info
2280 if inventory_function == check_api_utils.no_discovery_possible:
2281 inventory_function = None
2283 scan_function = snmp_scan_functions.get(check_plugin_name,
2284 snmp_scan_functions.get(section_name))
2286 check_info[check_plugin_name] = {
2287 "check_function": check_function,
2288 "service_description": descr,
2289 "has_perfdata": bool(has_perfdata),
2290 "inventory_function": inventory_function,
2291 # Insert check name as group if no group is being defined
2292 "group": check_plugin_name,
2293 "snmp_info": snmp_info.get(check_plugin_name),
2294 # Sometimes the scan function is assigned to the check_plugin_name
2295 # rather than to the base name.
2296 "snmp_scan_function": scan_function,
2297 "handle_empty_info": False,
2298 "handle_real_time_checks": False,
2299 "default_levels_variable": check_default_levels.get(check_plugin_name),
2300 "node_info": False,
2301 "parse_function": None,
2302 "extra_sections": [],
2303 "management_board": None,
2305 else:
2306 # Ensure that there are only the known keys set. Is meant to detect typos etc.
2307 for key in info.keys():
2308 if key != "includes" and key not in check_info_defaults:
2309 raise MKGeneralException(
2310 "The check '%s' declares an unexpected key '%s' in 'check_info'." %
2311 (check_plugin_name, key))
2313 # Check does already use new API. Make sure that all keys are present,
2314 # extra check-specific information into file-specific variables.
2315 for key, val in check_info_defaults.items():
2316 info.setdefault(key, val)
2318 # Include files are related to the check file (= the section_name),
2319 # not to the (sub-)check. So we keep them in check_includes.
2320 check_includes.setdefault(section_name, [])
2321 check_includes[section_name] += info.get("includes", [])
2323 # Make sure that setting for node_info of check and subcheck matches
2324 for check_plugin_name, info in check_info.iteritems():
2325 if "." in check_plugin_name:
2326 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
2327 if section_name not in check_info:
2328 if info["node_info"]:
2329 raise MKGeneralException(
2330 "Invalid check implementation: node_info for %s is "
2331 "True, but base check %s not defined" % (check_plugin_name, section_name))
2333 elif check_info[section_name]["node_info"] != info["node_info"]:
2334 raise MKGeneralException(
2335 "Invalid check implementation: node_info for %s "
2336 "and %s are different." % ((section_name, check_plugin_name)))
2338 # Now gather snmp_info and snmp_scan_function back to the
2339 # original arrays. Note: these information is tied to a "agent section",
2340 # not to a check. Several checks may use the same SNMP info and scan function.
2341 for check_plugin_name, info in check_info.iteritems():
2342 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
2343 if info["snmp_info"] and section_name not in snmp_info:
2344 snmp_info[section_name] = info["snmp_info"]
2346 if info["snmp_scan_function"] and section_name not in snmp_scan_functions:
2347 snmp_scan_functions[section_name] = info["snmp_scan_function"]
2350 # This function validates the checks which are members of checkgroups to have either
2351 # all or none an item. Mixed checkgroups lead to strange exceptions when processing
2352 # the check parameters. So it is much better to catch these errors in a central place
2353 # with a clear error message.
2354 def verify_checkgroup_members():
2355 groups = checks_by_checkgroup()
2357 for group_name, check_entries in groups.items():
2358 with_item, without_item = [], []
2359 for check_plugin_name, check_info_entry in check_entries:
2360 # Trying to detect whether or not the check has an item. But this mechanism is not
2361 # 100% reliable since Check_MK appends an item to the service_description when "%s"
2362 # is not in the checks service_description template.
2363 # Maybe we need to define a new rule which enforces the developer to use the %s in
2364 # the service_description. At least for grouped checks.
2365 if "%s" in check_info_entry["service_description"]:
2366 with_item.append(check_plugin_name)
2367 else:
2368 without_item.append(check_plugin_name)
2370 if with_item and without_item:
2371 raise MKGeneralException(
2372 "Checkgroup %s has checks with and without item! At least one of "
2373 "the checks in this group needs to be changed (With item: %s, "
2374 "Without item: %s)" % (group_name, ", ".join(with_item), ", ".join(without_item)))
2377 def checks_by_checkgroup():
2378 groups = {}
2379 for check_plugin_name, check in check_info.items():
2380 group_name = check["group"]
2381 if group_name:
2382 groups.setdefault(group_name, [])
2383 groups[group_name].append((check_plugin_name, check))
2384 return groups
2387 # These caches both only hold the base names of the checks
2388 def initialize_check_type_caches():
2389 snmp_cache = cmk_base.runtime_cache.get_set("check_type_snmp")
2390 snmp_cache.update(snmp_info.keys())
2392 tcp_cache = cmk_base.runtime_cache.get_set("check_type_tcp")
2393 for check_plugin_name in check_info:
2394 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
2395 if section_name not in snmp_cache:
2396 tcp_cache.add(section_name)
2400 # .--Helpers-------------------------------------------------------------.
2401 # | _ _ _ |
2402 # | | | | | ___| |_ __ ___ _ __ ___ |
2403 # | | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
2404 # | | _ | __/ | |_) | __/ | \__ \ |
2405 # | |_| |_|\___|_| .__/ \___|_| |___/ |
2406 # | |_| |
2407 # +----------------------------------------------------------------------+
2408 # | Misc check related helper functions |
2409 # '----------------------------------------------------------------------'
2412 def discoverable_tcp_checks():
2413 types = []
2414 for check_plugin_name, check in check_info.items():
2415 if cmk_base.check_utils.is_tcp_check(check_plugin_name) and check["inventory_function"]:
2416 types.append(check_plugin_name)
2417 return sorted(types)
2420 def discoverable_snmp_checks():
2421 types = []
2422 for check_plugin_name, check in check_info.items():
2423 if cmk_base.check_utils.is_snmp_check(check_plugin_name) and check["inventory_function"]:
2424 types.append(check_plugin_name)
2425 return sorted(types)
2428 # Compute parameters for a check honoring factory settings,
2429 # default settings of user in main.mk, check_parameters[] and
2430 # the values code in autochecks (given as parameter params)
2431 def compute_check_parameters(host, checktype, item, params):
2432 if checktype not in check_info: # handle vanished checktype
2433 return None
2435 params = _update_with_default_check_parameters(checktype, params)
2436 params = _update_with_configured_check_parameters(host, checktype, item, params)
2438 return params
2441 def _update_with_default_check_parameters(checktype, params):
2442 # Handle dictionary based checks
2443 def_levels_varname = check_info[checktype].get("default_levels_variable")
2445 # Handle case where parameter is None but the type of the
2446 # default value is a dictionary. This is for example the
2447 # case if a check type has gotten parameters in a new version
2448 # but inventory of the old version left None as a parameter.
2449 # Also from now on we support that the inventory simply puts
2450 # None as a parameter. We convert that to an empty dictionary
2451 # that will be updated with the factory settings and default
2452 # levels, if possible.
2453 if params is None and def_levels_varname:
2454 fs = factory_settings.get(def_levels_varname)
2455 if isinstance(fs, dict):
2456 params = {}
2458 # Honor factory settings for dict-type checks. Merge
2459 # dict type checks with multiple matching rules
2460 if isinstance(params, dict):
2462 # Start with factory settings
2463 if def_levels_varname:
2464 new_params = factory_settings.get(def_levels_varname, {}).copy()
2465 else:
2466 new_params = {}
2468 # Merge user's default settings onto it
2469 check_context = _check_contexts[checktype]
2470 if def_levels_varname and def_levels_varname in check_context:
2471 def_levels = check_context[def_levels_varname]
2472 if isinstance(def_levels, dict):
2473 new_params.update(def_levels)
2475 # Merge params from inventory onto it
2476 new_params.update(params)
2477 params = new_params
2479 return params
2482 def _update_with_configured_check_parameters(host, checktype, item, params):
2483 descr = service_description(host, checktype, item)
2485 # Get parameters configured via checkgroup_parameters
2486 entries = _get_checkgroup_parameters(host, checktype, item)
2488 # Get parameters configured via check_parameters
2489 entries += service_extra_conf(host, descr, check_parameters)
2491 if entries:
2492 if _has_timespecific_params(entries):
2493 # some parameters include timespecific settings
2494 # these will be executed just before the check execution
2495 return TimespecificParamList(entries)
2497 # loop from last to first (first must have precedence)
2498 for entry in entries[::-1]:
2499 if isinstance(params, dict) and isinstance(entry, dict):
2500 params.update(entry)
2501 else:
2502 if isinstance(entry, dict):
2503 # The entry still has the reference from the rule..
2504 # If we don't make a deepcopy the rule might be modified by
2505 # a followup params.update(...)
2506 entry = copy.deepcopy(entry)
2507 params = entry
2508 return params
2511 def _has_timespecific_params(entries):
2512 for entry in entries:
2513 if isinstance(entry, dict) and "tp_default_value" in entry:
2514 return True
2515 return False
2518 def _get_checkgroup_parameters(host, checktype, item):
2519 checkgroup = check_info[checktype]["group"]
2520 if not checkgroup:
2521 return []
2522 rules = checkgroup_parameters.get(checkgroup)
2523 if rules is None:
2524 return []
2526 try:
2527 # checks without an item
2528 if item is None and checkgroup not in service_rule_groups:
2529 return host_extra_conf(host, rules)
2531 # checks with an item need service-specific rules
2532 return service_extra_conf(host, item, rules)
2533 except MKGeneralException as e:
2534 raise MKGeneralException(str(e) + " (on host %s, checktype %s)" % (host, checktype))
2537 def do_status_data_inventory_for(hostname):
2538 rules = active_checks.get('cmk_inv')
2539 if rules is None:
2540 return False
2542 # 'host_extra_conf' is already cached thus we can
2543 # use it after every check cycle.
2544 entries = get_config_cache().host_extra_conf(hostname, rules)
2546 if not entries:
2547 return False # No matching rule -> disable
2549 # Convert legacy rules to current dict format (just like the valuespec)
2550 params = {} if entries[0] is None else entries[0]
2552 return params.get('status_data_inventory', False)
2555 def do_host_label_discovery_for(hostname):
2556 rules = active_checks.get('cmk_inv')
2557 if rules is None:
2558 return True
2560 entries = get_config_cache().host_extra_conf(hostname, rules)
2562 if not entries:
2563 return True # No matching rule -> disable
2565 # Convert legacy rules to current dict format (just like the valuespec)
2566 params = {} if entries[0] is None else entries[0]
2568 return params.get("host_label_inventory", True)
2571 def filter_by_management_board(hostname,
2572 found_check_plugin_names,
2573 for_mgmt_board,
2574 for_discovery=False,
2575 for_inventory=False):
2577 In order to decide which check is used for which data source
2578 we have to filter the found check plugins. This is done via
2579 the check_info key "management_board". There are three values
2580 with the following meanings:
2581 - MGMT_ONLY
2582 These check plugins
2583 - are only used for management board data sources,
2584 - have the prefix 'mgmt_' in their name,
2585 - have the prefix 'Management Interface:' in their service description.
2586 - If there is an equivalent host check plugin then it must be 'HOST_ONLY'.
2588 - HOST_PRECEDENCE
2589 - Default value for all check plugins.
2590 - It does not have to be declared in the check_info.
2591 - Special situation for SNMP management boards:
2592 - If a host is not a SNMP host these checks are used for
2593 the SNMP management boards.
2594 - If a host is a SNMP host these checks are used for
2595 the host itself.
2597 - HOST_ONLY
2598 These check plugins
2599 - are used for 'real' host data sources, not for host management board data sources
2600 - there is an equivalent 'MGMT_ONLY'-management board check plugin.
2603 mgmt_only, host_precedence_snmp, host_only_snmp, host_precedence_tcp, host_only_tcp =\
2604 _get_categorized_check_plugins(found_check_plugin_names, for_inventory=for_inventory)
2606 config_cache = get_config_cache()
2607 host_config = config_cache.get_host_config(hostname)
2609 final_collection = set()
2610 if not host_config.has_management_board:
2611 if host_config.is_snmp_host:
2612 final_collection.update(host_precedence_snmp)
2613 final_collection.update(host_only_snmp)
2614 if host_config.is_agent_host:
2615 final_collection.update(host_precedence_tcp)
2616 final_collection.update(host_only_tcp)
2617 return final_collection
2619 if for_mgmt_board:
2620 final_collection.update(mgmt_only)
2621 if not host_config.is_snmp_host:
2622 final_collection.update(host_precedence_snmp)
2623 if not for_discovery:
2624 # Migration from 1.4 to 1.5:
2625 # in 1.4 TCP hosts with SNMP management boards discovered TCP and
2626 # SNMP checks, eg. uptime and snmp_uptime. During checking phase
2627 # these checks should be executed
2628 # further on.
2629 # In versions >= 1.5 there are management board specific check
2630 # plugins, eg. mgmt_snmp_uptime.
2631 # After a re-discovery Check_MK finds the uptime check plugin for
2632 # the TCP host and the mgmt_snmp_uptime check for the SNMP
2633 # management board. Moreover Check_MK eliminates 'HOST_ONLT'
2634 # checks like snmp_uptime.
2635 final_collection.update(host_only_snmp)
2637 else:
2638 if host_config.is_snmp_host:
2639 final_collection.update(host_precedence_snmp)
2640 final_collection.update(host_only_snmp)
2641 if host_config.is_agent_host:
2642 final_collection.update(host_precedence_tcp)
2643 final_collection.update(host_only_tcp)
2645 return final_collection
2648 def _get_categorized_check_plugins(check_plugin_names, for_inventory=False):
2649 if for_inventory:
2650 is_snmp_check_f = cmk_base.inventory_plugins.is_snmp_plugin
2651 plugins_info = cmk_base.inventory_plugins.inv_info
2652 else:
2653 is_snmp_check_f = cmk_base.check_utils.is_snmp_check
2654 plugins_info = check_info
2656 mgmt_only = set()
2657 host_precedence_snmp = set()
2658 host_precedence_tcp = set()
2659 host_only_snmp = set()
2660 host_only_tcp = set()
2662 for check_plugin_name in check_plugin_names:
2663 if check_plugin_name not in plugins_info:
2664 msg = "Unknown plugin file %s" % check_plugin_name
2665 if cmk.utils.debug.enabled():
2666 raise MKGeneralException(msg)
2667 else:
2668 console.verbose("%s\n" % msg)
2669 continue
2671 is_snmp_check_ = is_snmp_check_f(check_plugin_name)
2672 mgmt_board = _get_management_board_precedence(check_plugin_name, plugins_info)
2673 if mgmt_board == check_api_utils.HOST_PRECEDENCE:
2674 if is_snmp_check_:
2675 host_precedence_snmp.add(check_plugin_name)
2676 else:
2677 host_precedence_tcp.add(check_plugin_name)
2679 elif mgmt_board == check_api_utils.MGMT_ONLY:
2680 mgmt_only.add(check_plugin_name)
2682 elif mgmt_board == check_api_utils.HOST_ONLY:
2683 if is_snmp_check_:
2684 host_only_snmp.add(check_plugin_name)
2685 else:
2686 host_only_tcp.add(check_plugin_name)
2688 return mgmt_only, host_precedence_snmp, host_only_snmp,\
2689 host_precedence_tcp, host_only_tcp
2692 def _get_management_board_precedence(check_plugin_name, plugins_info):
2693 mgmt_board = plugins_info[check_plugin_name].get("management_board")
2694 if mgmt_board is None:
2695 return check_api_utils.HOST_PRECEDENCE
2696 return mgmt_board
2699 cmk_base.cleanup.register_cleanup(check_api_utils.reset_hostname)
2702 # .--Host Configuration--------------------------------------------------.
2703 # | _ _ _ |
2704 # | | | | | ___ ___| |_ |
2705 # | | |_| |/ _ \/ __| __| |
2706 # | | _ | (_) \__ \ |_ |
2707 # | |_| |_|\___/|___/\__| |
2708 # | |
2709 # | ____ __ _ _ _ |
2710 # | / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ __ |
2711 # | | | / _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ |
2712 # | | |__| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | | |
2713 # | \____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| |
2714 # | |___/ |
2715 # +----------------------------------------------------------------------+
2718 class HostConfig(object):
2719 def __init__(self, config_cache, hostname):
2720 # type: (ConfigCache, str) -> None
2721 super(HostConfig, self).__init__()
2722 self.hostname = hostname
2724 self._config_cache = config_cache
2726 self.is_cluster = is_cluster(hostname)
2727 self.part_of_clusters = self._config_cache.clusters_of(hostname)
2729 # TODO: Rename self.tags to self.tag_list and self.tag_groups to self.tags
2730 self.tags = self._config_cache.tag_list_of_host(self.hostname)
2731 self.tag_groups = host_tags.get(hostname, {})
2732 self.labels = self._get_host_labels()
2733 self.label_sources = self._get_host_label_sources()
2735 # Basic types
2736 self.is_tcp_host = self._config_cache.in_binary_hostlist(hostname, tcp_hosts)
2737 self.is_snmp_host = self._config_cache.in_binary_hostlist(hostname, snmp_hosts)
2738 self.is_usewalk_host = self._config_cache.in_binary_hostlist(hostname, usewalk_hosts)
2740 if "piggyback" in self.tags:
2741 self.is_piggyback_host = True
2742 elif "no-piggyback" in self.tags:
2743 self.is_piggyback_host = False
2744 else: # Legacy automatic detection
2745 self.is_piggyback_host = self.has_piggyback_data
2747 # Agent types
2748 self.is_agent_host = self.is_tcp_host or self.is_piggyback_host
2749 self.management_protocol = management_protocol.get(hostname)
2750 self.has_management_board = self.management_protocol is not None
2752 self.is_ping_host = not self.is_snmp_host and\
2753 not self.is_agent_host and\
2754 not self.has_management_board
2756 self.is_dual_host = self.is_tcp_host and self.is_snmp_host
2757 self.is_all_agents_host = "all-agents" in self.tags
2758 self.is_all_special_agents_host = "special-agents" in self.tags
2760 # IP addresses
2761 # Whether or not the given host is configured not to be monitored via IP
2762 self.is_no_ip_host = "no-ip" in self.tags
2763 self.is_ipv6_host = "ip-v6" in self.tags
2764 # Whether or not the given host is configured to be monitored via IPv4.
2765 # This is the case when it is set to be explicit IPv4 or implicit (when
2766 # host is not an IPv6 host and not a "No IP" host)
2767 self.is_ipv4_host = "ip-v4" in self.tags or (not self.is_ipv6_host and
2768 not self.is_no_ip_host)
2770 self.is_ipv4v6_host = "ip-v6" in self.tags and "ip-v4" in self.tags
2772 # Whether or not the given host is configured to be monitored primarily via IPv6
2773 self.is_ipv6_primary = (not self.is_ipv4v6_host and self.is_ipv6_host) \
2774 or (self.is_ipv4v6_host and self._primary_ip_address_family_of() == "ipv6")
2776 @property
2777 def has_piggyback_data(self):
2778 if piggyback.has_piggyback_raw_data(piggyback_max_cachefile_age, self.hostname):
2779 return True
2781 from cmk_base.data_sources.piggyback import PiggyBackDataSource
2782 return PiggyBackDataSource(self.hostname, None).has_persisted_agent_sections()
2784 def _primary_ip_address_family_of(self):
2785 rules = self._config_cache.host_extra_conf(self.hostname, primary_address_family)
2786 if rules:
2787 return rules[0]
2788 return "ipv4"
2790 def _get_host_labels(self):
2791 """Returns the effective set of host labels from all available sources
2793 1. Discovered labels
2794 2. Ruleset "Host labels"
2795 3. Explicit labels (via host/folder config)
2797 Last one wins.
2799 labels = {}
2800 labels.update(self._discovered_labels_of_host())
2801 labels.update(self._config_cache.host_extra_conf_merged(self.hostname, host_label_rules))
2802 labels.update(host_labels.get(self.hostname, {}))
2803 return labels
2805 def _get_host_label_sources(self):
2806 """Returns the effective set of host label keys with their source identifier instead of the value
2807 Order and merging logic is equal to _get_host_labels()"""
2808 labels = {}
2809 labels.update({k: "discovered" for k in self._discovered_labels_of_host().keys()})
2810 labels.update({k : "ruleset" \
2811 for k in self._config_cache.host_extra_conf_merged(self.hostname, host_label_rules)})
2812 labels.update({k: "explicit" for k in host_labels.get(self.hostname, {}).keys()})
2813 return labels
2815 def _discovered_labels_of_host(self):
2816 # type: () -> Dict
2817 return DiscoveredHostLabelsStore(self.hostname).load()
2821 # .--Configuration Cache-------------------------------------------------.
2822 # | ____ __ _ _ _ |
2823 # | / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ __ |
2824 # | | | / _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ |
2825 # | | |__| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | | |
2826 # | \____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| |
2827 # | |___/ |
2828 # | ____ _ |
2829 # | / ___|__ _ ___| |__ ___ |
2830 # | | | / _` |/ __| '_ \ / _ \ |
2831 # | | |__| (_| | (__| | | | __/ |
2832 # | \____\__,_|\___|_| |_|\___| |
2833 # | |
2834 # +----------------------------------------------------------------------+
2837 class ConfigCache(object):
2838 def __init__(self):
2839 super(ConfigCache, self).__init__()
2840 self._initialize_caches()
2842 def initialize(self):
2843 self._initialize_caches()
2844 self._collect_hosttags()
2845 self._setup_clusters_nodes_cache()
2847 self._all_processed_hosts = all_active_hosts()
2848 self._all_configured_hosts = all_configured_hosts()
2849 self._initialize_host_lookup()
2851 def _initialize_caches(self):
2852 self.single_host_checks = cmk_base.config_cache.get_dict("single_host_checks")
2853 self.multi_host_checks = cmk_base.config_cache.get_list("multi_host_checks")
2854 self.check_table_cache = cmk_base.config_cache.get_dict("check_tables")
2856 self._cache_is_snmp_check = cmk_base.runtime_cache.get_dict("is_snmp_check")
2857 self._cache_is_tcp_check = cmk_base.runtime_cache.get_dict("is_tcp_check")
2858 self._cache_section_name_of = {}
2860 # Host lookup
2862 # Contains all hostnames which are currently relevant for this cache
2863 # Most of the time all_processed hosts is similar to all_active_hosts
2864 # Howewer, in a multiprocessing environment all_processed_hosts only
2865 # may contain a reduced set of hosts, since each process handles a subset
2866 self._all_processed_hosts = set()
2867 self._all_configured_hosts = set()
2869 # Reference hostname -> dirname including /
2870 self._host_paths = {}
2871 # Reference dirname -> hosts in this dir including subfolders
2872 self._folder_host_lookup = {}
2873 # All used folders used for various set intersection operations
2874 self._folder_path_set = set()
2876 # Host tags
2877 self._hosttags = {}
2878 self._hosttags_without_folder = {}
2880 # Reference hosttags_without_folder -> list of hosts
2881 # Provides a list of hosts with the same hosttags, excluding the folder
2882 self._hosts_grouped_by_tags = {}
2883 # Reference hostname -> tag group reference
2884 self._host_grouped_ref = {}
2886 # Autochecks cache
2887 self._autochecks_cache = {}
2889 # Cache for all_matching_host
2890 self._all_matching_hosts_match_cache = {}
2892 # Caches for host_extra_conf
2893 self._host_extra_conf_ruleset_cache = {}
2894 self._host_extra_conf_match_cache = {}
2896 # Caches for service_extra_conf
2897 self._service_extra_conf_ruleset_cache = {}
2898 self._service_extra_conf_host_matched_ruleset_cache = {}
2899 self._service_extra_conf_match_cache = {}
2901 # Caches for in_boolean_serviceconf_list
2902 self._in_boolean_service_conf_list_ruleset_cache = {}
2903 self._in_boolean_service_conf_list_match_cache = {}
2905 # Cache for in_binary_hostlist
2906 self._in_binary_hostlist_cache = {}
2908 # Caches for nodes and clusters
2909 self._clusters_of_cache = {}
2910 self._nodes_of_cache = {}
2912 # A factor which indicates how much hosts share the same host tag configuration (excluding folders).
2913 # len(all_processed_hosts) / len(different tag combinations)
2914 # It is used to determine the best rule evualation method
2915 self._all_processed_hosts_similarity = 1
2917 # Keep HostConfig instances created with the current configuration cache
2918 self._host_configs = {}
2920 def get_host_config(self, hostname):
2921 """Returns a HostConfig instance for the given host
2923 It lazy initializes the host config object and caches the objects during the livetime
2924 of the ConfigCache."""
2925 host_config = self._host_configs.get(hostname)
2926 if host_config:
2927 return host_config
2929 host_config = self._host_configs[hostname] = HostConfig(self, hostname)
2930 return host_config
2932 def _collect_hosttags(self):
2933 for tagged_host in all_hosts + clusters.keys():
2934 parts = tagged_host.split("|")
2935 self._hosttags[parts[0]] = set(parts[1:])
2937 # TODO: check all call sites and remove this
2938 def tag_list_of_host(self, hostname):
2939 """Returns the list of all configured tags of a host. In case
2940 a host has no tags configured or is not known, it returns an
2941 empty list."""
2942 return self._hosttags.get(hostname, [])
2944 def tags_of_host(self, hostname):
2945 """Returns the dict of all configured tag groups and values of a host"""
2946 return host_tags.get(hostname, {})
2948 def tags_of_service(self, hostname, svc_desc):
2949 """Returns the dict of all configured tags of a service
2950 It takes all explicitly configured tag groups into account.
2952 tags = {}
2953 for entry in self.service_extra_conf(hostname, svc_desc, service_tag_rules):
2954 tags.update(entry)
2955 return tags
2957 def labels_of_service(self, hostname, svc_desc):
2958 """Returns the effective set of service labels from all available sources
2960 1. Discovered labels
2961 2. Ruleset "Service labels"
2963 Last one wins.
2965 labels = {}
2966 labels.update(self.service_extra_conf_merged(hostname, svc_desc, service_label_rules))
2967 return labels
2969 def label_sources_of_service(self, hostname, svc_desc):
2970 """Returns the effective set of service label keys with their source identifier instead of the value
2971 Order and merging logic is equal to labels_of_service()"""
2972 labels = {}
2973 labels.update({
2974 k: "ruleset"
2975 for k in self.service_extra_conf_merged(hostname, svc_desc, service_label_rules)
2977 return labels
2979 def set_all_processed_hosts(self, all_processed_hosts):
2980 self._all_processed_hosts = set(all_processed_hosts)
2982 nodes_and_clusters = set()
2983 for hostname in self._all_processed_hosts:
2984 nodes_and_clusters.update(self._nodes_of_cache.get(hostname, []))
2985 nodes_and_clusters.update(self._clusters_of_cache.get(hostname, []))
2986 self._all_processed_hosts.update(nodes_and_clusters)
2988 # The folder host lookup includes a list of all -processed- hosts within a given
2989 # folder. Any update with set_all_processed hosts invalidates this cache, because
2990 # the scope of relevant hosts has changed. This is -good-, since the values in this
2991 # lookup are iterated one by one later on in all_matching_hosts
2992 self._folder_host_lookup = {}
2994 self._adjust_processed_hosts_similarity()
2996 def _adjust_processed_hosts_similarity(self):
2997 """ This function computes the tag similarities between of the processed hosts
2998 The result is a similarity factor, which helps finding the most perfomant operation
2999 for the current hostset """
3000 used_groups = set()
3001 for hostname in self._all_processed_hosts:
3002 used_groups.add(self._host_grouped_ref[hostname])
3003 self._all_processed_hosts_similarity = (
3004 1.0 * len(self._all_processed_hosts) / len(used_groups))
3006 def _initialize_host_lookup(self):
3007 for hostname in self._all_configured_hosts:
3008 dirname_of_host = os.path.dirname(host_paths[hostname])
3009 if dirname_of_host[-1] != "/":
3010 dirname_of_host += "/"
3011 self._host_paths[hostname] = dirname_of_host
3013 # Determine hosts within folders
3014 dirnames = [
3015 x[0][len(cmk.utils.paths.check_mk_config_dir):] + "/+"
3016 for x in os.walk(cmk.utils.paths.check_mk_config_dir)
3018 self._folder_path_set = set(dirnames)
3020 # Determine hosttags without folder tag
3021 for hostname in self._all_configured_hosts:
3022 tags_without_folder = set(self._hosttags[hostname])
3023 try:
3024 tags_without_folder.remove(self._host_paths[hostname])
3025 except KeyError:
3026 pass
3028 self._hosttags_without_folder[hostname] = tags_without_folder
3030 # Determine hosts with same tag setup (ignoring folder tag)
3031 for hostname in self._all_configured_hosts:
3032 group_ref = tuple(sorted(self._hosttags_without_folder[hostname]))
3033 self._hosts_grouped_by_tags.setdefault(group_ref, set()).add(hostname)
3034 self._host_grouped_ref[hostname] = group_ref
3036 def get_hosts_within_folder(self, folder_path, with_foreign_hosts):
3037 cache_id = with_foreign_hosts, folder_path
3038 if cache_id not in self._folder_host_lookup:
3039 hosts_in_folder = set()
3040 # Strip off "+"
3041 folder_path_tmp = folder_path[:-1]
3042 relevant_hosts = self._all_configured_hosts if with_foreign_hosts else self._all_processed_hosts
3043 for hostname in relevant_hosts:
3044 if self._host_paths[hostname].startswith(folder_path_tmp):
3045 hosts_in_folder.add(hostname)
3046 self._folder_host_lookup[cache_id] = hosts_in_folder
3047 return hosts_in_folder
3048 return self._folder_host_lookup[cache_id]
3050 def get_autochecks_of(self, hostname):
3051 try:
3052 return self._autochecks_cache[hostname]
3053 except KeyError:
3054 result = cmk_base.autochecks.read_autochecks_of(hostname)
3055 self._autochecks_cache[hostname] = result
3056 return result
3058 def section_name_of(self, section):
3059 try:
3060 return self._cache_section_name_of[section]
3061 except KeyError:
3062 section_name = cmk_base.check_utils.section_name_of(section)
3063 self._cache_section_name_of[section] = section_name
3064 return section_name
3066 def is_snmp_check(self, check_plugin_name):
3067 try:
3068 return self._cache_is_snmp_check[check_plugin_name]
3069 except KeyError:
3070 snmp_checks = cmk_base.runtime_cache.get_set("check_type_snmp")
3071 result = self.section_name_of(check_plugin_name) in snmp_checks
3072 self._cache_is_snmp_check[check_plugin_name] = result
3073 return result
3075 def is_tcp_check(self, check_plugin_name):
3076 try:
3077 return self._cache_is_tcp_check[check_plugin_name]
3078 except KeyError:
3079 tcp_checks = cmk_base.runtime_cache.get_set("check_type_tcp")
3080 result = self.section_name_of(check_plugin_name) in tcp_checks
3081 self._cache_is_tcp_check[check_plugin_name] = result
3082 return result
3084 def filter_hosts_with_same_tags_as_host(self, hostname, hosts):
3085 return self._hosts_grouped_by_tags[self._host_grouped_ref[hostname]].intersection(hosts)
3087 def all_matching_hosts(self, tags, hostlist, with_foreign_hosts):
3088 """Returns a set containing the names of hosts that match the given
3089 tags and hostlist conditions."""
3090 cache_id = tuple(tags), tuple(hostlist), with_foreign_hosts
3092 try:
3093 return self._all_matching_hosts_match_cache[cache_id]
3094 except KeyError:
3095 pass
3097 if with_foreign_hosts:
3098 valid_hosts = self._all_configured_hosts
3099 else:
3100 valid_hosts = self._all_processed_hosts
3102 tags_set = set(tags)
3103 tags_set_without_folder = tags_set
3104 rule_path_set = tags_set.intersection(self._folder_path_set)
3105 tags_set_without_folder = tags_set - rule_path_set
3107 if rule_path_set:
3108 # More than one dynamic folder in one rule is simply wrong..
3109 rule_path = list(rule_path_set)[0]
3110 else:
3111 rule_path = "/+"
3113 # Thin out the valid hosts further. If the rule is located in a folder
3114 # we only need the intersection of the folders hosts and the previously determined valid_hosts
3115 valid_hosts = self.get_hosts_within_folder(rule_path,
3116 with_foreign_hosts).intersection(valid_hosts)
3118 # Contains matched hosts
3120 if tags_set_without_folder and hostlist == ALL_HOSTS:
3121 return self._match_hosts_by_tags(cache_id, valid_hosts, tags_set_without_folder)
3123 matching = set([])
3124 only_specific_hosts = not bool([x for x in hostlist if x[0] in ["@", "!", "~"]])
3126 # If no tags are specified and there are only specific hosts we already have the matches
3127 if not tags_set_without_folder and only_specific_hosts:
3128 matching = valid_hosts.intersection(hostlist)
3129 # If no tags are specified and the hostlist only include @all (all hosts)
3130 elif not tags_set_without_folder and hostlist == ALL_HOSTS:
3131 matching = valid_hosts
3132 else:
3133 # If the rule has only exact host restrictions, we can thin out the list of hosts to check
3134 if only_specific_hosts:
3135 hosts_to_check = valid_hosts.intersection(set(hostlist))
3136 else:
3137 hosts_to_check = valid_hosts
3139 for hostname in hosts_to_check:
3140 # When no tag matching is requested, do not filter by tags. Accept all hosts
3141 # and filter only by hostlist
3142 if (not tags or
3143 hosttags_match_taglist(self._hosttags[hostname], tags_set_without_folder)):
3144 if in_extraconf_hostlist(hostlist, hostname):
3145 matching.add(hostname)
3147 self._all_matching_hosts_match_cache[cache_id] = matching
3148 return matching
3150 def _match_hosts_by_tags(self, cache_id, valid_hosts, tags_set_without_folder):
3151 matching = set([])
3152 has_specific_folder_tag = sum([x[0] == "/" for x in tags_set_without_folder])
3153 negative_match_tags = set()
3154 positive_match_tags = set()
3155 for tag in tags_set_without_folder:
3156 if tag[0] == "!":
3157 negative_match_tags.add(tag[1:])
3158 else:
3159 positive_match_tags.add(tag)
3161 if has_specific_folder_tag or self._all_processed_hosts_similarity < 3:
3162 # Without shared folders
3163 for hostname in valid_hosts:
3164 if not positive_match_tags - self._hosttags[hostname]:
3165 if not negative_match_tags.intersection(self._hosttags[hostname]):
3166 matching.add(hostname)
3168 self._all_matching_hosts_match_cache[cache_id] = matching
3169 return matching
3171 # With shared folders
3172 checked_hosts = set()
3173 for hostname in valid_hosts:
3174 if hostname in checked_hosts:
3175 continue
3177 hosts_with_same_tag = self.filter_hosts_with_same_tags_as_host(hostname, valid_hosts)
3178 checked_hosts.update(hosts_with_same_tag)
3180 if not positive_match_tags - self._hosttags[hostname]:
3181 if not negative_match_tags.intersection(self._hosttags[hostname]):
3182 matching.update(hosts_with_same_tag)
3184 self._all_matching_hosts_match_cache[cache_id] = matching
3185 return matching
3187 def host_extra_conf_merged(self, hostname, conf):
3188 rule_dict = {}
3189 for rule in self.host_extra_conf(hostname, conf):
3190 for key, value in rule.items():
3191 rule_dict.setdefault(key, value)
3192 return rule_dict
3194 def host_extra_conf(self, hostname, ruleset):
3195 with_foreign_hosts = hostname not in self._all_processed_hosts
3196 cache_id = id(ruleset), with_foreign_hosts
3197 try:
3198 return self._host_extra_conf_match_cache[cache_id][hostname]
3199 except KeyError:
3200 pass
3202 try:
3203 ruleset = self._host_extra_conf_ruleset_cache[cache_id]
3204 except KeyError:
3205 ruleset = self._convert_host_ruleset(ruleset, with_foreign_hosts)
3206 self._host_extra_conf_ruleset_cache[cache_id] = ruleset
3207 new_cache = {}
3208 for value, hostname_list in ruleset:
3209 for other_hostname in hostname_list:
3210 new_cache.setdefault(other_hostname, []).append(value)
3211 self._host_extra_conf_match_cache[cache_id] = new_cache
3213 if hostname not in self._host_extra_conf_match_cache[cache_id]:
3214 return []
3216 return self._host_extra_conf_match_cache[cache_id][hostname]
3218 def _convert_host_ruleset(self, ruleset, with_foreign_hosts):
3219 new_rules = []
3220 if len(ruleset) == 1 and ruleset[0] == "":
3221 console.warning('deprecated entry [ "" ] in host configuration list')
3223 for rule in ruleset:
3224 item, tags, hostlist, rule_options = parse_host_rule(rule)
3225 if rule_options.get("disabled"):
3226 continue
3228 # Directly compute set of all matching hosts here, this
3229 # will avoid recomputation later
3230 new_rules.append((item, self.all_matching_hosts(tags, hostlist, with_foreign_hosts)))
3232 return new_rules
3234 def service_extra_conf(self, hostname, service, ruleset):
3235 """Compute outcome of a service rule set that has an item."""
3236 # When the requested host is part of the local sites configuration,
3237 # then use only the sites hosts for processing the rules
3238 with_foreign_hosts = hostname not in self._all_processed_hosts
3239 cache_id = id(ruleset), with_foreign_hosts
3241 cached_ruleset = self._service_extra_conf_ruleset_cache.get(cache_id)
3242 if cached_ruleset is None:
3243 cached_ruleset = self._convert_service_ruleset(
3244 ruleset, with_foreign_hosts=with_foreign_hosts)
3245 self._service_extra_conf_ruleset_cache[cache_id] = cached_ruleset
3247 entries = []
3249 for value, hosts, service_matchers in cached_ruleset:
3250 if hostname not in hosts:
3251 continue
3253 descr_cache_id = service_matchers, service
3255 # 20% faster without exception handling
3256 # self._profile_log("descr cache id %r" % (descr_cache_id))
3257 match = self._service_extra_conf_match_cache.get(descr_cache_id)
3258 if match is None:
3259 match = _in_servicematcher_list(service_matchers, service)
3260 self._service_extra_conf_match_cache[descr_cache_id] = match
3262 if match:
3263 entries.append(value)
3265 return entries
3267 def service_extra_conf_merged(self, hostname, service, ruleset):
3268 rule_dict = {}
3269 for rule in self.service_extra_conf(hostname, service, ruleset):
3270 for key, value in rule.items():
3271 rule_dict.setdefault(key, value)
3272 return rule_dict
3274 def _convert_service_ruleset(self, ruleset, with_foreign_hosts):
3275 new_rules = []
3276 for rule in ruleset:
3277 rule, rule_options = get_rule_options(rule)
3278 if rule_options.get("disabled"):
3279 continue
3281 num_elements = len(rule)
3282 if num_elements == 3:
3283 item, hostlist, servlist = rule
3284 tags = []
3285 elif num_elements == 4:
3286 item, tags, hostlist, servlist = rule
3287 else:
3288 raise MKGeneralException("Invalid rule '%r' in service configuration "
3289 "list: must have 3 or 4 elements" % (rule,))
3291 # Directly compute set of all matching hosts here, this
3292 # will avoid recomputation later
3293 hosts = self.all_matching_hosts(tags, hostlist, with_foreign_hosts)
3295 # And now preprocess the configured patterns in the servlist
3296 new_rules.append((item, hosts, _convert_pattern_list(servlist)))
3298 return new_rules
3300 # Compute outcome of a service rule set that just say yes/no
3301 def in_boolean_serviceconf_list(self, hostname, descr, ruleset):
3302 # When the requested host is part of the local sites configuration,
3303 # then use only the sites hosts for processing the rules
3304 with_foreign_hosts = hostname not in self._all_processed_hosts
3305 cache_id = id(ruleset), with_foreign_hosts
3306 try:
3307 ruleset = self._in_boolean_service_conf_list_ruleset_cache[cache_id]
3308 except KeyError:
3309 ruleset = self._convert_boolean_service_ruleset(ruleset, with_foreign_hosts)
3310 self._in_boolean_service_conf_list_ruleset_cache[cache_id] = ruleset
3312 for negate, hosts, service_matchers in ruleset:
3313 if hostname in hosts:
3314 cache_id = service_matchers, descr
3315 try:
3316 match = self._in_boolean_service_conf_list_match_cache[cache_id]
3317 except KeyError:
3318 match = _in_servicematcher_list(service_matchers, descr)
3319 self._in_boolean_service_conf_list_match_cache[cache_id] = match
3321 if match:
3322 return not negate
3323 return False # no match. Do not ignore
3325 def _convert_boolean_service_ruleset(self, ruleset, with_foreign_hosts):
3326 new_rules = []
3327 for rule in ruleset:
3328 entry, rule_options = get_rule_options(rule)
3329 if rule_options.get("disabled"):
3330 continue
3332 if entry[0] == NEGATE: # this entry is logically negated
3333 negate = True
3334 entry = entry[1:]
3335 else:
3336 negate = False
3338 if len(entry) == 2:
3339 hostlist, servlist = entry
3340 tags = []
3341 elif len(entry) == 3:
3342 tags, hostlist, servlist = entry
3343 else:
3344 raise MKGeneralException("Invalid entry '%r' in configuration: "
3345 "must have 2 or 3 elements" % (entry,))
3347 # Directly compute set of all matching hosts here, this
3348 # will avoid recomputation later
3349 hosts = self.all_matching_hosts(tags, hostlist, with_foreign_hosts)
3350 new_rules.append((negate, hosts, _convert_pattern_list(servlist)))
3352 return new_rules
3354 def _setup_clusters_nodes_cache(self):
3355 for cluster, hosts in clusters.items():
3356 clustername = cluster.split('|', 1)[0]
3357 for name in hosts:
3358 self._clusters_of_cache.setdefault(name, []).append(clustername)
3359 self._nodes_of_cache[clustername] = hosts
3361 # Return a list of the cluster host names.
3362 def clusters_of(self, hostname):
3363 return self._clusters_of_cache.get(hostname, [])
3365 # TODO: cleanup none
3366 # Returns the nodes of a cluster. Returns None if no match
3367 def nodes_of(self, hostname):
3368 return self._nodes_of_cache.get(hostname)
3370 # Determine weather a service (found on a physical host) is a clustered
3371 # service and - if yes - return the cluster host of the service. If
3372 # no, returns the hostname of the physical host.
3373 def host_of_clustered_service(self, hostname, servicedesc, part_of_clusters=None):
3374 if part_of_clusters:
3375 the_clusters = part_of_clusters
3376 else:
3377 the_clusters = self.clusters_of(hostname)
3379 if not the_clusters:
3380 return hostname
3382 cluster_mapping = self.service_extra_conf(hostname, servicedesc, clustered_services_mapping)
3383 for cluster in cluster_mapping:
3384 # Check if the host is in this cluster
3385 if cluster in the_clusters:
3386 return cluster
3388 # 1. New style: explicitly assigned services
3389 for cluster, conf in clustered_services_of.iteritems():
3390 nodes = nodes_of(cluster)
3391 if not nodes:
3392 raise MKGeneralException(
3393 "Invalid entry clustered_services_of['%s']: %s is not a cluster." % (cluster,
3394 cluster))
3395 if hostname in nodes and \
3396 self.in_boolean_serviceconf_list(hostname, servicedesc, conf):
3397 return cluster
3399 # 1. Old style: clustered_services assumes that each host belong to
3400 # exactly on cluster
3401 if self.in_boolean_serviceconf_list(hostname, servicedesc, clustered_services):
3402 return the_clusters[0]
3404 return hostname
3406 def in_binary_hostlist(self, hostname, conf):
3407 cache = self._in_binary_hostlist_cache
3409 cache_id = id(conf), hostname
3410 try:
3411 return cache[cache_id]
3412 except KeyError:
3413 pass
3415 # if we have just a list of strings just take it as list of hostnames
3416 if conf and isinstance(conf[0], str):
3417 result = hostname in conf
3418 cache[cache_id] = result
3419 else:
3420 for entry in conf:
3421 actual_host_tags = self.tag_list_of_host(hostname)
3422 entry, rule_options = get_rule_options(entry)
3423 if rule_options.get("disabled"):
3424 continue
3426 try:
3427 # Negation via 'NEGATE'
3428 if entry[0] == NEGATE:
3429 entry = entry[1:]
3430 negate = True
3431 else:
3432 negate = False
3433 # entry should be one-tuple or two-tuple. Tuple's elements are
3434 # lists of strings. User might forget comma in one tuple. Then the
3435 # entry is the list itself.
3436 if isinstance(entry, list):
3437 hostlist = entry
3438 tags = []
3439 else:
3440 if len(entry) == 1: # 1-Tuple with list of hosts
3441 hostlist = entry[0]
3442 tags = []
3443 else:
3444 tags, hostlist = entry
3446 if hosttags_match_taglist(actual_host_tags, tags) and \
3447 in_extraconf_hostlist(hostlist, hostname):
3448 cache[cache_id] = not negate
3449 break
3450 except:
3451 # TODO: Fix this too generic catching (+ bad error message)
3452 raise MKGeneralException("Invalid entry '%r' in host configuration list: "
3453 "must be tuple with 1 or 2 entries" % (entry,))
3454 else:
3455 cache[cache_id] = False
3457 return cache[cache_id]
3460 def get_config_cache():
3461 config_cache = cmk_base.config_cache.get_dict("config_cache")
3462 if not config_cache:
3463 config_cache["cache"] = ConfigCache()
3464 return config_cache["cache"]