Licenses: Updated the list of licenses and added a PDF containing all license texts
[check_mk.git] / cmk_base / discovery.py
blob04a77e847830ec6a4343131690fc08a9f49a2f7a
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.
27 import os
28 import socket
29 import time
30 import signal
32 from cmk.utils.regex import regex
33 import cmk.utils.tty as tty
34 import cmk.utils.debug
35 import cmk.utils.paths
36 from cmk.utils.exceptions import MKGeneralException, MKTimeout
38 import cmk_base.crash_reporting
39 import cmk_base.config as config
40 import cmk_base.console as console
41 import cmk_base.ip_lookup as ip_lookup
42 import cmk_base.check_api_utils as check_api_utils
43 import cmk_base.item_state as item_state
44 import cmk_base.checking as checking
45 import cmk_base.data_sources as data_sources
46 import cmk_base.check_table as check_table
47 import cmk_base.core
48 from cmk_base.exceptions import MKParseFunctionError
49 import cmk_base.cleanup
50 import cmk_base.check_utils
51 import cmk_base.decorator
52 import cmk_base.snmp_scan as snmp_scan
54 # Run the discovery queued by check_discovery() - if any
55 _marked_host_discovery_timeout = 120
57 # .--cmk -I--------------------------------------------------------------.
58 # | _ ___ |
59 # | ___ _ __ ___ | | __ |_ _| |
60 # | / __| '_ ` _ \| |/ / _____| | |
61 # | | (__| | | | | | < |_____| | |
62 # | \___|_| |_| |_|_|\_\ |___| |
63 # | |
64 # +----------------------------------------------------------------------+
65 # | Functions for command line options -I and -II |
66 # '----------------------------------------------------------------------'
69 # Function implementing cmk -I and cmk -II. This is directly
70 # being called from the main option parsing code. The list of
71 # hostnames is already prepared by the main code. If it is
72 # empty then we use all hosts and switch to using cache files.
73 def do_discovery(hostnames, check_plugin_names, only_new):
74 use_caches = data_sources.abstract.DataSource.get_may_use_cache_file()
75 if not hostnames:
76 console.verbose("Discovering services on all hosts\n")
77 hostnames = config.all_active_realhosts()
78 use_caches = True
79 else:
80 console.verbose("Discovering services on: %s\n" % ", ".join(hostnames))
82 # For clusters add their nodes to the list. Clusters itself
83 # cannot be discovered but the user is allowed to specify
84 # them and we do discovery on the nodes instead.
85 nodes = []
86 cluster_hosts = []
87 for h in hostnames:
88 nodes = config.nodes_of(h)
89 if nodes:
90 cluster_hosts.append(h)
91 hostnames += nodes
93 # Then remove clusters and make list unique
94 hostnames = list(set([h for h in hostnames if not config.is_cluster(h)]))
95 hostnames.sort()
97 # Now loop through all hosts
98 for hostname in hostnames:
99 console.section_begin(hostname)
101 try:
102 if cmk.utils.debug.enabled():
103 on_error = "raise"
104 else:
105 on_error = "warn"
107 ipaddress = ip_lookup.lookup_ip_address(hostname)
109 # Usually we disable SNMP scan if cmk -I is used without a list of
110 # explicity hosts. But for host that have never been service-discovered
111 # yet (do not have autochecks), we enable SNMP scan.
112 do_snmp_scan = not use_caches or not _has_autochecks(hostname)
114 sources = _get_sources_for_discovery(hostname, ipaddress, check_plugin_names,
115 do_snmp_scan, on_error)
116 multi_host_sections = _get_host_sections_for_discovery(sources, use_caches=use_caches)
118 _do_discovery_for(hostname, ipaddress, sources, multi_host_sections, check_plugin_names,
119 only_new, on_error)
121 except Exception as e:
122 if cmk.utils.debug.enabled():
123 raise
124 console.section_error("%s" % e)
125 finally:
126 cmk_base.cleanup.cleanup_globals()
128 # Check whether or not the cluster host autocheck files are still
129 # existant. Remove them. The autochecks are only stored in the nodes
130 # autochecks files these days.
131 for hostname in cluster_hosts:
132 _remove_autochecks_file(hostname)
135 def _do_discovery_for(hostname, ipaddress, sources, multi_host_sections, check_plugin_names,
136 only_new, on_error):
137 if not check_plugin_names and not only_new:
138 old_items = [] # do not even read old file
139 else:
140 old_items = parse_autochecks_file(hostname)
142 if not check_plugin_names:
143 # In 'multi_host_sections = _get_host_sections_for_discovery(..)'
144 # we've already discovered the right check plugin names.
145 # _discover_services(..) would discover check plugin names again.
146 # In order to avoid a second discovery (SNMP data source would do
147 # another SNMP scan) we enforce this selection to be used.
148 check_plugin_names = multi_host_sections.get_check_plugin_names()
149 sources.enforce_check_plugin_names(check_plugin_names)
151 console.step("Executing discovery plugins (%d)" % len(check_plugin_names))
152 console.vverbose(" Trying discovery with: %s\n" % ", ".join(check_plugin_names))
153 new_items = _discover_services(
154 hostname, ipaddress, sources, multi_host_sections, on_error=on_error)
156 # There are three ways of how to merge existing and new discovered checks:
157 # 1. -II without --checks=
158 # check_plugin_names is empty, only_new is False
159 # --> complete drop old services, only use new ones
160 # 2. -II with --checks=
161 # --> drop old services of that types
162 # check_plugin_names is not empty, only_new is False
163 # 3. -I
164 # --> just add new services
165 # only_new is True
167 # Parse old items into a dict (ct, item) -> paramstring
168 result = {}
169 for check_plugin_name, item, paramstring in old_items:
170 # Take over old items if -I is selected or if -II
171 # is selected with --checks= and the check type is not
172 # one of the listed ones
173 if only_new or (check_plugin_names and check_plugin_name not in check_plugin_names):
174 result[(check_plugin_name, item)] = paramstring
176 stats, num_services = {}, 0
177 for check_plugin_name, item, paramstring in new_items:
178 if (check_plugin_name, item) not in result:
179 result[(check_plugin_name, item)] = paramstring
180 stats.setdefault(check_plugin_name, 0)
181 stats[check_plugin_name] += 1
182 num_services += 1
184 final_items = []
185 for (check_plugin_name, item), paramstring in result.items():
186 final_items.append((check_plugin_name, item, paramstring))
187 final_items.sort()
188 _save_autochecks_file(hostname, final_items)
190 found_check_plugin_names = stats.keys()
191 found_check_plugin_names.sort()
193 if found_check_plugin_names:
194 for check_plugin_name in found_check_plugin_names:
195 console.verbose("%s%3d%s %s\n" % (tty.green + tty.bold, stats[check_plugin_name],
196 tty.normal, check_plugin_name))
197 console.section_success("Found %d services" % num_services)
198 else:
199 console.section_success("Found nothing%s" % (only_new and " new" or ""))
202 # determine changed services on host.
203 # param mode: can be one of "new", "remove", "fixall", "refresh"
204 # param do_snmp_scan: if True, a snmp host will be scanned, otherwise uses only the check types
205 # previously discovereda
206 # param servic_filter: if a filter is set, it controls whether items are touched by the discovery.
207 # if it returns False for a new item it will not be added, if it returns
208 # False for a vanished item, that item is kept
209 def discover_on_host(mode,
210 hostname,
211 do_snmp_scan,
212 use_caches,
213 on_error="ignore",
214 service_filter=None):
215 counts = {
216 "self_new": 0,
217 "self_removed": 0,
218 "self_kept": 0,
219 "self_total": 0,
220 "clustered_new": 0,
221 "clustered_vanished": 0,
224 if hostname not in config.all_active_realhosts():
225 return counts, ""
227 if service_filter is None:
228 service_filter = lambda hostname, check_plugin_name, item: True
230 err = None
232 try:
233 # in "refresh" mode we first need to remove all previously discovered
234 # checks of the host, so that _get_host_services() does show us the
235 # new discovered check parameters.
236 if mode == "refresh":
237 counts["self_removed"] += remove_autochecks_of(hostname) # this is cluster-aware!
239 if config.is_cluster(hostname):
240 ipaddress = None
241 else:
242 ipaddress = ip_lookup.lookup_ip_address(hostname)
244 sources = _get_sources_for_discovery(
245 hostname,
246 ipaddress,
247 check_plugin_names=None,
248 do_snmp_scan=do_snmp_scan,
249 on_error=on_error)
251 multi_host_sections = _get_host_sections_for_discovery(sources, use_caches=use_caches)
253 # Compute current state of new and existing checks
254 services = _get_host_services(
255 hostname, ipaddress, sources, multi_host_sections, on_error=on_error)
257 # Create new list of checks
258 new_items = {}
259 for (check_plugin_name, item), (check_source, paramstring) in services.items():
260 if check_source in ("custom", "legacy", "active", "manual"):
261 continue # this is not an autocheck or ignored and currently not checked
262 # Note discovered checks that are shadowed by manual checks will vanish
263 # that way.
265 if check_source == "new":
266 if mode in ("new", "fixall", "refresh") and service_filter(
267 hostname, check_plugin_name, item):
268 counts["self_new"] += 1
269 new_items[(check_plugin_name, item)] = paramstring
271 elif check_source in ("old", "ignored"):
272 # keep currently existing valid services in any case
273 new_items[(check_plugin_name, item)] = paramstring
274 counts["self_kept"] += 1
276 elif check_source == "vanished":
277 # keep item, if we are currently only looking for new services
278 # otherwise fix it: remove ignored and non-longer existing services
279 if mode not in ("fixall",
280 "remove") or not service_filter(hostname, check_plugin_name, item):
281 new_items[(check_plugin_name, item)] = paramstring
282 counts["self_kept"] += 1
283 else:
284 counts["self_removed"] += 1
286 elif check_source.startswith("clustered_"):
287 # Silently keep clustered services
288 counts[check_source] += 1
289 new_items[(check_plugin_name, item)] = paramstring
291 else:
292 raise MKGeneralException("Unknown check source '%s'" % check_source)
293 set_autochecks_of(hostname, new_items)
295 except MKTimeout:
296 raise # let general timeout through
298 except Exception as e:
299 if cmk.utils.debug.enabled():
300 raise
301 err = str(e)
303 counts["self_total"] = counts["self_new"] + counts["self_kept"]
304 return counts, err
308 # .--Discovery Check-----------------------------------------------------.
309 # | ____ _ _ _ |
310 # | | _ \(_)___ ___ ___| |__ ___ ___| | __ |
311 # | | | | | / __|/ __| / __| '_ \ / _ \/ __| |/ / |
312 # | | |_| | \__ \ (__ _ | (__| | | | __/ (__| < |
313 # | |____/|_|___/\___(_) \___|_| |_|\___|\___|_|\_\ |
314 # | |
315 # +----------------------------------------------------------------------+
316 # | Active check for checking undiscovered services. |
317 # '----------------------------------------------------------------------'
320 @cmk_base.decorator.handle_check_mk_check_result("discovery", "Check_MK Discovery")
321 def check_discovery(hostname, ipaddress):
322 params = discovery_check_parameters(hostname) or \
323 default_discovery_check_parameters()
325 status, infotexts, long_infotexts, perfdata = 0, [], [], []
327 # In case of keepalive discovery we always have an ipaddress. When called as non keepalive
328 # ipaddress is always None
329 if ipaddress is None and not config.is_cluster(hostname):
330 ipaddress = ip_lookup.lookup_ip_address(hostname)
332 sources = _get_sources_for_discovery(
333 hostname,
334 ipaddress,
335 check_plugin_names=None,
336 do_snmp_scan=params["inventory_check_do_scan"],
337 on_error="raise")
339 multi_host_sections = _get_host_sections_for_discovery(
340 sources, use_caches=data_sources.abstract.DataSource.get_may_use_cache_file())
342 services = _get_host_services(
343 hostname, ipaddress, sources, multi_host_sections, on_error="raise")
345 need_rediscovery = False
347 params_rediscovery = params.get("inventory_rediscovery", {})
349 if params_rediscovery.get("service_whitelist", []) or\
350 params_rediscovery.get("service_blacklist", []):
351 # whitelist. if none is specified, this matches everything
352 whitelist = regex("|".join(
353 ["(%s)" % pat for pat in params_rediscovery.get("service_whitelist", [".*"])]))
354 # blacklist. if none is specified, this matches nothing
355 blacklist = regex("|".join(
356 ["(%s)" % pat for pat in params_rediscovery.get("service_blacklist", ["(?!x)x"])]))
358 item_filters = lambda hostname, check_plugin_name, item:\
359 _discovery_filter_by_lists(hostname, check_plugin_name, item, whitelist, blacklist)
360 else:
361 item_filters = None
363 for check_state, title, params_key, default_state in [
364 ("new", "unmonitored", "severity_unmonitored", config.inventory_check_severity),
365 ("vanished", "vanished", "severity_vanished", 0),
368 affected_check_plugin_names = {}
369 count = 0
370 unfiltered = False
372 for (check_plugin_name, item), (check_source, _unused_paramstring) in services.items():
373 if check_source == check_state:
374 count += 1
375 affected_check_plugin_names.setdefault(check_plugin_name, 0)
376 affected_check_plugin_names[check_plugin_name] += 1
378 if not unfiltered and\
379 (item_filters is None or item_filters(hostname, check_plugin_name, item)):
380 unfiltered = True
382 long_infotexts.append(
383 "%s: %s: %s" % (title, check_plugin_name,
384 config.service_description(hostname, check_plugin_name, item)))
386 if affected_check_plugin_names:
387 info = ", ".join(["%s:%d" % e for e in affected_check_plugin_names.items()])
388 st = params.get(params_key, default_state)
389 status = cmk_base.utils.worst_service_state(status, st)
390 infotexts.append(
391 "%d %s services (%s)%s" % (count, title, info, check_api_utils.state_markers[st]))
393 if params.get("inventory_rediscovery", False):
394 mode = params["inventory_rediscovery"]["mode"]
395 if unfiltered and\
396 ((check_state == "new" and mode in ( 0, 2, 3 )) or
397 (check_state == "vanished" and mode in ( 1, 2, 3 ))):
398 need_rediscovery = True
399 else:
400 infotexts.append("no %s services found" % title)
402 for (check_plugin_name, item), (check_source, _unused_paramstring) in services.items():
403 if check_source == "ignored":
404 long_infotexts.append(
405 "ignored: %s: %s" % (check_plugin_name,
406 config.service_description(hostname, check_plugin_name, item)))
408 if need_rediscovery:
409 if config.is_cluster(hostname):
410 for nodename in config.nodes_of(hostname):
411 _set_rediscovery_flag(nodename)
412 else:
413 _set_rediscovery_flag(hostname)
414 infotexts.append("rediscovery scheduled")
416 # Add data source information to check results
417 for source in sources.get_data_sources():
418 source_state, source_output, _source_perfdata = source.get_summary_result_for_discovery()
419 # Do not output informational (state = 0) things. These information are shown by the "Check_MK" service
420 if source_state != 0:
421 status = max(status, source_state)
422 infotexts.append("[%s] %s" % (source.id(), source_output))
424 return status, infotexts, long_infotexts, perfdata
427 # Compute the parameters for the discovery check for a host. Note:
428 # if the discovery check is disabled for that host, default parameters
429 # will be returned.
430 def discovery_check_parameters(hostname):
431 entries = config.host_extra_conf(hostname, config.periodic_discovery)
432 if entries:
433 return entries[0]
435 elif config.inventory_check_interval:
436 # Support legacy global configurations
437 return default_discovery_check_parameters()
439 return None
442 def default_discovery_check_parameters():
443 return {
444 "check_interval": config.inventory_check_interval,
445 "severity_unmonitored": config.inventory_check_severity,
446 "severity_vanished": 0,
447 "inventory_check_do_scan": config.inventory_check_do_scan,
451 def _set_rediscovery_flag(hostname):
452 def touch(filename):
453 if not os.path.exists(filename):
454 f = open(filename, "w")
455 f.close()
457 autodiscovery_dir = cmk.utils.paths.var_dir + '/autodiscovery'
458 discovery_filename = os.path.join(autodiscovery_dir, hostname)
460 if not os.path.exists(autodiscovery_dir):
461 os.makedirs(autodiscovery_dir)
462 touch(discovery_filename)
465 class DiscoveryTimeout(Exception):
466 pass
469 def _handle_discovery_timeout():
470 raise DiscoveryTimeout()
473 def _set_discovery_timeout():
474 signal.signal(signal.SIGALRM, _handle_discovery_timeout)
475 # Add an additional 10 seconds as grace period
476 signal.alarm(_marked_host_discovery_timeout + 10)
479 def _clear_discovery_timeout():
480 signal.alarm(0)
483 def _get_autodiscovery_dir():
484 return cmk.utils.paths.var_dir + '/autodiscovery'
487 def discover_marked_hosts(core):
488 console.verbose("Doing discovery for all marked hosts:\n")
489 autodiscovery_dir = _get_autodiscovery_dir()
491 if not os.path.exists(autodiscovery_dir):
492 # there is obviously nothing to do
493 console.verbose(" Nothing to do. %s is missing.\n" % autodiscovery_dir)
494 return
496 now_ts = time.time()
497 end_time_ts = now_ts + _marked_host_discovery_timeout # don't run for more than 2 minutes
498 oldest_queued = _queue_age()
499 all_hosts = config.all_configured_hosts()
500 hosts = os.listdir(autodiscovery_dir)
501 if not hosts:
502 console.verbose(" Nothing to do. No hosts marked by discovery check.\n")
504 # Fetch host state information from livestatus
505 host_states = _fetch_host_states()
506 activation_required = False
508 try:
509 _set_discovery_timeout()
510 for hostname in hosts:
511 if not _discover_marked_host_exists(hostname, all_hosts):
512 continue
514 # Only try to discover hosts with UP state
515 if host_states and host_states.get(hostname) != 0:
516 continue
518 if _discover_marked_host(hostname, all_hosts, now_ts, oldest_queued):
519 activation_required = True
521 if time.time() > end_time_ts:
522 console.verbose(
523 " Timeout of %d seconds reached. Lets do the remaining hosts next time." %
524 _marked_host_discovery_timeout)
525 break
526 except DiscoveryTimeout:
527 pass
528 finally:
529 _clear_discovery_timeout()
531 if activation_required:
532 console.verbose("\nRestarting monitoring core with updated configuration...\n")
533 if config.monitoring_core == "cmc":
534 cmk_base.core.do_reload(core)
535 else:
536 cmk_base.core.do_restart(core)
539 def _fetch_host_states():
540 host_states = {}
541 try:
542 import livestatus
543 query = "GET hosts\nColumns: name state"
544 host_states = dict(livestatus.LocalConnection().query(query))
545 except (livestatus.MKLivestatusNotFoundError, livestatus.MKLivestatusSocketError):
546 pass
547 return host_states
550 def _discover_marked_host_exists(hostname, all_hosts):
551 if hostname in all_hosts:
552 return True
554 host_flag_path = os.path.join(_get_autodiscovery_dir(), hostname)
555 try:
556 os.remove(host_flag_path)
557 except OSError:
558 pass
559 console.verbose(
560 " Skipped. Host %s does not exist in configuration. Removing mark.\n" % hostname)
561 return False
564 def _discover_marked_host(hostname, all_hosts, now_ts, oldest_queued):
565 services_changed = False
567 mode_table = {0: "new", 1: "remove", 2: "fixall", 3: "refresh"}
569 console.verbose("%s%s%s:\n" % (tty.bold, hostname, tty.normal))
570 host_flag_path = os.path.join(_get_autodiscovery_dir(), hostname)
572 params = discovery_check_parameters(hostname) or default_discovery_check_parameters()
573 params_rediscovery = params.get("inventory_rediscovery", {})
574 if "service_blacklist" in params_rediscovery or "service_whitelist" in params_rediscovery:
575 # whitelist. if none is specified, this matches everything
576 whitelist = regex("|".join(
577 ["(%s)" % pat for pat in params_rediscovery.get("service_whitelist", [".*"])]))
578 # blacklist. if none is specified, this matches nothing
579 blacklist = regex("|".join(
580 ["(%s)" % pat for pat in params_rediscovery.get("service_blacklist", ["(?!x)x"])]))
581 item_filters = lambda hostname, check_plugin_name, item:\
582 _discovery_filter_by_lists(hostname, check_plugin_name, item, whitelist, blacklist)
583 else:
584 item_filters = None
586 why_not = _may_rediscover(params, now_ts, oldest_queued)
587 if not why_not:
588 redisc_params = params["inventory_rediscovery"]
589 console.verbose(" Doing discovery with mode '%s'...\n" % mode_table[redisc_params["mode"]])
590 result, error = discover_on_host(
591 mode_table[redisc_params["mode"]],
592 hostname,
593 do_snmp_scan=params["inventory_check_do_scan"],
594 use_caches=True,
595 service_filter=item_filters)
596 if error is not None:
597 if error:
598 console.verbose("failed: %s\n" % error)
599 else:
600 # for offline hosts the error message is empty. This is to remain
601 # compatible with the automation code
602 console.verbose(" failed: host is offline\n")
603 else:
604 if result["self_new"] == 0 and\
605 result["self_removed"] == 0 and\
606 result["self_kept"] == result["self_total"] and\
607 result["clustered_new"] == 0 and\
608 result["clustered_vanished"] == 0:
609 console.verbose(" nothing changed.\n")
610 else:
611 console.verbose(" %(self_new)s new, %(self_removed)s removed, "\
612 "%(self_kept)s kept, %(self_total)s total services. "\
613 "clustered new %(clustered_new)s, clustered vanished %(clustered_vanished)s" % result)
615 # Note: Even if the actual mark-for-discovery flag may have been created by a cluster host,
616 # the activation decision is based on the discovery configuration of the node
617 if redisc_params["activation"]:
618 services_changed = True
620 # Now ensure that the discovery service is updated right after the changes
621 schedule_discovery_check(hostname)
623 # delete the file even in error case, otherwise we might be causing the same error
624 # every time the cron job runs
625 try:
626 os.remove(host_flag_path)
627 except OSError:
628 pass
629 else:
630 console.verbose(" skipped: %s\n" % why_not)
632 return services_changed
635 def _queue_age():
636 autodiscovery_dir = _get_autodiscovery_dir()
637 oldest = time.time()
638 for filename in os.listdir(autodiscovery_dir):
639 oldest = min(oldest, os.path.getmtime(autodiscovery_dir + "/" + filename))
640 return oldest
643 def _may_rediscover(params, now_ts, oldest_queued):
644 if "inventory_rediscovery" not in params:
645 return "automatic discovery disabled for this host"
647 now = time.gmtime(now_ts)
648 for start_hours_mins, end_hours_mins in params["inventory_rediscovery"]["excluded_time"]:
649 start_time = time.struct_time(
650 (now.tm_year, now.tm_mon, now.tm_mday, start_hours_mins[0], start_hours_mins[1], 0,
651 now.tm_wday, now.tm_yday, now.tm_isdst))
653 end_time = time.struct_time((now.tm_year, now.tm_mon, now.tm_mday, end_hours_mins[0],
654 end_hours_mins[1], 0, now.tm_wday, now.tm_yday, now.tm_isdst))
656 if start_time <= now <= end_time:
657 return "we are currently in a disallowed time of day"
659 if now_ts - oldest_queued < params["inventory_rediscovery"]["group_time"]:
660 return "last activation is too recent"
662 return None
665 def _discovery_filter_by_lists(hostname, check_plugin_name, item, whitelist, blacklist):
666 description = config.service_description(hostname, check_plugin_name, item)
667 return whitelist.match(description) is not None and\
668 blacklist.match(description) is None
672 # .--Helpers-------------------------------------------------------------.
673 # | _ _ _ |
674 # | | | | | ___| |_ __ ___ _ __ ___ |
675 # | | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
676 # | | _ | __/ | |_) | __/ | \__ \ |
677 # | |_| |_|\___|_| .__/ \___|_| |___/ |
678 # | |_| |
679 # +----------------------------------------------------------------------+
680 # | Various helper functions |
681 # '----------------------------------------------------------------------'
684 # TODO: Move to livestatus module!
685 def schedule_discovery_check(hostname):
686 try:
687 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
688 s.connect(cmk.utils.paths.livestatus_unix_socket)
689 now = int(time.time())
690 if 'cmk-inventory' in config.use_new_descriptions_for:
691 command = "SCHEDULE_FORCED_SVC_CHECK;%s;Check_MK Discovery;%d" % (hostname, now)
692 else:
693 # TODO: Remove this old name handling one day
694 command = "SCHEDULE_FORCED_SVC_CHECK;%s;Check_MK inventory;%d" % (hostname, now)
696 # Ignore missing check and avoid warning in cmc.log
697 if config.monitoring_core == "cmc":
698 command += ";TRY"
700 s.send("COMMAND [%d] %s\n" % (now, command))
701 except Exception:
702 if cmk.utils.debug.enabled():
703 raise
707 # .--Discovery-----------------------------------------------------------.
708 # | ____ _ |
709 # | | _ \(_)___ ___ _____ _____ _ __ _ _ |
710 # | | | | | / __|/ __/ _ \ \ / / _ \ '__| | | | |
711 # | | |_| | \__ \ (_| (_) \ V / __/ | | |_| | |
712 # | |____/|_|___/\___\___/ \_/ \___|_| \__, | |
713 # | |___/ |
714 # +----------------------------------------------------------------------+
715 # | Core code of actual service discovery |
716 # '----------------------------------------------------------------------'
719 # Create a table of autodiscovered services of a host. Do not save
720 # this table anywhere. Do not read any previously discovered
721 # services. The table has the following columns:
722 # 1. Check type
723 # 2. Item
724 # 3. Parameter string (not evaluated)
726 # This function does not handle:
727 # - clusters
728 # - disabled services
730 # This function *does* handle:
731 # - disabled check typess
733 # on_error is one of:
734 # "ignore" -> silently ignore any exception
735 # "warn" -> output a warning on stderr
736 # "raise" -> let the exception come through
737 def _discover_services(hostname, ipaddress, sources, multi_host_sections, on_error):
738 # Make hostname available as global variable in discovery functions
739 # (used e.g. by ps-discovery)
740 check_api_utils.set_hostname(hostname)
742 discovered_services = []
743 try:
744 for check_plugin_name in sources.get_check_plugin_names():
745 try:
746 for item, paramstring in _execute_discovery(multi_host_sections, hostname,
747 ipaddress, check_plugin_name, on_error):
748 discovered_services.append((check_plugin_name, item, paramstring))
749 except (KeyboardInterrupt, MKTimeout):
750 raise
751 except Exception as e:
752 if on_error == "raise":
753 raise
754 elif on_error == "warn":
755 console.error("Discovery of '%s' failed: %s\n" % (check_plugin_name, e))
757 check_table_formatted = {}
758 for (check_type, item, _paramstring) in discovered_services:
759 check_table_formatted[(check_type, item)] = (None,
760 config.service_description(
761 hostname, check_type, item))
763 check_table_formatted = check_table.remove_duplicate_checks(check_table_formatted)
764 for entry in discovered_services[:]:
765 check_type, item = entry[:2]
766 if (check_type, item) not in check_table_formatted:
767 discovered_services.remove(entry)
769 return discovered_services
771 except KeyboardInterrupt:
772 raise MKGeneralException("Interrupted by Ctrl-C.")
775 def _get_sources_for_discovery(hostname, ipaddress, check_plugin_names, do_snmp_scan, on_error):
776 sources = data_sources.DataSources(hostname, ipaddress)
778 for source in sources.get_data_sources():
779 if isinstance(source, data_sources.SNMPDataSource):
780 source.set_on_error(on_error)
781 source.set_do_snmp_scan(do_snmp_scan)
782 source.set_use_snmpwalk_cache(False)
783 source.set_ignore_check_interval(True)
784 source.set_check_plugin_name_filter(snmp_scan.gather_snmp_check_plugin_names)
786 # When check types are specified via command line, enforce them and disable auto detection
787 if check_plugin_names:
788 sources.enforce_check_plugin_names(check_plugin_names)
790 return sources
793 def _get_host_sections_for_discovery(sources, use_caches):
794 max_cachefile_age = config.inventory_max_cachefile_age if use_caches else 0
795 return sources.get_host_sections(max_cachefile_age)
798 def _execute_discovery(multi_host_sections, hostname, ipaddress, check_plugin_name, on_error):
799 # Skip this check type if is ignored for that host
800 if config.service_ignored(hostname, check_plugin_name, None):
801 console.vverbose(" Skip ignored check plugin name '%s'\n" % check_plugin_name)
802 return []
804 discovery_function = _get_discovery_function_of(check_plugin_name)
806 try:
807 # TODO: There is duplicate code with checking.execute_check(). Find a common place!
808 try:
809 section_content = multi_host_sections.get_section_content(
810 hostname, ipaddress, check_plugin_name, for_discovery=True)
811 except MKParseFunctionError as e:
812 if cmk.utils.debug.enabled() or on_error == "raise":
813 x = e.exc_info()
814 if x[0] == item_state.MKCounterWrapped:
815 return []
816 else:
817 # re-raise the original exception to not destory the trace. This may raise a MKCounterWrapped
818 # exception which need to lead to a skipped check instead of a crash
819 raise x[0], x[1], x[2]
821 elif on_error == "warn":
822 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
823 console.warning(
824 "Exception while parsing agent section '%s': %s\n" % (section_name, e))
826 return []
828 if section_content is None: # No data for this check type
829 return []
831 # In case of SNMP checks but missing agent response, skip this check.
832 # Special checks which still need to be called even with empty data
833 # may declare this.
834 if not section_content and cmk_base.check_utils.is_snmp_check(check_plugin_name) \
835 and not config.check_info[check_plugin_name]["handle_empty_info"]:
836 return []
838 # Now do the actual discovery
839 discovered_items = _execute_discovery_function(discovery_function, section_content)
840 return _validate_discovered_items(hostname, check_plugin_name, discovered_items)
841 except Exception as e:
842 if on_error == "warn":
843 console.warning(
844 " Exception in discovery function of check type '%s': %s" % (check_plugin_name, e))
845 elif on_error == "raise":
846 raise
847 return []
850 def _get_discovery_function_of(check_plugin_name):
851 try:
852 discovery_function = config.check_info[check_plugin_name]["inventory_function"]
853 except KeyError:
854 raise MKGeneralException("No such check type '%s'" % check_plugin_name)
856 if discovery_function is None:
857 return lambda _info: _no_discovery_possible(check_plugin_name)
859 return discovery_function
862 def _no_discovery_possible(check_plugin_name):
863 console.verbose("%s does not support discovery. Skipping it.\n", check_plugin_name)
864 return []
867 def _execute_discovery_function(discovery_function, section_content):
868 discovered_items = discovery_function(section_content)
870 # tolerate function not explicitely returning []
871 if discovered_items is None:
872 discovered_items = []
874 # New yield based api style
875 elif not isinstance(discovered_items, list):
876 discovered_items = list(discovered_items)
878 return discovered_items
881 def _validate_discovered_items(hostname, check_plugin_name, discovered_items):
882 results = []
883 for entry in discovered_items:
884 if not isinstance(entry, tuple):
885 console.error("%s: Check %s returned invalid discovery data (entry not a tuple): %r\n" %
886 (hostname, check_plugin_name, repr(entry)))
887 continue
889 if len(entry) == 2: # comment is now obsolete
890 item, paramstring = entry
891 elif len(entry) == 3: # allow old school
892 item, __, paramstring = entry
893 else: # we really don't want longer tuples (or 1-tuples).
894 console.error("%s: Check %s returned invalid discovery data (not 2 or 3 elements): %r\n"
895 % (hostname, check_plugin_name, repr(entry)))
896 continue
898 # Check_MK 1.2.7i3 defines items to be unicode strings. Convert non unicode
899 # strings here seamless. TODO remove this conversion one day and replace it
900 # with a validation that item needs to be of type unicode
901 if isinstance(item, str):
902 item = config.decode_incoming_string(item)
904 description = config.service_description(hostname, check_plugin_name, item)
905 # make sanity check
906 if len(description) == 0:
907 console.error("%s: Check %s returned empty service description - ignoring it.\n" %
908 (hostname, check_plugin_name))
909 continue
911 results.append((item, paramstring))
912 return results
915 # Creates a table of all services that a host has or could have according
916 # to service discovery. The result is a dictionary of the form
917 # (check_plugin_name, item) -> (check_source, paramstring)
918 # check_source is the reason/state/source of the service:
919 # "new" : Check is discovered but currently not yet monitored
920 # "old" : Check is discovered and already monitored (most common)
921 # "vanished" : Check had been discovered previously, but item has vanished
922 # "legacy" : Check is defined via legacy_checks
923 # "active" : Check is defined via active_checks
924 # "custom" : Check is defined via custom_checks
925 # "manual" : Check is a manual Check_MK check without service discovery
926 # "ignored" : discovered or static, but disabled via ignored_services
927 # "clustered_new" : New service found on a node that belongs to a cluster
928 # "clustered_old" : Old service found on a node that belongs to a cluster
929 # This function is cluster-aware
930 def _get_host_services(hostname, ipaddress, sources, multi_host_sections, on_error):
931 if config.is_cluster(hostname):
932 return _get_cluster_services(hostname, ipaddress, sources, multi_host_sections, on_error)
934 return _get_node_services(hostname, ipaddress, sources, multi_host_sections, on_error)
937 # Do the actual work for a non-cluster host or node
938 def _get_node_services(hostname, ipaddress, sources, multi_host_sections, on_error):
939 services = _get_discovered_services(hostname, ipaddress, sources, multi_host_sections, on_error)
941 config_cache = config.get_config_cache()
942 # Identify clustered services
943 for (check_plugin_name, item), (check_source, paramstring) in services.items():
944 try:
945 descr = config.service_description(hostname, check_plugin_name, item)
946 except Exception as e:
947 if on_error == "raise":
948 raise
949 elif on_error == "warn":
950 console.error("Invalid service description: %s\n" % e)
951 else:
952 continue # ignore
954 clustername = config_cache.host_of_clustered_service(hostname, descr)
955 if hostname != clustername:
956 if config.service_ignored(clustername, check_plugin_name, descr):
957 check_source = "ignored"
958 services[(check_plugin_name, item)] = ("clustered_" + check_source, paramstring)
960 _merge_manual_services(services, hostname, on_error)
961 return services
964 # Part of _get_node_services that deals with discovered services
965 def _get_discovered_services(hostname, ipaddress, sources, multi_host_sections, on_error):
966 # Create a dict from check_plugin_name/item to check_source/paramstring
967 services = {}
969 # In 'multi_host_sections = _get_host_sections_for_discovery(..)'
970 # we've already discovered the right check plugin names.
971 # _discover_services(..) would discover check plugin names again.
972 # In order to avoid a second discovery (SNMP data source would do
973 # another SNMP scan) we enforce this selection to be used.
974 check_plugin_names = multi_host_sections.get_check_plugin_names()
975 sources.enforce_check_plugin_names(check_plugin_names)
977 # Handle discovered services -> "new"
978 new_items = _discover_services(hostname, ipaddress, sources, multi_host_sections, on_error)
979 for check_plugin_name, item, paramstring in new_items:
980 services.setdefault((check_plugin_name, item), ("new", paramstring))
982 # Match with existing items -> "old" and "vanished"
983 old_items = parse_autochecks_file(hostname)
984 for check_plugin_name, item, paramstring in old_items:
985 if (check_plugin_name, item) not in services:
986 services[(check_plugin_name, item)] = ("vanished", paramstring)
987 else:
988 services[(check_plugin_name, item)] = ("old", paramstring)
990 return services
993 # To a list of discovered services add/replace manual and active
994 # checks and handle ignoration
995 def _merge_manual_services(services, hostname, on_error):
996 # Find manual checks. These can override discovered checks -> "manual"
997 manual_items = check_table.get_check_table(hostname, skip_autochecks=True)
998 for (check_plugin_name, item), (params, descr, _unused_deps) in manual_items.items():
999 services[(check_plugin_name, item)] = ('manual', repr(params))
1001 config_cache = config.get_config_cache()
1002 # Add legacy checks -> "legacy"
1003 legchecks = config_cache.host_extra_conf(hostname, config.legacy_checks)
1004 for _unused_cmd, descr, _unused_perf in legchecks:
1005 services[('legacy', descr)] = ('legacy', 'None')
1007 # Add custom checks -> "custom"
1008 custchecks = config_cache.host_extra_conf(hostname, config.custom_checks)
1009 for entry in custchecks:
1010 services[('custom', entry['service_description'])] = ('custom', 'None')
1012 # Similar for 'active_checks', but here we have parameters
1013 for acttype, rules in config.active_checks.items():
1014 entries = config_cache.host_extra_conf(hostname, rules)
1015 for params in entries:
1016 descr = config.active_check_service_description(hostname, acttype, params)
1017 services[(acttype, descr)] = ('active', repr(params))
1019 # Handle disabled services -> "ignored"
1020 for (check_plugin_name, item), (check_source, paramstring) in services.items():
1021 if check_source in ["legacy", "active", "custom"]:
1022 # These are ignored later in get_check_preview
1023 # TODO: This needs to be cleaned up. The problem here is that service_description() can not
1024 # calculate the description of active checks and the active checks need to be put into
1025 # "[source]_ignored" instead of ignored.
1026 continue
1028 try:
1029 descr = config.service_description(hostname, check_plugin_name, item)
1030 except Exception as e:
1031 if on_error == "raise":
1032 raise
1033 elif on_error == "warn":
1034 console.error("Invalid service description: %s\n" % e)
1035 else:
1036 continue # ignore
1038 if config.service_ignored(hostname, check_plugin_name, descr):
1039 services[(check_plugin_name, item)] = ("ignored", paramstring)
1041 return services
1044 # Do the work for a cluster
1045 def _get_cluster_services(hostname, ipaddress, sources, multi_host_sections, on_error):
1046 nodes = config.nodes_of(hostname)
1048 config_cache = config.get_config_cache()
1050 # Get setting from cluster SNMP data source
1051 do_snmp_scan = False
1052 for source in sources.get_data_sources():
1053 if isinstance(source, data_sources.SNMPDataSource):
1054 do_snmp_scan = source.get_do_snmp_scan()
1056 # Get services of the nodes. We are only interested in "old", "new" and "vanished"
1057 # From the states and parameters of these we construct the final state per service.
1058 cluster_items = {}
1059 for node in nodes:
1060 node_ipaddress = ip_lookup.lookup_ip_address(node)
1061 node_sources = _get_sources_for_discovery(
1062 node,
1063 node_ipaddress,
1064 check_plugin_names=sources.get_enforced_check_plugin_names(),
1065 do_snmp_scan=do_snmp_scan,
1066 on_error=on_error,
1069 services = _get_discovered_services(node, node_ipaddress, node_sources, multi_host_sections,
1070 on_error)
1071 for (check_plugin_name, item), (check_source, paramstring) in services.items():
1072 descr = config.service_description(hostname, check_plugin_name, item)
1073 if hostname == config_cache.host_of_clustered_service(node, descr):
1074 if (check_plugin_name, item) not in cluster_items:
1075 cluster_items[(check_plugin_name, item)] = (check_source, paramstring)
1076 else:
1077 first_check_source, first_paramstring = cluster_items[(check_plugin_name, item)]
1078 if first_check_source == "old":
1079 pass
1080 elif check_source == "old":
1081 cluster_items[(check_plugin_name, item)] = (check_source, paramstring)
1082 elif first_check_source == "vanished" and check_source == "new":
1083 cluster_items[(check_plugin_name, item)] = ("old", first_paramstring)
1084 elif check_source == "vanished" and first_check_source == "new":
1085 cluster_items[(check_plugin_name, item)] = ("old", paramstring)
1086 # In all other cases either both must be "new" or "vanished" -> let it be
1088 # Now add manual and active serivce and handle ignored services
1089 _merge_manual_services(cluster_items, hostname, on_error)
1090 return cluster_items
1093 # Translates a parameter string (read from autochecks) to it's final value
1094 # (according to the current configuration)
1095 def resolve_paramstring(check_plugin_name, paramstring):
1096 check_context = config.get_check_context(check_plugin_name)
1097 # TODO: Can't we simply access check_context[paramstring]?
1098 return eval(paramstring, check_context, check_context)
1101 # Get the list of service of a host or cluster and guess the current state of
1102 # all services if possible
1103 # TODO: Can't we reduce the duplicate code here? Check out the "checking" code
1104 def get_check_preview(hostname, use_caches, do_snmp_scan, on_error):
1105 if config.is_cluster(hostname):
1106 ipaddress = None
1107 else:
1108 ipaddress = ip_lookup.lookup_ip_address(hostname)
1110 sources = _get_sources_for_discovery(
1111 hostname, ipaddress, check_plugin_names=None, do_snmp_scan=do_snmp_scan, on_error=on_error)
1113 multi_host_sections = _get_host_sections_for_discovery(sources, use_caches=use_caches)
1115 services = _get_host_services(hostname, ipaddress, sources, multi_host_sections, on_error)
1117 table = []
1118 for (check_plugin_name, item), (check_source, paramstring) in services.items():
1119 params = None
1120 exitcode = None
1121 perfdata = []
1122 if check_source not in ['legacy', 'active', 'custom']:
1123 # apply check_parameters
1124 try:
1125 if isinstance(paramstring, str):
1126 params = resolve_paramstring(check_plugin_name, paramstring)
1127 else:
1128 params = paramstring
1129 except Exception:
1130 raise MKGeneralException("Invalid check parameter string '%s'" % paramstring)
1132 try:
1133 descr = config.service_description(hostname, check_plugin_name, item)
1134 except Exception as e:
1135 if on_error == "raise":
1136 raise
1137 elif on_error == "warn":
1138 console.error("Invalid service description: %s\n" % e)
1139 else:
1140 continue # ignore
1142 check_api_utils.set_service(check_plugin_name, descr)
1143 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
1145 if check_plugin_name not in config.check_info:
1146 continue # Skip not existing check silently
1148 try:
1149 try:
1150 section_content = multi_host_sections.get_section_content(
1151 hostname, ipaddress, section_name, for_discovery=True)
1152 except MKParseFunctionError as e:
1153 if cmk.utils.debug.enabled() or on_error == "raise":
1154 x = e.exc_info()
1155 # re-raise the original exception to not destory the trace. This may raise a MKCounterWrapped
1156 # exception which need to lead to a skipped check instead of a crash
1157 raise x[0], x[1], x[2]
1158 else:
1159 raise
1160 except Exception as e:
1161 if cmk.utils.debug.enabled():
1162 raise
1163 exitcode = 3
1164 output = "Error: %s" % e
1166 # TODO: Move this to a helper function
1167 if section_content is None: # No data for this check type
1168 exitcode = 3
1169 output = "Received no data"
1171 if not section_content and cmk_base.check_utils.is_snmp_check(check_plugin_name) \
1172 and not config.check_info[check_plugin_name]["handle_empty_info"]:
1173 exitcode = 0
1174 output = "Received no data"
1176 item_state.set_item_state_prefix(check_plugin_name, item)
1178 if exitcode is None:
1179 check_function = config.check_info[check_plugin_name]["check_function"]
1180 if check_source != 'manual':
1181 params = check_table.get_precompiled_check_parameters(
1182 hostname, item,
1183 config.compute_check_parameters(hostname, check_plugin_name, item, params),
1184 check_plugin_name)
1185 else:
1186 params = check_table.get_precompiled_check_parameters(
1187 hostname, item, params, check_plugin_name)
1189 try:
1190 item_state.reset_wrapped_counters()
1191 result = checking.sanitize_check_result(
1192 check_function(item, checking.determine_check_params(params),
1193 section_content),
1194 cmk_base.check_utils.is_snmp_check(check_plugin_name))
1195 item_state.raise_counter_wrap()
1196 except item_state.MKCounterWrapped as e:
1197 result = (None, "WAITING - Counter based check, cannot be done offline")
1198 except Exception as e:
1199 if cmk.utils.debug.enabled():
1200 raise
1201 result = (
1202 3, "UNKNOWN - invalid output from agent or error in check implementation")
1203 if len(result) == 2:
1204 result = (result[0], result[1], [])
1205 exitcode, output, perfdata = result
1206 else:
1207 descr = item
1208 exitcode = None
1209 output = "WAITING - %s check, cannot be done offline" % check_source.title()
1210 perfdata = []
1212 if check_source == "active":
1213 params = resolve_paramstring(check_plugin_name, paramstring)
1215 if check_source in ["legacy", "active", "custom"]:
1216 checkgroup = None
1217 if config.service_ignored(hostname, None, descr):
1218 check_source = "%s_ignored" % check_source
1219 else:
1220 checkgroup = config.check_info[check_plugin_name]["group"]
1222 table.append((check_source, check_plugin_name, checkgroup, item, paramstring, params, descr,
1223 exitcode, output, perfdata))
1225 return table
1229 # .--Autochecks----------------------------------------------------------.
1230 # | _ _ _ _ |
1231 # | / \ _ _| |_ ___ ___| |__ ___ ___| | _____ |
1232 # | / _ \| | | | __/ _ \ / __| '_ \ / _ \/ __| |/ / __| |
1233 # | / ___ \ |_| | || (_) | (__| | | | __/ (__| <\__ \ |
1234 # | /_/ \_\__,_|\__\___/ \___|_| |_|\___|\___|_|\_\___/ |
1235 # | |
1236 # +----------------------------------------------------------------------+
1237 # | Reading, parsing, writing, modifying autochecks files |
1238 # '----------------------------------------------------------------------'
1241 # Read autochecks, but do not compute final check parameters,
1242 # also return a forth column with the raw string of the parameters.
1243 # Returns a table with three columns:
1244 # 1. check_plugin_name
1245 # 2. item
1246 # 3. parameter string, not yet evaluated!
1247 # TODO: use store.load_data_from_file()
1248 def parse_autochecks_file(hostname):
1249 def split_python_tuple(line):
1250 quote = None
1251 bracklev = 0
1252 backslash = False
1253 for i, c in enumerate(line):
1254 if backslash:
1255 backslash = False
1256 continue
1257 elif c == '\\':
1258 backslash = True
1259 elif c == quote:
1260 quote = None # end of quoted string
1261 elif c in ['"', "'"] and not quote:
1262 quote = c # begin of quoted string
1263 elif quote:
1264 continue
1265 elif c in ['(', '{', '[']:
1266 bracklev += 1
1267 elif c in [')', '}', ']']:
1268 bracklev -= 1
1269 elif bracklev > 0:
1270 continue
1271 elif c == ',':
1272 value = line[0:i]
1273 rest = line[i + 1:]
1274 return value.strip(), rest
1276 return line.strip(), None
1278 path = "%s/%s.mk" % (cmk.utils.paths.autochecks_dir, hostname)
1279 if not os.path.exists(path):
1280 return []
1282 lineno = 0
1283 table = []
1284 for line in file(path):
1285 lineno += 1
1286 try:
1287 line = line.strip()
1288 if not line.startswith("("):
1289 continue
1291 # drop everything after potential '#' (from older versions)
1292 i = line.rfind('#')
1293 if i > 0: # make sure # is not contained in string
1294 rest = line[i:]
1295 if '"' not in rest and "'" not in rest:
1296 line = line[:i].strip()
1298 if line.endswith(","):
1299 line = line[:-1]
1300 line = line[1:-1] # drop brackets
1302 # First try old format - with hostname
1303 parts = []
1304 while True:
1305 try:
1306 part, line = split_python_tuple(line)
1307 parts.append(part)
1308 except:
1309 break
1310 if len(parts) == 4:
1311 parts = parts[1:] # drop hostname, legacy format with host in first column
1312 elif len(parts) != 3:
1313 raise Exception("Invalid number of parts: %d (%r)" % (len(parts), parts))
1315 checktypestring, itemstring, paramstring = parts
1317 item = eval(itemstring)
1318 # With Check_MK 1.2.7i3 items are now defined to be unicode strings. Convert
1319 # items from existing autocheck files for compatibility. TODO remove this one day
1320 if isinstance(item, str):
1321 item = config.decode_incoming_string(item)
1323 table.append((eval(checktypestring), item, paramstring))
1324 except:
1325 if cmk.utils.debug.enabled():
1326 raise
1327 raise Exception("Invalid line %d in autochecks file %s" % (lineno, path))
1328 return table
1331 def _has_autochecks(hostname):
1332 return os.path.exists(cmk.utils.paths.autochecks_dir + "/" + hostname + ".mk")
1335 def _remove_autochecks_file(hostname):
1336 filepath = cmk.utils.paths.autochecks_dir + "/" + hostname + ".mk"
1337 try:
1338 os.remove(filepath)
1339 except OSError:
1340 pass
1343 # FIXME TODO: Consolidate with automation.py automation_write_autochecks_file()
1344 def _save_autochecks_file(hostname, items):
1345 if not os.path.exists(cmk.utils.paths.autochecks_dir):
1346 os.makedirs(cmk.utils.paths.autochecks_dir)
1347 filepath = "%s/%s.mk" % (cmk.utils.paths.autochecks_dir, hostname)
1348 out = file(filepath, "w")
1349 out.write("[\n")
1350 for check_plugin_name, item, paramstring in items:
1351 out.write(" (%r, %r, %s),\n" % (check_plugin_name, item, paramstring))
1352 out.write("]\n")
1355 def set_autochecks_of(hostname, new_items):
1356 # A Cluster does not have an autochecks file
1357 # All of its services are located in the nodes instead
1358 # So we cycle through all nodes remove all clustered service
1359 # and add the ones we've got from stdin
1360 config_cache = config.get_config_cache()
1361 if config.is_cluster(hostname):
1362 for node in config.nodes_of(hostname):
1363 new_autochecks = []
1364 existing = parse_autochecks_file(node)
1365 for check_plugin_name, item, paramstring in existing:
1366 descr = config.service_description(node, check_plugin_name, item)
1367 if hostname != config_cache.host_of_clustered_service(node, descr):
1368 new_autochecks.append((check_plugin_name, item, paramstring))
1370 for (check_plugin_name, item), paramstring in new_items.items():
1371 new_autochecks.append((check_plugin_name, item, paramstring))
1373 # write new autochecks file for that host
1374 _save_autochecks_file(node, new_autochecks)
1376 # Check whether or not the cluster host autocheck files are still
1377 # existant. Remove them. The autochecks are only stored in the nodes
1378 # autochecks files these days.
1379 _remove_autochecks_file(hostname)
1380 else:
1381 existing = parse_autochecks_file(hostname)
1382 # write new autochecks file, but take paramstrings from existing ones
1383 # for those checks which are kept
1384 new_autochecks = []
1385 for ct, item, paramstring in existing:
1386 if (ct, item) in new_items:
1387 new_autochecks.append((ct, item, paramstring))
1388 del new_items[(ct, item)]
1390 for (ct, item), paramstring in new_items.items():
1391 new_autochecks.append((ct, item, paramstring))
1393 # write new autochecks file for that host
1394 _save_autochecks_file(hostname, new_autochecks)
1397 # Remove all autochecks of a host while being cluster-aware!
1398 def remove_autochecks_of(hostname):
1399 removed = 0
1400 nodes = config.nodes_of(hostname)
1401 if nodes:
1402 for node in nodes:
1403 removed += _remove_autochecks_of_host(node)
1404 else:
1405 removed += _remove_autochecks_of_host(hostname)
1407 return removed
1410 def _remove_autochecks_of_host(hostname):
1411 old_items = parse_autochecks_file(hostname)
1412 removed = 0
1413 new_items = []
1414 config_cache = config.get_config_cache()
1415 for check_plugin_name, item, paramstring in old_items:
1416 descr = config.service_description(hostname, check_plugin_name, item)
1417 if hostname != config_cache.host_of_clustered_service(hostname, descr):
1418 new_items.append((check_plugin_name, item, paramstring))
1419 else:
1420 removed += 1
1421 _save_autochecks_file(hostname, new_items)
1422 return removed