Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk_base / check_table.py
blob9aab32771112bea5fe393a3ac42fbf1d8a2fc438
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
26 """Code for computing the table of checks of hosts."""
28 from cmk.utils.regex import regex
29 from cmk.utils.exceptions import MKGeneralException
31 import cmk_base
32 import cmk_base.config as config
33 import cmk_base.item_state as item_state
34 import cmk_base.check_utils
35 import cmk_base.autochecks
36 import cmk_base.check_api_utils as check_api_utils
38 # TODO: Refactor this to OO. The check table needs to be an object.
41 # Returns check table for a specific host
42 # Format: (checkname, item) -> (params, description)
44 # filter_mode: None -> default, returns only checks for this host
45 # filter_mode: "only_clustered" -> returns only checks belonging to clusters
46 # filter_mode: "include_clustered" -> returns checks of own host, including clustered checks
47 def get_check_table(hostname,
48 remove_duplicates=False,
49 use_cache=True,
50 world='config',
51 skip_autochecks=False,
52 filter_mode=None,
53 skip_ignored=True):
54 if config.is_ping_host(hostname):
55 skip_autochecks = True
57 # speed up multiple lookup of same host
58 check_table_cache = cmk_base.config_cache.get_dict("check_tables")
59 table_cache_id = hostname, filter_mode
61 if not skip_autochecks and use_cache and table_cache_id in check_table_cache:
62 # TODO: The whole is_dual_host handling needs to be cleaned up. The duplicate checking
63 # needs to be done in all cases since a host can now have a lot of different data
64 # sources.
65 if remove_duplicates and config.is_dual_host(hostname):
66 return _remove_duplicate_checks(check_table_cache[table_cache_id])
67 return check_table_cache[table_cache_id]
69 check_table = {}
71 single_host_checks = cmk_base.config_cache.get_dict("single_host_checks")
72 multi_host_checks = cmk_base.config_cache.get_list("multi_host_checks")
74 hosttags = config.tags_of_host(hostname)
76 # Just a local cache and its function
77 is_checkname_valid_cache = {}
79 def is_checkname_valid(checkname):
80 the_id = (hostname, checkname)
81 if the_id in is_checkname_valid_cache:
82 return is_checkname_valid_cache[the_id]
84 passed = True
85 if checkname not in config.check_info:
86 passed = False
88 # Skip SNMP checks for non SNMP hosts (might have been discovered before with other
89 # agent setting. Remove them without rediscovery). Same for agent based checks.
90 elif not config.is_snmp_host(hostname) and cmk_base.check_utils.is_snmp_check(checkname) and \
91 (not config.has_management_board(hostname) or config.management_protocol_of(hostname) != "snmp"):
92 passed = False
94 elif not config.is_agent_host(hostname) and cmk_base.check_utils.is_tcp_check(checkname):
95 passed = False
97 is_checkname_valid_cache[the_id] = passed
98 return passed
100 def handle_entry(entry):
101 num_elements = len(entry)
102 if num_elements == 3: # from autochecks
103 hostlist = hostname
104 checkname, item, params = entry
105 tags = []
106 elif num_elements == 4:
107 hostlist, checkname, item, params = entry
108 tags = []
109 elif num_elements == 5:
110 tags, hostlist, checkname, item, params = entry
111 if not isinstance(tags, list):
112 raise MKGeneralException(
113 "Invalid entry '%r' in check table. First entry must be list of host tags." %
114 (entry,))
116 else:
117 raise MKGeneralException(
118 "Invalid entry '%r' in check table. It has %d entries, but must have 4 or 5." %
119 (entry, len(entry)))
121 # hostlist list might be:
122 # 1. a plain hostname (string)
123 # 2. a list of hostnames (list of strings)
124 # Hostnames may be tagged. Tags are removed.
125 # In autochecks there are always single untagged hostnames. We optimize for that.
126 if isinstance(hostlist, str):
127 if hostlist != hostname:
128 return # optimize most common case: hostname mismatch
129 hostlist = [hostlist]
130 elif isinstance(hostlist[0], str):
131 pass # regular case: list of hostnames
132 elif hostlist != []:
133 raise MKGeneralException("Invalid entry '%r' in check table. Must be single hostname "
134 "or list of hostnames" % hostlist)
136 if not is_checkname_valid(checkname):
137 return
139 if config.hosttags_match_taglist(hosttags, tags) and \
140 config.in_extraconf_hostlist(hostlist, hostname):
141 descr = config.service_description(hostname, checkname, item)
142 if skip_ignored and config.service_ignored(hostname, checkname, descr):
143 return
145 svc_is_mine = hostname == config.host_of_clustered_service(hostname, descr)
146 if filter_mode is None and not svc_is_mine:
147 return
149 elif filter_mode == "only_clustered" and svc_is_mine:
150 return
152 deps = service_deps(hostname, descr)
153 check_table[(checkname, item)] = (params, descr, deps)
155 # Now process all entries that are specific to the host
156 # in search (single host) or that might match the host.
157 if not skip_autochecks:
158 for entry in cmk_base.autochecks.read_autochecks_of(hostname, world):
159 handle_entry(entry)
161 for entry in single_host_checks.get(hostname, []):
162 handle_entry(entry)
164 for entry in multi_host_checks:
165 handle_entry(entry)
167 # Now add checks a cluster might receive from its nodes
168 if config.is_cluster(hostname):
169 single_host_checks = cmk_base.config_cache.get_dict("single_host_checks")
171 for node in config.nodes_of(hostname):
172 node_checks = single_host_checks.get(node, [])
173 if not skip_autochecks:
174 node_checks = node_checks + cmk_base.autochecks.read_autochecks_of(node, world)
175 for entry in node_checks:
176 if len(entry) == 4:
177 entry = entry[1:] # drop hostname from single_host_checks
178 checkname, item, params = entry
179 descr = config.service_description(node, checkname, item)
180 if hostname == config.host_of_clustered_service(node, descr):
181 cluster_params = config.compute_check_parameters(hostname, checkname, item,
182 params)
183 handle_entry((hostname, checkname, item, cluster_params))
185 # Remove dependencies to non-existing services
186 all_descr = set([descr for ((checkname, item), (params, descr, deps)) in check_table.items()])
187 for (checkname, item), (params, descr, deps) in check_table.items():
188 deeps = deps[:]
189 del deps[:]
190 for d in deeps:
191 if d in all_descr:
192 deps.append(d)
194 if not skip_autochecks and use_cache:
195 check_table_cache[table_cache_id] = check_table
197 if remove_duplicates:
198 return _remove_duplicate_checks(check_table)
199 return check_table
202 def get_precompiled_check_table(hostname,
203 remove_duplicates=True,
204 world="config",
205 filter_mode=None,
206 skip_ignored=True):
207 host_checks = get_sorted_check_table(
208 hostname, remove_duplicates, world, filter_mode=filter_mode, skip_ignored=skip_ignored)
209 precomp_table = []
210 for check_plugin_name, item, params, description, _unused_deps in host_checks:
211 # make these globals available to the precompile function
212 check_api_utils.set_service(check_plugin_name, description)
213 item_state.set_item_state_prefix(check_plugin_name, item)
215 params = get_precompiled_check_parameters(hostname, item, params, check_plugin_name)
216 precomp_table.append((check_plugin_name, item, params,
217 description)) # deps not needed while checking
218 return precomp_table
221 def get_precompiled_check_parameters(hostname, item, params, check_plugin_name):
222 precomp_func = config.precompile_params.get(check_plugin_name)
223 if precomp_func:
224 return precomp_func(hostname, item, params)
225 return params
228 # Return a list of services this services depends upon
229 # TODO: Make this use the generic "rulesets" functions
230 # TODO: Is this needed here? Investigate for what this is used for
231 def service_deps(hostname, servicedesc):
232 deps = []
233 for entry in config.service_dependencies:
234 entry, rule_options = config.get_rule_options(entry)
235 if rule_options.get("disabled"):
236 continue
238 if len(entry) == 3:
239 depname, hostlist, patternlist = entry
240 tags = []
241 elif len(entry) == 4:
242 depname, tags, hostlist, patternlist = entry
243 else:
244 raise MKGeneralException("Invalid entry '%r' in service dependencies: "
245 "must have 3 or 4 entries" % entry)
247 if config.hosttags_match_taglist(config.tags_of_host(hostname), tags) and \
248 config.in_extraconf_hostlist(hostlist, hostname):
249 for pattern in patternlist:
250 matchobject = regex(pattern).search(servicedesc)
251 if matchobject:
252 try:
253 item = matchobject.groups()[-1]
254 deps.append(depname % item)
255 except:
256 deps.append(depname)
257 return deps
260 def _remove_duplicate_checks(check_table):
261 have_with_tcp = {}
262 have_with_snmp = {}
263 without_duplicates = {}
264 for key, value in check_table.iteritems():
265 checkname = key[0]
266 descr = value[1]
267 if cmk_base.check_utils.is_snmp_check(checkname):
268 if descr in have_with_tcp:
269 continue
270 have_with_snmp[descr] = key
271 else:
272 if descr in have_with_snmp:
273 snmp_key = have_with_snmp[descr]
274 del without_duplicates[snmp_key]
275 del have_with_snmp[descr]
276 have_with_tcp[descr] = key
277 without_duplicates[key] = value
278 return without_duplicates
281 # remove_duplicates: Automatically remove SNMP based checks
282 # if there already is a TCP based one with the same
283 # description. E.g: df vs hr_fs.
284 # TODO: Clean this up!
285 def get_sorted_check_table(hostname,
286 remove_duplicates=False,
287 world="config",
288 filter_mode=None,
289 skip_ignored=True):
290 # Convert from dictionary into simple tuple list. Then sort
291 # it according to the service dependencies.
292 unsorted = [(checkname, item, params, descr, deps)
293 for ((checkname, item), (params, descr, deps)) in get_check_table(
294 hostname,
295 remove_duplicates=remove_duplicates,
296 world=world,
297 filter_mode=filter_mode,
298 skip_ignored=skip_ignored).items()]
300 unsorted.sort(key=lambda x: x[3])
302 ordered = []
303 while len(unsorted) > 0:
304 unsorted_descrs = set([entry[3] for entry in unsorted])
305 left = []
306 at_least_one_hit = False
307 for check in unsorted:
308 deps_fulfilled = True
309 for dep in check[4]: # deps
310 if dep in unsorted_descrs:
311 deps_fulfilled = False
312 break
313 if deps_fulfilled:
314 ordered.append(check)
315 at_least_one_hit = True
316 else:
317 left.append(check)
318 if len(left) == 0:
319 break
320 if not at_least_one_hit:
321 raise MKGeneralException("Cyclic service dependency of host %s. Problematic are: %s" %
322 (hostname, ",".join(unsorted_descrs)))
323 unsorted = left
324 return ordered