2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
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
37 from typing
import Any
, Callable
, Dict
, List
, Tuple
, Union
, Optional
# pylint: disable=unused-import
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
47 from cmk
.utils
.exceptions
import MKGeneralException
, MKTerminate
50 import cmk_base
.console
as console
51 import cmk_base
.default_config
as default_config
52 import cmk_base
.check_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):
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"""
82 for key
in get_variable_names():
83 value
= getattr(default_config
, key
)
85 if isinstance(value
, (dict, list)):
86 value
= copy
.deepcopy(value
)
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
:
110 delattr(default_config
, varname
)
111 except AttributeError:
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()
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 # | |_| \_\___|\__,_|\__,_| \____\___/|_| |_|_| |_|\__, | |
142 # +----------------------------------------------------------------------+
143 # | Code for reading the configuration files. |
144 # '----------------------------------------------------------------------'
147 def load(with_conf_d
=True, validate_hosts
=True, exclude_parents_mk
=False):
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()
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
):
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"):
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:]
225 "FOLDER_PATH": os
.path
.dirname(_file_path
),
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():
241 elif sys
.stderr
.isatty():
242 console
.error("Cannot read in configuration file %s: %s\n", _f
, e
)
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():
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
):
268 list_of_files
= sorted(
269 reduce(lambda a
, b
: a
+ b
,
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
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
)
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()
303 # TODO: Raise an exception
304 console
.error("Error in configuration: duplicate hosts: %s\n", ", ".join(duplicates
))
308 # Add WATO-configured explicit checks to (possibly empty) checks
309 # statically defined in checks.
310 def add_wato_static_checks_to_checks():
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"):
320 # Parameters are optional
321 if len(entry
[0]) == 2:
322 checktype
, item
= entry
[0]
325 checktype
, item
, params
= entry
[0]
327 taglist
, hostlist
= entry
[1:3]
332 # Do not process manual checks that are related to not existing or have not
335 check_plugin_info
= check_info
[checktype
]
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
:
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".
359 # Now prepend to checks. That makes that checks variable have precedence
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")
369 if len(entry
) == 4 and isinstance(entry
[0], str):
370 single_host_checks
.setdefault(entry
[0], []).append(entry
)
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
):
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'
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
)
400 console
.error("--> Found %d invalid variables\n" % found_invalid
)
401 console
.error("If you use own helper variables, please prefix them with _.\n")
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")
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
]:
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():
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
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",
450 "define_servicegroups",
451 "service_contactgroups",
452 "host_contactgroups",
457 "extra_service_conf",
462 super(PackedConfig
, self
).__init
__()
463 self
._path
= os
.path
.join(cmk
.utils
.paths
.var_dir
, "base", "precompiled_check_config.mk")
466 self
._write
(self
._pack
())
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
):
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
)
485 def filter_clusters(clusters_orig
):
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
493 def filter_hostname_in_dict(values
):
495 for hostname
, attributes
in values
.items():
496 if hostname
in active_hosts
:
497 values_red
[hostname
] = attributes
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
:
523 val
= global_variables
[varname
]
525 if varname
not in derived_config_variable_names
and val
== variable_defaults
[varname
]:
528 if not self
._packable
(varname
, val
):
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
]:
546 if not self
._packable
(varname
, val
):
549 helper_config
+= "\n%s = %r\n" % (varname
, val
)
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
:
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
)
578 exec (marshal
.load(open(self
._path
)), globals())
579 _perform_post_config_loading_actions()
583 # .--Host tags-----------------------------------------------------------.
585 # | | | | | ___ ___| |_ | |_ __ _ __ _ ___ |
586 # | | |_| |/ _ \/ __| __| | __/ _` |/ _` / __| |
587 # | | _ | (_) \__ \ |_ | || (_| | (_| \__ \ |
588 # | |_| |_|\___/|___/\__| \__\__,_|\__, |___/ |
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
)
600 return cache
[cache_id
]
602 result
= [h
.split('|', 1)[0] for h
in tagged_hostlist
]
603 cache
[cache_id
] = result
608 # .--HostCollections-----------------------------------------------------.
609 # | _ _ _ ____ _ _ _ _ |
610 # || | | | ___ ___| |_ / ___|___ | | | ___ ___| |_(_) ___ _ __ ___ |
611 # || |_| |/ _ \/ __| __| | / _ \| | |/ _ \/ __| __| |/ _ \| '_ \/ __| |
612 # || _ | (_) \__ \ |_| |__| (_) | | | __/ (__| |_| | (_) | | | \__ \ |
613 # ||_| |_|\___/|___/\__|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|___/ |
615 # +----------------------------------------------------------------------+
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()
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()
663 # Returns a set of all host names, regardless if currently
664 # disabled or monitored on a remote site. Does not return
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()
674 # Returns a set of all cluster names, regardless if currently
675 # disabled or monitored on a remote site. Does not return
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()
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()
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:
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
715 hostname
for hostname
in hostlist
if in_binary_hostlist(hostname
, only_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
)
728 return set(active_hosts
)
731 def duplicate_hosts():
732 seen_hostnames
= set([])
735 for hostname
in all_active_hosts_with_duplicates():
736 if hostname
in seen_hostnames
:
737 duplicates
.add(hostname
)
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---------------------------------------------------------------.
765 # | | | | | ___ ___| |_ ___ |
766 # | | |_| |/ _ \/ __| __/ __| |
767 # | | _ | (_) \__ \ |_\__ \ |
768 # | |_| |_|\___/|___/\__|___/ |
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
783 def alias_of(hostname
, fallback
):
784 aliases
= host_extra_conf(hostname
, extra_host_conf
.get("alias", []))
785 if len(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
811 if pss
in all_active_realhosts():
812 used_parents
.append(pss
)
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
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
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:
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)
880 return credentials_variable
[hostname
]
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
:
894 # Agent communication
898 def agent_port_of(hostname
):
899 ports
= host_extra_conf(hostname
, agent_ports
)
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
914 def agent_encryption_of(hostname
):
915 settings
= host_extra_conf(hostname
, agent_encryption
)
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]
929 return cmk
.__version
__
930 elif isinstance(spec
, str):
931 # Compatibility to old value specification format (a single version string)
933 elif spec
[0] == 'specific':
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
):
944 return explicit_service_custom_variables
[(hostname
, description
)]
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
):
960 return explicit_snmp_communities
[hostname
]
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
)
978 def snmp_timing_of(hostname
):
979 timing
= host_extra_conf(hostname
, snmp_timing
)
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
):
995 ports
= host_extra_conf(hostname
, snmp_ports
)
1001 def is_bulkwalk_host(hostname
):
1002 # type: (str) -> bool
1004 return in_binary_hostlist(hostname
, bulkwalk_hosts
)
1009 def bulk_walk_size_of(hostname
):
1010 bulk_sizes
= host_extra_conf(hostname
, snmp_bulk_size
)
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
)
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
):
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
1054 for entry
in host_extra_conf(hostname
, host_contactgroups
):
1055 if isinstance(entry
, list) and first_list
:
1061 if monitoring_core
== "nagios" and enable_rulebased_notifications
:
1062 cgrs
.append("check-mk-notify")
1064 return list(set(cgrs
))
1072 def exit_code_spec(hostname
, data_source_id
=None):
1074 specs
= host_extra_conf(hostname
, check_mk_exit_status
)
1075 for entry
in specs
[::-1]:
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:
1083 return spec
["individual"][data_source_id
]
1088 return spec
["overall"]
1092 # Old configuration format
1096 def check_period_of(hostname
, service
):
1097 periods
= service_extra_conf(hostname
, service
, check_periods
)
1100 if period
== "24X7":
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-------------------------------------------------------------.
1123 # | / ___| |_ _ ___| |_ ___ _ __ |
1124 # | | | | | | | / __| __/ _ \ '__| |
1125 # | | |___| | |_| \__ \ || __/ | |
1126 # | \____|_|\__,_|___/\__\___|_| |
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
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------------------------------------------------------------.
1158 # | / ___| ___ _ ____ _(_) ___ ___ ___ |
1159 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \/ __| |
1160 # | ___) | __/ | \ V /| | (_| __/\__ \ |
1161 # | |____/ \___|_| \_/ |_|\___\___||___/ |
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])
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
= {
1192 "df_netapp": "fs_%s",
1193 "df_netapp32": "fs_%s",
1194 "esx_vsphere_datastores": "fs_%s",
1196 "vms_diskstat.df": "fs_%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
:
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
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
)
1269 descr_format
= old_descr
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
,)
1286 descr
= descr_format
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
)
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
)
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")
1328 new_description
= cache
[description
]
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
:
1341 if check_plugin_name
and _checktype_ignored_for_host(hostname
, check_plugin_name
):
1344 if description
is not None \
1345 and in_boolean_serviceconf_list(hostname
, description
, ignored_services
):
1351 def _checktype_ignored_for_host(host
, checktype
):
1352 if checktype
in ignored_checktypes
:
1354 ignored
= host_extra_conf(host
, ignored_checks
)
1356 if checktype
== e
or (isinstance(e
, list) and checktype
in e
):
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"""
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"):
1376 depname
, hostlist
, patternlist
= entry
1378 elif len(entry
) == 4:
1379 depname
, tags
, hostlist
, patternlist
= entry
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
)
1390 item
= matchobject
.groups()[-1]
1391 deps
.append(depname
% item
)
1393 deps
.append(depname
)
1398 # .--Misc Helpers--------------------------------------------------------.
1400 # | | \/ (_)___ ___ | | | | ___| |_ __ ___ _ __ ___ |
1401 # | | |\/| | / __|/ __| | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
1402 # | | | | | \__ \ (__ | _ | __/ | |_) | __/ | \__ \ |
1403 # | |_| |_|_|___/\___| |_| |_|\___|_| .__/ \___|_| |___/ |
1405 # +----------------------------------------------------------------------+
1406 # | Different helper functions |
1407 # '----------------------------------------------------------------------'
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"):
1417 return s
.decode(encoding
)
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
)
1439 for rule
in rules
[::-1]:
1440 translations
.update(rule
)
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
)
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
)
1459 translations_cache
[hostname
] = 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
1472 if isinstance(command_spec
, six
.string_types
):
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:]
1491 password
= stored_passwords
[pw_ident
]["password"]
1493 if hostname
and description
:
1494 descr
= " used by service \"%s\" on host \"%s\"" % (description
, hostname
)
1496 descr
= " used by host host \"%s\"" % (hostname
)
1501 "The stored password \"%s\"%s does not exist (anymore)." % (pw_ident
, descr
))
1504 pw_start_index
= str(preformated_arg
.index("%s"))
1506 cmk_base
.utils
.quote_shell_string(preformated_arg
% ("*" * len(password
))))
1507 passwords
.append((str(len(formated
)), pw_start_index
, pw_ident
))
1510 raise MKGeneralException("Invalid argument for command line: %r" % (arg
,))
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):
1529 proxy_type
, value
= http_proxy
1531 if proxy_type
== "environment":
1534 if proxy_type
== "global":
1535 return http_proxies
.get(value
, {}).get("proxy_url", None)
1537 if proxy_type
== "url":
1540 if proxy_type
== "no_proxy":
1547 # .--Service rules-------------------------------------------------------.
1549 # | / ___| ___ _ ____ _(_) ___ ___ _ __ _ _| | ___ ___ |
1550 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \ | '__| | | | |/ _ \/ __| |
1551 # | ___) | __/ | \ V /| | (_| __/ | | | |_| | | __/\__ \ |
1552 # | |____/ \___|_| \_/ |_|\___\___| |_| \__,_|_|\___||___/ |
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-------------------------------------------------------.
1571 # | | | | | ___ ___| |_ _ __ _ _| | ___ ___ ___| |_ ___ |
1572 # | | |_| |/ _ \/ __| __| | '__| | | | |/ _ \/ __|/ _ \ __/ __| |
1573 # | | _ | (_) \__ \ |_ | | | |_| | | __/\__ \ __/ |_\__ \ |
1574 # | |_| |_|\___/|___/\__| |_| \__,_|_|\___||___/\___|\__|___/ |
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-------------------------------------------------------.
1592 # | | | | | ___ ___| |_ _ __ ___ __ _| |_ ___| |__ (_)_ __ __ _ |
1593 # | | |_| |/ _ \/ __| __| | '_ ` _ \ / _` | __/ __| '_ \| | '_ \ / _` | |
1594 # | | _ | (_) \__ \ |_ | | | | | | (_| | || (__| | | | | | | | (_| | |
1595 # | |_| |_|\___/|___/\__| |_| |_| |_|\__,_|\__\___|_| |_|_|_| |_|\__, | |
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'
1618 # Migration help: print error if old format appears in config file
1619 # FIXME: When can this be removed?
1621 if hostlist
[0] == "":
1622 raise MKGeneralException('Invalid empty entry [ "" ] in configuration')
1624 pass # Empty list, no problem.
1626 for hostentry
in hostlist
:
1628 raise MKGeneralException('Empty hostname in host list %r' % hostlist
)
1631 if hostentry
[0] == '@':
1632 if hostentry
== '@all':
1634 ic
= is_cluster(hostname
)
1635 if hostentry
== '@cluster' and ic
:
1637 elif hostentry
== '@physical' and not ic
:
1640 # Allow negation of hostentry with prefix '!'
1642 if hostentry
[0] == '!':
1643 hostentry
= hostentry
[1:]
1646 # Allow regex with prefix '~'
1647 if hostentry
[0] == '~':
1648 hostentry
= hostentry
[1:]
1652 if not use_regex
and hostname
== hostentry
:
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:
1658 except MKGeneralException
:
1659 if cmk
.utils
.debug
.enabled():
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
1676 elif num_elements
== 3:
1677 item
, tags
, hostlist
= rule
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]
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] == '+':
1708 if t
.startswith(tag
):
1713 matches
= tag
in hosttags
1715 if matches
== negate
:
1721 def _parse_negated(pattern
):
1722 # Allow negation of pattern with prefix '!'
1724 negate
= pattern
[0] == '!'
1726 pattern
= pattern
[1:]
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
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])
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
:
1792 # no match in list -> negative answer
1797 # .--Constants-----------------------------------------------------------.
1799 # | / ___|___ _ __ ___| |_ __ _ _ __ | |_ ___ |
1800 # | | | / _ \| '_ \/ __| __/ _` | '_ \| __/ __| |
1801 # | | |__| (_) | | | \__ \ || (_| | | | | |_\__ \ |
1802 # | \____\___/|_| |_|___/\__\__,_|_| |_|\__|___/ |
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
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
1860 service_rule_groups
= set(["temperature"])
1863 # .--Loading-------------------------------------------------------------.
1865 # | | | ___ __ _ __| (_)_ __ __ _ |
1866 # | | | / _ \ / _` |/ _` | | '_ \ / _` | |
1867 # | | |__| (_) | (_| | (_| | | | | | (_| | |
1868 # | |_____\___/ \__,_|\__,_|_|_| |_|\__, | |
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()
1896 check_includes
.clear()
1897 precompile_params
.clear()
1898 check_default_levels
.clear()
1899 factory_settings
.clear()
1900 del check_config_variables
[:]
1902 snmp_scan_functions
.clear()
1903 active_check_info
.clear()
1904 special_agent_info
.clear()
1907 def get_plugin_paths(*dirs
):
1909 for directory
in dirs
:
1910 filelist
+= _plugin_pathnames_in_directory(directory
)
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()
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)
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
)
1947 except Exception as e
:
1948 console
.error("Error in plugin file %s: %s\n", f
, e
)
1949 if cmk
.utils
.debug
.enabled():
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
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
)
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
:
1995 if varname
.startswith("_"):
1998 if inspect
.isfunction(value
) or inspect
.ismodule(value
):
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
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())
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
)
2052 load_precompiled_plugin(include_file_path
, check_context
)
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():
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
):
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
)
2074 return _get_cached_check_includes(check_file_path
, cache_file_path
)
2076 pass # No usable cache. Terminate
2078 includes
= includes_of_plugin(check_file_path
)
2079 _write_check_include_cache(cache_file_path
, 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()
2105 return [] # Shouldn't happen. Empty files are handled above
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
):
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
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
):
2166 for f
in os
.listdir(path
)
2167 if not f
.startswith(".") and not f
.endswith(".include")
2172 def load_precompiled_plugin(path
, check_context
):
2173 """Loads the given check or check include plugin into the given
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
):
2195 # Check precompiled file header
2196 f
= open(precompiled_path
, "rb")
2198 file_magic
= f
.read(4)
2199 if file_magic
!= py_compile
.MAGIC
:
2203 origin_file_mtime
= struct
.unpack("I", f
.read(4))[0]
2204 except struct
.error
:
2207 if long(os
.stat(path
).st_mtime
) != origin_file_mtime
:
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."""
2244 for varname
, context_ident_list
in _check_variables
.iteritems():
2245 check_config
[varname
] = _check_contexts
[context_ident_list
[0]][varname
]
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,
2263 "snmp_scan_function": None,
2264 "handle_empty_info": False,
2265 "handle_real_time_checks": False,
2266 "default_levels_variable": None,
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
),
2301 "parse_function": None,
2302 "extra_sections": [],
2303 "management_board": None,
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
)
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():
2379 for check_plugin_name
, check
in check_info
.items():
2380 group_name
= check
["group"]
2382 groups
.setdefault(group_name
, [])
2383 groups
[group_name
].append((check_plugin_name
, check
))
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-------------------------------------------------------------.
2402 # | | | | | ___| |_ __ ___ _ __ ___ |
2403 # | | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
2404 # | | _ | __/ | |_) | __/ | \__ \ |
2405 # | |_| |_|\___|_| .__/ \___|_| |___/ |
2407 # +----------------------------------------------------------------------+
2408 # | Misc check related helper functions |
2409 # '----------------------------------------------------------------------'
2412 def discoverable_tcp_checks():
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():
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
2435 params
= _update_with_default_check_parameters(checktype
, params
)
2436 params
= _update_with_configured_check_parameters(host
, checktype
, item
, 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):
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()
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
)
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
)
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
)
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
)
2511 def _has_timespecific_params(entries
):
2512 for entry
in entries
:
2513 if isinstance(entry
, dict) and "tp_default_value" in entry
:
2518 def _get_checkgroup_parameters(host
, checktype
, item
):
2519 checkgroup
= check_info
[checktype
]["group"]
2522 rules
= checkgroup_parameters
.get(checkgroup
)
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')
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
)
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')
2560 entries
= get_config_cache().host_extra_conf(hostname
, rules
)
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
,
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:
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'.
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
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
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
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
)
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):
2650 is_snmp_check_f
= cmk_base
.inventory_plugins
.is_snmp_plugin
2651 plugins_info
= cmk_base
.inventory_plugins
.inv_info
2653 is_snmp_check_f
= cmk_base
.check_utils
.is_snmp_check
2654 plugins_info
= check_info
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
)
2668 console
.verbose("%s\n" % msg
)
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
:
2675 host_precedence_snmp
.add(check_plugin_name
)
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
:
2684 host_only_snmp
.add(check_plugin_name
)
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
2699 cmk_base
.cleanup
.register_cleanup(check_api_utils
.reset_hostname
)
2702 # .--Host Configuration--------------------------------------------------.
2704 # | | | | | ___ ___| |_ |
2705 # | | |_| |/ _ \/ __| __| |
2706 # | | _ | (_) \__ \ |_ |
2707 # | |_| |_|\___/|___/\__| |
2710 # | / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ __ |
2711 # | | | / _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ |
2712 # | | |__| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | | |
2713 # | \____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| |
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
()
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
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
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")
2777 def has_piggyback_data(self
):
2778 if piggyback
.has_piggyback_raw_data(piggyback_max_cachefile_age
, self
.hostname
):
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
)
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)
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
, {}))
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()"""
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()})
2815 def _discovered_labels_of_host(self
):
2817 return DiscoveredHostLabelsStore(self
.hostname
).load()
2821 # .--Configuration Cache-------------------------------------------------.
2823 # | / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ __ |
2824 # | | | / _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ |
2825 # | | |__| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | | |
2826 # | \____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| |
2829 # | / ___|__ _ ___| |__ ___ |
2830 # | | | / _` |/ __| '_ \ / _ \ |
2831 # | | |__| (_| | (__| | | | __/ |
2832 # | \____\__,_|\___|_| |_|\___| |
2834 # +----------------------------------------------------------------------+
2837 class ConfigCache(object):
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
= {}
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()
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
= {}
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
)
2929 host_config
= self
._host
_configs
[hostname
] = HostConfig(self
, hostname
)
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
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.
2953 for entry
in self
.service_extra_conf(hostname
, svc_desc
, service_tag_rules
):
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"
2966 labels
.update(self
.service_extra_conf_merged(hostname
, svc_desc
, service_label_rules
))
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()"""
2975 for k
in self
.service_extra_conf_merged(hostname
, svc_desc
, service_label_rules
)
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 """
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
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
])
3024 tags_without_folder
.remove(self
._host
_paths
[hostname
])
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()
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
):
3052 return self
._autochecks
_cache
[hostname
]
3054 result
= cmk_base
.autochecks
.read_autochecks_of(hostname
)
3055 self
._autochecks
_cache
[hostname
] = result
3058 def section_name_of(self
, section
):
3060 return self
._cache
_section
_name
_of
[section
]
3062 section_name
= cmk_base
.check_utils
.section_name_of(section
)
3063 self
._cache
_section
_name
_of
[section
] = section_name
3066 def is_snmp_check(self
, check_plugin_name
):
3068 return self
._cache
_is
_snmp
_check
[check_plugin_name
]
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
3075 def is_tcp_check(self
, check_plugin_name
):
3077 return self
._cache
_is
_tcp
_check
[check_plugin_name
]
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
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
3093 return self
._all
_matching
_hosts
_match
_cache
[cache_id
]
3097 if with_foreign_hosts
:
3098 valid_hosts
= self
._all
_configured
_hosts
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
3108 # More than one dynamic folder in one rule is simply wrong..
3109 rule_path
= list(rule_path_set
)[0]
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
)
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
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
))
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
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
3150 def _match_hosts_by_tags(self
, cache_id
, valid_hosts
, tags_set_without_folder
):
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
:
3157 negative_match_tags
.add(tag
[1:])
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
3171 # With shared folders
3172 checked_hosts
= set()
3173 for hostname
in valid_hosts
:
3174 if hostname
in checked_hosts
:
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
3187 def host_extra_conf_merged(self
, hostname
, conf
):
3189 for rule
in self
.host_extra_conf(hostname
, conf
):
3190 for key
, value
in rule
.items():
3191 rule_dict
.setdefault(key
, value
)
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
3198 return self
._host
_extra
_conf
_match
_cache
[cache_id
][hostname
]
3203 ruleset
= self
._host
_extra
_conf
_ruleset
_cache
[cache_id
]
3205 ruleset
= self
._convert
_host
_ruleset
(ruleset
, with_foreign_hosts
)
3206 self
._host
_extra
_conf
_ruleset
_cache
[cache_id
] = ruleset
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
]:
3216 return self
._host
_extra
_conf
_match
_cache
[cache_id
][hostname
]
3218 def _convert_host_ruleset(self
, ruleset
, with_foreign_hosts
):
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"):
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
)))
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
3249 for value
, hosts
, service_matchers
in cached_ruleset
:
3250 if hostname
not in hosts
:
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
)
3259 match
= _in_servicematcher_list(service_matchers
, service
)
3260 self
._service
_extra
_conf
_match
_cache
[descr_cache_id
] = match
3263 entries
.append(value
)
3267 def service_extra_conf_merged(self
, hostname
, service
, ruleset
):
3269 for rule
in self
.service_extra_conf(hostname
, service
, ruleset
):
3270 for key
, value
in rule
.items():
3271 rule_dict
.setdefault(key
, value
)
3274 def _convert_service_ruleset(self
, ruleset
, with_foreign_hosts
):
3276 for rule
in ruleset
:
3277 rule
, rule_options
= get_rule_options(rule
)
3278 if rule_options
.get("disabled"):
3281 num_elements
= len(rule
)
3282 if num_elements
== 3:
3283 item
, hostlist
, servlist
= rule
3285 elif num_elements
== 4:
3286 item
, tags
, hostlist
, servlist
= rule
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
)))
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
3307 ruleset
= self
._in
_boolean
_service
_conf
_list
_ruleset
_cache
[cache_id
]
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
3316 match
= self
._in
_boolean
_service
_conf
_list
_match
_cache
[cache_id
]
3318 match
= _in_servicematcher_list(service_matchers
, descr
)
3319 self
._in
_boolean
_service
_conf
_list
_match
_cache
[cache_id
] = match
3323 return False # no match. Do not ignore
3325 def _convert_boolean_service_ruleset(self
, ruleset
, with_foreign_hosts
):
3327 for rule
in ruleset
:
3328 entry
, rule_options
= get_rule_options(rule
)
3329 if rule_options
.get("disabled"):
3332 if entry
[0] == NEGATE
: # this entry is logically negated
3339 hostlist
, servlist
= entry
3341 elif len(entry
) == 3:
3342 tags
, hostlist
, servlist
= entry
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
)))
3354 def _setup_clusters_nodes_cache(self
):
3355 for cluster
, hosts
in clusters
.items():
3356 clustername
= cluster
.split('|', 1)[0]
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
3377 the_clusters
= self
.clusters_of(hostname
)
3379 if not the_clusters
:
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
:
3388 # 1. New style: explicitly assigned services
3389 for cluster
, conf
in clustered_services_of
.iteritems():
3390 nodes
= nodes_of(cluster
)
3392 raise MKGeneralException(
3393 "Invalid entry clustered_services_of['%s']: %s is not a cluster." % (cluster
,
3395 if hostname
in nodes
and \
3396 self
.in_boolean_serviceconf_list(hostname
, servicedesc
, conf
):
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]
3406 def in_binary_hostlist(self
, hostname
, conf
):
3407 cache
= self
._in
_binary
_hostlist
_cache
3409 cache_id
= id(conf
), hostname
3411 return cache
[cache_id
]
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
3421 actual_host_tags
= self
.tag_list_of_host(hostname
)
3422 entry
, rule_options
= get_rule_options(entry
)
3423 if rule_options
.get("disabled"):
3427 # Negation via 'NEGATE'
3428 if entry
[0] == NEGATE
:
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):
3440 if len(entry
) == 1: # 1-Tuple with list of hosts
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
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
,))
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"]