Cleanup all direct config.service_extra_conf calls
[check_mk.git] / cmk_base / core_nagios.py
blobd123be7e0fd1c28542576e9f3aa2efd59ec2160d
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 support of Nagios (and compatible) cores"""
28 import os
29 import sys
30 import py_compile
31 import tempfile
32 import errno
33 from typing import Dict # pylint: disable=unused-import
35 import cmk.utils.paths
36 import cmk.utils.tty as tty
37 from cmk.utils.exceptions import MKGeneralException
39 import cmk_base.utils
40 import cmk_base.console as console
41 import cmk_base.config as config
42 import cmk_base.core_config as core_config
43 import cmk_base.ip_lookup as ip_lookup
44 import cmk_base.data_sources as data_sources
45 import cmk_base.check_utils
46 import cmk_base.check_api_utils as check_api_utils
49 class NagiosCore(core_config.MonitoringCore):
50 def create_config(self):
51 """Tries to create a new Check_MK object configuration file for the Nagios core
53 During create_config() exceptions may be raised which are caused by configuration issues.
54 Don't produce a half written object file. Simply throw away everything and keep the old file.
56 The user can then start the site with the old configuration and fix the configuration issue
57 while the monitoring is running.
58 """
59 tmp_path = None
60 try:
61 with tempfile.NamedTemporaryFile(
62 "w",
63 dir=os.path.dirname(cmk.utils.paths.nagios_objects_file),
64 prefix=".%s.new" % os.path.basename(cmk.utils.paths.nagios_objects_file),
65 delete=False) as tmp:
66 tmp_path = tmp.name
67 os.chmod(tmp.name, 0660)
68 create_config(tmp, hostnames=None)
69 os.rename(tmp.name, cmk.utils.paths.nagios_objects_file)
71 except Exception as e:
72 # In case an exception happens cleanup the tempfile created for writing
73 try:
74 if tmp_path:
75 os.unlink(tmp_path)
76 except IOError as e:
77 if e.errno == errno.ENOENT: # No such file or directory
78 pass
80 raise
82 def precompile(self):
83 console.output("Precompiling host checks...")
84 precompile_hostchecks()
85 console.output(tty.ok + "\n")
88 # .--Create config-------------------------------------------------------.
89 # | ____ _ __ _ |
90 # | / ___|_ __ ___ __ _| |_ ___ ___ ___ _ __ / _(_) __ _ |
91 # | | | | '__/ _ \/ _` | __/ _ \ / __/ _ \| '_ \| |_| |/ _` | |
92 # | | |___| | | __/ (_| | || __/ | (_| (_) | | | | _| | (_| | |
93 # | \____|_| \___|\__,_|\__\___| \___\___/|_| |_|_| |_|\__, | |
94 # | |___/ |
95 # +----------------------------------------------------------------------+
96 # | Create a configuration file for Nagios core with hosts + services |
97 # '----------------------------------------------------------------------'
100 class NagiosConfig(object):
101 def __init__(self, outfile, hostnames):
102 super(NagiosConfig, self).__init__()
103 self.outfile = outfile
104 self.hostnames = hostnames
106 self.hostgroups_to_define = set([])
107 self.servicegroups_to_define = set([])
108 self.contactgroups_to_define = set([])
109 self.checknames_to_define = set([])
110 self.active_checks_to_define = set([])
111 self.custom_commands_to_define = set([])
112 self.hostcheck_commands_to_define = []
115 def create_config(outfile, hostnames):
116 if config.host_notification_periods != []:
117 core_config.warning(
118 "host_notification_periods is not longer supported. Please use extra_host_conf['notification_period'] instead."
121 if config.service_notification_periods != []:
122 core_config.warning(
123 "service_notification_periods is not longer supported. Please use extra_service_conf['notification_period'] instead."
126 # Map service_period to _SERVICE_PERIOD. This field das not exist in Nagios.
127 # The CMC has this field natively.
128 if "service_period" in config.extra_host_conf:
129 config.extra_host_conf["_SERVICE_PERIOD"] = config.extra_host_conf["service_period"]
130 del config.extra_host_conf["service_period"]
131 if "service_period" in config.extra_service_conf:
132 config.extra_service_conf["_SERVICE_PERIOD"] = config.extra_service_conf["service_period"]
133 del config.extra_service_conf["service_period"]
135 if hostnames is None:
136 hostnames = config.all_active_hosts()
138 cfg = NagiosConfig(outfile, hostnames)
139 config_cache = config.get_config_cache()
141 _output_conf_header(cfg)
143 for hostname in hostnames:
144 _create_nagios_config_host(cfg, config_cache, hostname)
146 _create_nagios_config_contacts(cfg, hostnames)
147 _create_nagios_config_hostgroups(cfg)
148 _create_nagios_config_servicegroups(cfg)
149 _create_nagios_config_contactgroups(cfg)
150 _create_nagios_config_commands(cfg)
151 _create_nagios_config_timeperiods(cfg)
153 if config.extra_nagios_conf:
154 outfile.write("\n# extra_nagios_conf\n\n")
155 outfile.write(config.extra_nagios_conf)
158 def _output_conf_header(cfg):
159 cfg.outfile.write("""#
160 # Created by Check_MK. Do not edit.
163 """)
166 def _create_nagios_config_host(cfg, config_cache, hostname):
167 cfg.outfile.write("\n# ----------------------------------------------------\n")
168 cfg.outfile.write("# %s\n" % hostname)
169 cfg.outfile.write("# ----------------------------------------------------\n")
170 host_attrs = core_config.get_host_attributes(hostname, config_cache)
171 if config.generate_hostconf:
172 _create_nagios_hostdefs(cfg, config_cache, hostname, host_attrs)
173 _create_nagios_servicedefs(cfg, config_cache, hostname, host_attrs)
176 def _create_nagios_hostdefs(cfg, config_cache, hostname, attrs):
177 is_clust = config.is_cluster(hostname)
179 ip = attrs["address"]
181 if is_clust:
182 nodes = core_config.get_cluster_nodes_for_config(hostname)
183 attrs.update(core_config.get_cluster_attributes(hostname, nodes))
186 # / |
187 # | |
188 # | |
189 # |_| 1. normal, physical hosts
191 alias = hostname
192 host_spec = {
193 "host_name": hostname,
194 "use": config.cluster_template if is_clust else config.host_template,
195 "address": ip if ip else core_config.fallback_ip_for(hostname),
198 # Add custom macros
199 for key, value in attrs.items():
200 if key[0] == '_':
201 host_spec[key] = value
203 # Host check command might differ from default
204 command = core_config.host_check_command(
205 hostname, ip, is_clust, cfg.hostcheck_commands_to_define, cfg.custom_commands_to_define)
206 if command:
207 host_spec["check_command"] = command
209 # Host groups: If the host has no hostgroups it gets the default
210 # hostgroup (Nagios requires each host to be member of at least on
211 # group.
212 hgs = config.hostgroups_of(hostname)
213 hostgroups = ",".join(hgs)
214 if len(hgs) == 0:
215 hostgroups = config.default_host_group
216 cfg.hostgroups_to_define.add(config.default_host_group)
217 elif config.define_hostgroups:
218 cfg.hostgroups_to_define.update(hgs)
219 host_spec["hostgroups"] = hostgroups
221 # Contact groups
222 cgrs = config.contactgroups_of(hostname)
223 if len(cgrs) > 0:
224 host_spec["contact_groups"] = ",".join(cgrs)
225 cfg.contactgroups_to_define.update(cgrs)
227 if not is_clust:
228 # Parents for non-clusters
230 # Get parents manually defined via extra_host_conf["parents"]. Only honor
231 # variable "parents" and implicit parents if this setting is empty
232 extra_conf_parents = config_cache.host_extra_conf(hostname,
233 config.extra_host_conf.get("parents", []))
235 if not extra_conf_parents:
236 parents_list = config.parents_of(hostname)
237 if parents_list:
238 host_spec["parents"] = ",".join(parents_list)
240 elif is_clust:
241 # Special handling of clusters
242 alias = "cluster of %s" % ", ".join(nodes)
243 host_spec["parents"] = ",".join(nodes)
245 # Output alias, but only if it's not defined in extra_host_conf
246 alias = config.alias_of(hostname, None)
247 if alias is not None:
248 host_spec["alias"] = alias
250 # Custom configuration last -> user may override all other values
251 host_spec.update(
252 _extra_host_conf_of(config_cache, hostname, exclude=["parents"] if is_clust else []))
253 cfg.outfile.write(_format_nagios_object("host", host_spec).encode("utf-8"))
256 def _create_nagios_servicedefs(cfg, config_cache, hostname, host_attrs):
257 outfile = cfg.outfile
258 import cmk_base.check_table as check_table
260 host_config = config_cache.get_host_config(hostname)
262 # _____
263 # |___ /
264 # |_ \
265 # ___) |
266 # |____/ 3. Services
268 def do_omit_service(hostname, description):
269 if config.service_ignored(hostname, None, description):
270 return True
271 if hostname != config_cache.host_of_clustered_service(hostname, description):
272 return True
273 return False
275 def get_dependencies(hostname, servicedesc):
276 result = ""
277 for dep in config.service_depends_on(hostname, servicedesc):
278 result += _format_nagios_object(
279 "servicedependency", {
280 "use": config.service_dependency_template,
281 "host_name": hostname,
282 "service_description": dep,
283 "dependent_host_name": hostname,
284 "dependent_service_description": servicedesc,
287 return result
289 host_checks = check_table.get_check_table(hostname, remove_duplicates=True).items()
290 host_checks.sort() # Create deterministic order
291 have_at_least_one_service = False
292 used_descriptions = {}
293 for ((checkname, item), (params, description, deps)) in host_checks:
294 if checkname not in config.check_info:
295 continue # simply ignore missing checks
297 description = config.get_final_service_description(hostname, description)
298 # Make sure, the service description is unique on this host
299 if description in used_descriptions:
300 cn, it = used_descriptions[description]
301 core_config.warning("ERROR: Duplicate service description '%s' for host '%s'!\n"
302 " - 1st occurrance: checktype = %s, item = %r\n"
303 " - 2nd occurrance: checktype = %s, item = %r\n" %
304 (description, hostname, cn, it, checkname, item))
305 continue
307 else:
308 used_descriptions[description] = (checkname, item)
309 if config.check_info[checkname].get("has_perfdata", False):
310 template = config.passive_service_template_perf
311 else:
312 template = config.passive_service_template
314 # Services Dependencies
315 for dep in deps:
316 outfile.write(
317 _format_nagios_object(
318 "servicedependency", {
319 "use": config.service_dependency_template,
320 "host_name": hostname,
321 "service_description": dep,
322 "dependent_host_name": hostname,
323 "dependent_service_description": description,
324 }).encode("utf-8"))
326 # Add the check interval of either the Check_MK service or
327 # (if configured) the snmp_check_interval for snmp based checks
328 check_interval = 1 # default hardcoded interval
329 # Customized interval of Check_MK service
330 values = config_cache.service_extra_conf(
331 hostname, "Check_MK", config.extra_service_conf.get('check_interval', []))
332 if values:
333 try:
334 check_interval = int(values[0])
335 except:
336 check_interval = float(values[0])
337 value = config.check_interval_of(hostname, cmk_base.check_utils.section_name_of(checkname))
338 if value is not None:
339 check_interval = value
341 service_spec = {
342 "use": template,
343 "host_name": hostname,
344 "service_description": description,
345 "check_interval": check_interval,
346 "check_command": "check_mk-%s" % checkname,
349 service_spec.update(
350 core_config.get_service_attributes(
351 hostname, description, config_cache, checkname=checkname, params=params))
352 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, description))
354 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
356 cfg.checknames_to_define.add(checkname)
357 have_at_least_one_service = True
359 # Active check for check_mk
360 if have_at_least_one_service:
361 service_spec = {
362 "use": config.active_service_template,
363 "host_name": hostname,
364 "service_description": "Check_MK",
366 service_spec.update(core_config.get_service_attributes(hostname, "Check_MK", config_cache))
367 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, "Check_MK"))
368 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
370 # legacy checks via legacy_checks
371 legchecks = config_cache.host_extra_conf(hostname, config.legacy_checks)
372 if len(legchecks) > 0:
373 outfile.write("\n\n# Legacy checks\n")
374 for command, description, has_perfdata in legchecks:
375 description = config.get_final_service_description(hostname, description)
376 if do_omit_service(hostname, description):
377 continue
379 if description in used_descriptions:
380 cn, it = used_descriptions[description]
381 core_config.warning(
382 "ERROR: Duplicate service description (legacy check) '%s' for host '%s'!\n"
383 " - 1st occurrance: checktype = %s, item = %r\n"
384 " - 2nd occurrance: checktype = legacy(%s), item = None\n" % (description, hostname,
385 cn, it, command))
386 continue
388 else:
389 used_descriptions[description] = ("legacy(" + command + ")", description)
391 if has_perfdata:
392 template = "check_mk_perf,"
393 else:
394 template = ""
396 service_spec = {
397 "use": "%scheck_mk_default" % template,
398 "host_name": hostname,
399 "service_description": description,
400 "check_command": _simulate_command(cfg, command),
401 "active_checks_enabled": 1,
403 service_spec.update(core_config.get_service_attributes(hostname, description, config_cache))
404 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, description))
405 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
407 # write service dependencies for legacy checks
408 outfile.write(get_dependencies(hostname, description).encode("utf-8"))
410 # legacy checks via active_checks
411 actchecks = []
412 for acttype, rules in config.active_checks.items():
413 entries = config_cache.host_extra_conf(hostname, rules)
414 if entries:
415 # Skip Check_MK HW/SW Inventory for all ping hosts, even when the user has enabled
416 # the inventory for ping only hosts
417 if acttype == "cmk_inv" and host_config.is_ping_host:
418 continue
420 cfg.active_checks_to_define.add(acttype)
421 act_info = config.active_check_info[acttype]
422 for params in entries:
423 actchecks.append((acttype, act_info, params))
425 if actchecks:
426 outfile.write("\n\n# Active checks\n")
427 for acttype, act_info, params in actchecks:
428 # Make hostname available as global variable in argument functions
429 check_api_utils.set_hostname(hostname)
431 has_perfdata = act_info.get('has_perfdata', False)
432 description = config.active_check_service_description(hostname, acttype, params)
434 if do_omit_service(hostname, description):
435 continue
437 # compute argument, and quote ! and \ for Nagios
438 args = core_config.active_check_arguments(
439 hostname, description, act_info["argument_function"](params)).replace(
440 "\\", "\\\\").replace("!", "\\!")
442 if description in used_descriptions:
443 cn, it = used_descriptions[description]
444 # If we have the same active check again with the same description,
445 # then we do not regard this as an error, but simply ignore the
446 # second one. That way one can override a check with other settings.
447 if cn == "active(%s)" % acttype:
448 continue
450 core_config.warning(
451 "ERROR: Duplicate service description (active check) '%s' for host '%s'!\n"
452 " - 1st occurrance: checktype = %s, item = %r\n"
453 " - 2nd occurrance: checktype = active(%s), item = None\n" %
454 (description, hostname, cn, it, acttype))
455 continue
457 else:
458 used_descriptions[description] = ("active(" + acttype + ")", description)
460 template = "check_mk_perf," if has_perfdata else ""
462 if host_attrs["address"] in ["0.0.0.0", "::"]:
463 command_name = "check-mk-custom"
464 command = command_name + "!echo \"CRIT - Failed to lookup IP address and no explicit IP address configured\" && exit 2"
465 cfg.custom_commands_to_define.add(command_name)
466 else:
467 command = "check_mk_active-%s!%s" % (acttype, args)
469 service_spec = {
470 "use": "%scheck_mk_default" % template,
471 "host_name": hostname,
472 "service_description": description,
473 "check_command": _simulate_command(cfg, command),
474 "active_checks_enabled": 1,
476 service_spec.update(
477 core_config.get_service_attributes(hostname, description, config_cache))
478 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, description))
479 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
481 # write service dependencies for active checks
482 outfile.write(get_dependencies(hostname, description).encode("utf-8"))
484 # Legacy checks via custom_checks
485 custchecks = config_cache.host_extra_conf(hostname, config.custom_checks)
486 if custchecks:
487 outfile.write("\n\n# Custom checks\n")
488 for entry in custchecks:
489 # entries are dicts with the following keys:
490 # "service_description" Service description to use
491 # "command_line" (optional) Unix command line for executing the check
492 # If this is missing, we create a passive check
493 # "command_name" (optional) Name of Monitoring command to define. If missing,
494 # we use "check-mk-custom"
495 # "has_perfdata" (optional) If present and True, we activate perf_data
496 description = config.get_final_service_description(hostname,
497 entry["service_description"])
498 has_perfdata = entry.get("has_perfdata", False)
499 command_name = entry.get("command_name", "check-mk-custom")
500 command_line = entry.get("command_line", "")
502 if do_omit_service(hostname, description):
503 continue
505 if command_line:
506 command_line = core_config.autodetect_plugin(command_line).replace("\\",
507 "\\\\").replace(
508 "!", "\\!")
510 if "freshness" in entry:
511 freshness = {
512 "check_freshness": 1,
513 "freshness_threshold": 60 * entry["freshness"]["interval"],
515 command_line = "echo %s && exit %d" % (_quote_nagios_string(
516 entry["freshness"]["output"]), entry["freshness"]["state"])
517 else:
518 freshness = {}
520 cfg.custom_commands_to_define.add(command_name)
522 if description in used_descriptions:
523 cn, it = used_descriptions[description]
524 # If we have the same active check again with the same description,
525 # then we do not regard this as an error, but simply ignore the
526 # second one.
527 if cn == "custom(%s)" % command_name:
528 continue
529 core_config.warning(
530 "ERROR: Duplicate service description (custom check) '%s' for host '%s'!\n"
531 " - 1st occurrance: checktype = %s, item = %r\n"
532 " - 2nd occurrance: checktype = custom(%s), item = %r\n" %
533 (description, hostname, cn, it, command_name, description))
534 continue
535 else:
536 used_descriptions[description] = ("custom(%s)" % command_name, description)
538 template = "check_mk_perf," if has_perfdata else ""
539 command = "%s!%s" % (command_name, command_line)
541 service_spec = {
542 "use": "%scheck_mk_default" % template,
543 "host_name": hostname,
544 "service_description": description,
545 "check_command": _simulate_command(cfg, command),
546 "active_checks_enabled": 1 if (command_line and not freshness) else 0,
548 service_spec.update(freshness)
549 service_spec.update(
550 core_config.get_service_attributes(hostname, description, config_cache))
551 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, description))
552 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
554 # write service dependencies for custom checks
555 outfile.write(get_dependencies(hostname, description).encode("utf-8"))
557 # FIXME: Remove old name one day
558 service_discovery_name = 'Check_MK inventory'
559 if 'cmk-inventory' in config.use_new_descriptions_for:
560 service_discovery_name = 'Check_MK Discovery'
562 import cmk_base.discovery as discovery
563 params = discovery.discovery_check_parameters(hostname) or \
564 discovery.default_discovery_check_parameters()
566 # Inventory checks - if user has configured them.
567 if params["check_interval"] \
568 and not config.service_ignored(hostname, None, service_discovery_name) \
569 and not host_config.is_ping_host:
570 service_spec = {
571 "use": config.inventory_check_template,
572 "host_name": hostname,
573 "normal_check_interval": params["check_interval"],
574 "retry_check_interval": params["check_interval"],
575 "service_description": service_discovery_name,
577 service_spec.update(
578 core_config.get_service_attributes(hostname, service_discovery_name, config_cache))
579 service_spec.update(
580 _extra_service_conf_of(cfg, config_cache, hostname, service_discovery_name))
581 outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
583 if have_at_least_one_service:
584 outfile.write(
585 _format_nagios_object(
586 "servicedependency", {
587 "use": config.service_dependency_template,
588 "host_name": hostname,
589 "service_description": "Check_MK",
590 "dependent_host_name": hostname,
591 "dependent_service_description": service_discovery_name,
592 }).encode("utf-8"))
594 # No check_mk service, no legacy service -> create PING service
595 if not have_at_least_one_service and not legchecks and not actchecks and not custchecks:
596 _add_ping_service(cfg, config_cache, hostname, host_attrs["address"],
597 config.is_ipv6_primary(hostname) and 6 or 4, "PING",
598 host_attrs.get("_NODEIPS"))
600 if config.is_ipv4v6_host(hostname):
601 if config.is_ipv6_primary(hostname):
602 _add_ping_service(cfg, config_cache, hostname, host_attrs["_ADDRESS_4"], 4, "PING IPv4",
603 host_attrs.get("_NODEIPS_4"))
604 else:
605 _add_ping_service(cfg, config_cache, hostname, host_attrs["_ADDRESS_6"], 6, "PING IPv6",
606 host_attrs.get("_NODEIPS_6"))
609 def _add_ping_service(cfg, config_cache, hostname, ipaddress, family, descr, node_ips):
610 arguments = core_config.check_icmp_arguments_of(hostname, family=family)
612 ping_command = 'check-mk-ping'
613 if config.is_cluster(hostname):
614 arguments += ' -m 1 ' + node_ips
615 else:
616 arguments += ' ' + ipaddress
618 service_spec = {
619 "use": config.pingonly_template,
620 "host_name": hostname,
621 "service_description": descr,
622 "check_command": "%s!%s" % (ping_command, arguments),
624 service_spec.update(core_config.get_service_attributes(hostname, descr, config_cache))
625 service_spec.update(_extra_service_conf_of(cfg, config_cache, hostname, descr))
626 cfg.outfile.write(_format_nagios_object("service", service_spec).encode("utf-8"))
629 def _format_nagios_object(object_type, object_spec):
630 cfg = ["define %s {" % object_type]
631 for key, val in sorted(object_spec.iteritems(), key=lambda x: x[0]):
632 cfg.append(" %-29s %s" % (key, val))
633 cfg.append("}")
635 return u"\n".join(cfg) + "\n\n"
638 def _simulate_command(cfg, command):
639 if config.simulation_mode:
640 cfg.custom_commands_to_define.add("check-mk-simulation")
641 return "check-mk-simulation!echo 'Simulation mode - cannot execute real check'"
642 return command
645 def _create_nagios_config_hostgroups(cfg):
646 outfile = cfg.outfile
647 if config.define_hostgroups:
648 outfile.write("\n# ------------------------------------------------------------\n")
649 outfile.write("# Host groups (controlled by define_hostgroups)\n")
650 outfile.write("# ------------------------------------------------------------\n")
651 hgs = list(cfg.hostgroups_to_define)
652 hgs.sort()
653 for hg in hgs:
654 try:
655 alias = config.define_hostgroups[hg]
656 except:
657 alias = hg
659 outfile.write(
660 _format_nagios_object("hostgroup", {
661 "hostgroup_name": hg,
662 "alias": alias,
663 }).encode("utf-8"))
665 # No creation of host groups but we need to define
666 # default host group
667 elif config.default_host_group in cfg.hostgroups_to_define:
668 outfile.write(
669 _format_nagios_object("hostgroup", {
670 "hostgroup_name": config.default_host_group,
671 "alias": "Check_MK default hostgroup",
672 }).encode("utf-8"))
675 def _create_nagios_config_servicegroups(cfg):
676 outfile = cfg.outfile
677 if config.define_servicegroups:
678 outfile.write("\n# ------------------------------------------------------------\n")
679 outfile.write("# Service groups (controlled by define_servicegroups)\n")
680 outfile.write("# ------------------------------------------------------------\n")
681 sgs = list(cfg.servicegroups_to_define)
682 sgs.sort()
683 for sg in sgs:
684 try:
685 alias = config.define_servicegroups[sg]
686 except:
687 alias = sg
689 outfile.write(
690 _format_nagios_object("servicegroup", {
691 "servicegroup_name": sg,
692 "alias": alias,
693 }).encode("utf-8"))
696 def _create_nagios_config_contactgroups(cfg):
697 if config.define_contactgroups is False:
698 return
700 cgs = list(cfg.contactgroups_to_define)
701 if not cgs:
702 return
704 cfg.outfile.write("\n# ------------------------------------------------------------\n")
705 cfg.outfile.write("# Contact groups (controlled by define_contactgroups)\n")
706 cfg.outfile.write("# ------------------------------------------------------------\n\n")
707 for name in sorted(cgs):
708 if isinstance(config.define_contactgroups, dict):
709 alias = config.define_contactgroups.get(name, name)
710 else:
711 alias = name
713 contactgroup_spec = {
714 "contactgroup_name": name,
715 "alias": alias,
718 members = config.contactgroup_members.get(name)
719 if members:
720 contactgroup_spec["members"] = ",".join(members)
722 cfg.outfile.write(_format_nagios_object("contactgroup", contactgroup_spec).encode("utf-8"))
725 def _create_nagios_config_commands(cfg):
726 outfile = cfg.outfile
727 if config.generate_dummy_commands:
728 outfile.write("\n# ------------------------------------------------------------\n")
729 outfile.write("# Dummy check commands and active check commands\n")
730 outfile.write("# ------------------------------------------------------------\n\n")
731 for checkname in cfg.checknames_to_define:
732 outfile.write(
733 _format_nagios_object(
734 "command", {
735 "command_name": "check_mk-%s" % checkname,
736 "command_line": config.dummy_check_commandline,
737 }).encode("utf-8"))
739 # active_checks
740 for acttype in cfg.active_checks_to_define:
741 act_info = config.active_check_info[acttype]
742 outfile.write(
743 _format_nagios_object(
744 "command", {
745 "command_name": "check_mk_active-%s" % acttype,
746 "command_line": act_info["command_line"],
747 }).encode("utf-8"))
749 # custom_checks
750 for command_name in cfg.custom_commands_to_define:
751 outfile.write(
752 _format_nagios_object("command", {
753 "command_name": command_name,
754 "command_line": "$ARG1$",
755 }).encode("utf-8"))
757 # custom host checks
758 for command_name, command_line in cfg.hostcheck_commands_to_define:
759 outfile.write(
760 _format_nagios_object("command", {
761 "command_name": command_name,
762 "command_line": command_line,
763 }).encode("utf-8"))
766 def _create_nagios_config_timeperiods(cfg):
767 if len(config.timeperiods) > 0:
768 cfg.outfile.write("\n# ------------------------------------------------------------\n")
769 cfg.outfile.write("# Timeperiod definitions (controlled by variable 'timeperiods')\n")
770 cfg.outfile.write("# ------------------------------------------------------------\n\n")
771 tpnames = config.timeperiods.keys()
772 tpnames.sort()
773 for name in tpnames:
774 tp = config.timeperiods[name]
775 timeperiod_spec = {
776 "timeperiod_name": name,
779 if "alias" in tp:
780 timeperiod_spec["alias"] = tp["alias"]
782 for key, value in tp.items():
783 if key not in ["alias", "exclude"]:
784 times = ",".join([("%s-%s" % (fr, to)) for (fr, to) in value])
785 if times:
786 timeperiod_spec[key] = times
788 if "exclude" in tp:
789 timeperiod_spec["exclude"] = ",".join(tp["exclude"])
791 cfg.outfile.write(_format_nagios_object("timeperiod", timeperiod_spec).encode("utf-8"))
794 def _create_nagios_config_contacts(cfg, hostnames):
795 outfile = cfg.outfile
796 if len(config.contacts) > 0:
797 outfile.write("\n# ------------------------------------------------------------\n")
798 outfile.write("# Contact definitions (controlled by variable 'contacts')\n")
799 outfile.write("# ------------------------------------------------------------\n\n")
800 cnames = config.contacts.keys()
801 cnames.sort()
802 for cname in cnames:
803 contact = config.contacts[cname]
804 # Create contact groups in nagios, even when they are empty. This is needed
805 # for RBN to work correctly when using contactgroups as recipients which are
806 # not assigned to any host
807 cfg.contactgroups_to_define.update(contact.get("contactgroups", []))
808 # If the contact is in no contact group or all of the contact groups
809 # of the contact have neither hosts nor services assigned - in other
810 # words if the contact is not assigned to any host or service, then
811 # we do not create this contact in Nagios. It's useless and will produce
812 # warnings.
813 cgrs = [
814 cgr for cgr in contact.get("contactgroups", [])
815 if cgr in cfg.contactgroups_to_define
817 if not cgrs:
818 continue
820 contact_spec = {
821 "contact_name": cname,
824 if "alias" in contact:
825 contact_spec["alias"] = contact["alias"]
827 if "email" in contact:
828 contact_spec["email"] = contact["email"]
830 if "pager" in contact:
831 contact_spec["pager"] = contact["pager"]
833 if config.enable_rulebased_notifications:
834 not_enabled = False
835 else:
836 not_enabled = contact.get("notifications_enabled", True)
838 for what in ["host", "service"]:
839 no = contact.get(what + "_notification_options", "")
841 if not no or not not_enabled:
842 contact_spec["%s_notifications_enabled" % what] = 0
843 no = "n"
845 contact_spec.update({
846 "%s_notification_options" % what: ",".join(list(no)),
847 "%s_notification_period" % what: contact.get("notification_period", "24X7"),
848 "%s_notification_commands" % what: contact.get(
849 "%s_notification_commands" % what, "check-mk-notify"),
852 # Add custom macros
853 for macro in [m for m in contact.keys() if m.startswith('_')]:
854 contact_spec[macro] = contact[macro]
856 contact_spec["contactgroups"] = ", ".join(cgrs)
857 cfg.outfile.write(_format_nagios_object("contact", contact_spec).encode("utf-8"))
859 if config.enable_rulebased_notifications and hostnames:
860 cfg.contactgroups_to_define.add("check-mk-notify")
861 outfile.write("# Needed for rule based notifications\n")
862 outfile.write(
863 _format_nagios_object(
864 "contact", {
865 "contact_name": "check-mk-notify",
866 "alias": "Contact for rule based notifications",
867 "host_notification_options": "d,u,r,f,s",
868 "service_notification_options": "u,c,w,r,f,s",
869 "host_notification_period": "24X7",
870 "service_notification_period": "24X7",
871 "host_notification_commands": "check-mk-notify",
872 "service_notification_commands": "check-mk-notify",
873 "contactgroups": "check-mk-notify",
874 }).encode("utf-8"))
877 # Quote string for use in a nagios command execution.
878 # Please note that also quoting for ! and \ vor Nagios
879 # itself takes place here.
880 def _quote_nagios_string(s):
881 return "'" + s.replace('\\', '\\\\').replace("'", "'\"'\"'").replace('!', '\\!') + "'"
884 def _extra_host_conf_of(config_cache, hostname, exclude=None):
885 if exclude is None:
886 exclude = []
887 return _extra_conf_of(config_cache, config.extra_host_conf, hostname, None, exclude)
890 # Collect all extra configuration data for a service
891 def _extra_service_conf_of(cfg, config_cache, hostname, description):
892 service_spec = {}
894 # Contact groups
895 sercgr = config_cache.service_extra_conf(hostname, description, config.service_contactgroups)
896 cfg.contactgroups_to_define.update(sercgr)
897 if len(sercgr) > 0:
898 if config.enable_rulebased_notifications:
899 sercgr.append("check-mk-notify") # not nessary if not explicit groups defined
900 service_spec["contact_groups"] = ",".join(sercgr)
902 sergr = config_cache.service_extra_conf(hostname, description, config.service_groups)
903 if len(sergr) > 0:
904 service_spec["service_groups"] = ",".join(sergr)
905 if config.define_servicegroups:
906 cfg.servicegroups_to_define.update(sergr)
907 service_spec.update(
908 _extra_conf_of(config_cache, config.extra_service_conf, hostname, description))
910 return service_spec
913 def _extra_conf_of(config_cache, confdict, hostname, service, exclude=None):
914 if exclude is None:
915 exclude = []
917 result = {}
918 for key, conflist in confdict.items():
919 if service is not None:
920 values = config_cache.service_extra_conf(hostname, service, conflist)
921 else:
922 values = config_cache.host_extra_conf(hostname, conflist)
924 if exclude and key in exclude:
925 continue
927 if values:
928 result[key] = values[0]
930 return result
934 # .--Precompile----------------------------------------------------------.
935 # | ____ _ _ |
936 # | | _ \ _ __ ___ ___ ___ _ __ ___ _ __ (_) | ___ |
937 # | | |_) | '__/ _ \/ __/ _ \| '_ ` _ \| '_ \| | |/ _ \ |
938 # | | __/| | | __/ (_| (_) | | | | | | |_) | | | __/ |
939 # | |_| |_| \___|\___\___/|_| |_| |_| .__/|_|_|\___| |
940 # | |_| |
941 # +----------------------------------------------------------------------+
942 # | Precompiling creates on dedicated Python file per host, which just |
943 # | contains that code and information that is needed for executing all |
944 # | checks of that host. Also static data that cannot change during the |
945 # | normal monitoring process is being precomputed and hard coded. This |
946 # | all saves substantial CPU resources as opposed to running Check_MK |
947 # | in adhoc mode (about 75%). |
948 # '----------------------------------------------------------------------'
951 # Find files to be included in precompile host check for a certain
952 # check (for example df or mem.used). In case of checks with a period
953 # (subchecks) we might have to include both "mem" and "mem.used". The
954 # subcheck *may* be implemented in a separate file.
955 def _find_check_plugins(checktype):
956 if '.' in checktype:
957 candidates = [cmk_base.check_utils.section_name_of(checktype), checktype]
958 else:
959 candidates = [checktype]
961 paths = []
962 for candidate in candidates:
963 filename = cmk.utils.paths.local_checks_dir + "/" + candidate
964 if os.path.exists(filename):
965 paths.append(filename)
966 continue
968 filename = cmk.utils.paths.checks_dir + "/" + candidate
969 if os.path.exists(filename):
970 paths.append(filename)
972 return paths
975 def precompile_hostchecks():
976 console.verbose("Creating precompiled host check config...\n")
977 config.PackedConfig().save()
979 if not os.path.exists(cmk.utils.paths.precompiled_hostchecks_dir):
980 os.makedirs(cmk.utils.paths.precompiled_hostchecks_dir)
982 console.verbose("Precompiling host checks...\n")
983 for host in config.all_active_hosts():
984 try:
985 _precompile_hostcheck(host)
986 except Exception as e:
987 if cmk.utils.debug.enabled():
988 raise
989 console.error("Error precompiling checks for host %s: %s\n" % (host, e))
990 sys.exit(5)
993 # read python file and strip comments
994 g_stripped_file_cache = {} # type: Dict[str, str]
997 def stripped_python_file(filename):
998 if filename in g_stripped_file_cache:
999 return g_stripped_file_cache[filename]
1000 a = ""
1001 for line in file(filename):
1002 l = line.strip()
1003 if l == "" or l[0] != '#':
1004 a += line # not stripped line because of indentation!
1005 g_stripped_file_cache[filename] = a
1006 return a
1009 def _precompile_hostcheck(hostname):
1010 import cmk_base.check_table as check_table
1012 console.verbose("%s%s%-16s%s:", tty.bold, tty.blue, hostname, tty.normal, stream=sys.stderr)
1014 check_api_utils.set_hostname(hostname)
1016 compiled_filename = cmk.utils.paths.precompiled_hostchecks_dir + "/" + hostname
1017 source_filename = compiled_filename + ".py"
1018 for fname in [compiled_filename, source_filename]:
1019 try:
1020 os.remove(fname)
1021 except:
1022 pass
1024 # check table, enriched with addition precompiled information.
1025 host_check_table = check_table.get_precompiled_check_table(
1026 hostname, filter_mode="include_clustered", skip_ignored=False)
1027 if not host_check_table:
1028 console.verbose("(no Check_MK checks)\n")
1029 return
1031 output = file(source_filename + ".new", "w")
1032 output.write("#!/usr/bin/env python\n")
1033 output.write("# encoding: utf-8\n\n")
1035 output.write("import sys\n\n")
1037 output.write("if not sys.executable.startswith('/omd'):\n")
1038 output.write(" sys.stdout.write(\"ERROR: Only executable with sites python\\n\")\n")
1039 output.write(" sys.exit(2)\n\n")
1041 # Remove precompiled directory from sys.path. Leaving it in the path
1042 # makes problems when host names (name of precompiled files) are equal
1043 # to python module names like "random"
1044 output.write("sys.path.pop(0)\n")
1046 output.write("import cmk.utils.log\n")
1047 output.write("import cmk.utils.debug\n")
1048 output.write("from cmk.utils.exceptions import MKTerminate\n")
1049 output.write("\n")
1050 output.write("import cmk_base.utils\n")
1051 output.write("import cmk_base.config as config\n")
1052 output.write("import cmk_base.console as console\n")
1053 output.write("import cmk_base.checking as checking\n")
1054 output.write("import cmk_base.check_api as check_api\n")
1055 output.write("import cmk_base.ip_lookup as ip_lookup\n")
1057 # Self-compile: replace symlink with precompiled python-code, if
1058 # we are run for the first time
1059 if config.delay_precompile:
1060 output.write("""
1061 import os
1062 if os.path.islink(%(dst)r):
1063 import py_compile
1064 os.remove(%(dst)r)
1065 py_compile.compile(%(src)r, %(dst)r, %(dst)r, True)
1066 os.chmod(%(dst)r, 0755)
1068 """ % {
1069 "src": source_filename,
1070 "dst": compiled_filename
1073 # Register default Check_MK signal handler
1074 output.write("cmk_base.utils.register_sigint_handler()\n")
1076 # initialize global variables
1077 output.write("""
1078 # very simple commandline parsing: only -v (once or twice) and -d are supported
1080 cmk.utils.log.setup_console_logging()
1081 logger = cmk.utils.log.get_logger("base")
1083 # TODO: This is not really good parsing, because it not cares about syntax like e.g. "-nv".
1084 # The later regular argument parsing is handling this correctly. Try to clean this up.
1085 cmk.utils.log.set_verbosity(verbosity=len([ a for a in sys.argv if a in [ "-v", "--verbose"] ]))
1087 if '-d' in sys.argv:
1088 cmk.utils.debug.enable()
1090 """)
1092 needed_check_plugin_names = _get_needed_check_plugin_names(hostname, host_check_table)
1094 output.write("config.load_checks(check_api.get_check_api_context, %r)\n" %
1095 _get_needed_check_file_names(needed_check_plugin_names))
1097 for check_plugin_name in sorted(needed_check_plugin_names):
1098 console.verbose(" %s%s%s", tty.green, check_plugin_name, tty.normal, stream=sys.stderr)
1100 output.write("config.load_packed_config()\n")
1102 # handling of clusters
1103 if config.is_cluster(hostname):
1104 cluster_nodes = config.nodes_of(hostname)
1105 output.write("config.is_cluster = lambda h: h == %r\n" % hostname)
1107 nodes_of_map = {hostname: cluster_nodes}
1108 for node in config.nodes_of(hostname):
1109 nodes_of_map[node] = None
1110 output.write("config.nodes_of = lambda h: %r[h]\n" % nodes_of_map)
1111 else:
1112 output.write("config.is_cluster = lambda h: False\n")
1113 output.write("config.nodes_of = lambda h: None\n")
1115 # IP addresses
1116 needed_ipaddresses, needed_ipv6addresses, = {}, {}
1117 if config.is_cluster(hostname):
1118 for node in config.nodes_of(hostname):
1119 if config.is_ipv4_host(node):
1120 needed_ipaddresses[node] = ip_lookup.lookup_ipv4_address(node)
1122 if config.is_ipv6_host(node):
1123 needed_ipv6addresses[node] = ip_lookup.lookup_ipv6_address(node)
1125 try:
1126 if config.is_ipv4_host(hostname):
1127 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1128 except:
1129 pass
1131 try:
1132 if config.is_ipv6_host(hostname):
1133 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1134 except:
1135 pass
1136 else:
1137 if config.is_ipv4_host(hostname):
1138 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1140 if config.is_ipv6_host(hostname):
1141 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1143 output.write("config.ipaddresses = %r\n\n" % needed_ipaddresses)
1144 output.write("config.ipv6addresses = %r\n\n" % needed_ipv6addresses)
1146 # datasource programs. Is this host relevant?
1148 # I think this is not needed anymore. Keep it here for reference
1150 ## Parameters for checks: Default values are defined in checks/*. The
1151 ## variables might be overridden by the user in main.mk. We need
1152 ## to set the actual values of those variables here. Otherwise the users'
1153 ## settings would get lost. But we only need to set those variables that
1154 ## influence the check itself - not those needed during inventory.
1155 #for var in config.check_config_variables:
1156 # output.write("%s = %r\n" % (var, getattr(config, var)))
1158 ## The same for those checks that use the new API
1159 #for check_type in needed_check_types:
1160 # # Note: check_type might not be in config.check_info. This is
1161 # # the case, if "mem" has been added to "extra_sections" and thus
1162 # # to "needed_check_types" - despite the fact that only subchecks
1163 # # mem.* exist
1164 # if check_type in config.check_info:
1165 # for var in config.check_info[check_type].get("check_config_variables", []):
1166 # output.write("%s = %r\n" % (var, getattr(config, var)))
1168 # perform actual check with a general exception handler
1169 output.write("try:\n")
1170 output.write(" sys.exit(checking.do_check(%r, None))\n" % hostname)
1171 output.write("except MKTerminate:\n")
1172 output.write(" console.output('<Interrupted>\\n', stream=sys.stderr)\n")
1173 output.write(" sys.exit(1)\n")
1174 output.write("except SystemExit, e:\n")
1175 output.write(" sys.exit(e.code)\n")
1176 output.write("except Exception, e:\n")
1177 output.write(" import traceback, pprint\n")
1179 # status output message
1180 output.write(
1181 " sys.stdout.write(\"UNKNOWN - Exception in precompiled check: %s (details in long output)\\n\" % e)\n"
1184 # generate traceback for long output
1185 output.write(" sys.stdout.write(\"Traceback: %s\\n\" % traceback.format_exc())\n")
1187 output.write("\n")
1188 output.write(" sys.exit(3)\n")
1189 output.close()
1191 # compile python (either now or delayed), but only if the source
1192 # code has not changed. The Python compilation is the most costly
1193 # operation here.
1194 if os.path.exists(source_filename):
1195 if file(source_filename).read() == file(source_filename + ".new").read():
1196 console.verbose(" (%s is unchanged)\n", source_filename, stream=sys.stderr)
1197 os.remove(source_filename + ".new")
1198 return
1199 else:
1200 console.verbose(" (new content)", stream=sys.stderr)
1202 os.rename(source_filename + ".new", source_filename)
1203 if not config.delay_precompile:
1204 py_compile.compile(source_filename, compiled_filename, compiled_filename, True)
1205 os.chmod(compiled_filename, 0755)
1206 else:
1207 if os.path.exists(compiled_filename) or os.path.islink(compiled_filename):
1208 os.remove(compiled_filename)
1209 os.symlink(hostname + ".py", compiled_filename)
1211 console.verbose(" ==> %s.\n", compiled_filename, stream=sys.stderr)
1214 def _get_needed_check_plugin_names(hostname, host_check_table):
1215 import cmk_base.check_table as check_table
1217 needed_check_plugin_names = set([])
1219 # In case the host is monitored as special agent, the check plugin for the special agent needs
1220 # to be loaded
1221 sources = data_sources.DataSources(hostname, ipaddress=None)
1222 for source in sources.get_data_sources():
1223 if isinstance(source, data_sources.programs.SpecialAgentDataSource):
1224 needed_check_plugin_names.add(source.special_agent_plugin_file_name)
1226 # Collect the needed check plugin names using the host check table
1227 for check_plugin_name, _unused_item, _unused_param, _descr in host_check_table:
1228 if check_plugin_name not in config.check_info:
1229 sys.stderr.write('Warning: Ignoring missing check %s.\n' % check_plugin_name)
1230 continue
1232 if config.check_info[check_plugin_name].get("extra_sections"):
1233 for section_name in config.check_info[check_plugin_name]["extra_sections"]:
1234 if section_name in config.check_info:
1235 needed_check_plugin_names.add(section_name)
1237 needed_check_plugin_names.add(check_plugin_name)
1239 # Also include the check plugins of the cluster nodes to be able to load
1240 # the autochecks of the nodes
1241 if config.is_cluster(hostname):
1242 for node in config.nodes_of(hostname):
1243 needed_check_plugin_names.update(
1244 [e[0] for e in check_table.get_precompiled_check_table(node, skip_ignored=False)])
1246 return needed_check_plugin_names
1249 def _get_needed_check_file_names(needed_check_plugin_names):
1250 # check info table
1251 # We need to include all those plugins that are referenced in the host's
1252 # check table.
1253 filenames = []
1254 for check_plugin_name in needed_check_plugin_names:
1255 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
1256 # Add library files needed by check (also look in local)
1257 for lib in set(config.check_includes.get(section_name, [])):
1258 if os.path.exists(cmk.utils.paths.local_checks_dir + "/" + lib):
1259 to_add = cmk.utils.paths.local_checks_dir + "/" + lib
1260 else:
1261 to_add = cmk.utils.paths.checks_dir + "/" + lib
1263 if to_add not in filenames:
1264 filenames.append(to_add)
1266 # Now add check file(s) itself
1267 paths = _find_check_plugins(check_plugin_name)
1268 if not paths:
1269 raise MKGeneralException("Cannot find check file %s needed for check type %s" % \
1270 (section_name, check_plugin_name))
1272 for path in paths:
1273 if path not in filenames:
1274 filenames.append(path)
1276 return filenames