Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk_base / core_nagios.py
blob4c2c19e9e1199a768fd9f27b83d0ac14ba5f73c7
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/Icinga.
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)
140 _output_conf_header(cfg)
142 for hostname in hostnames:
143 _create_nagios_config_host(cfg, hostname)
145 _create_nagios_config_contacts(cfg, hostnames)
146 _create_nagios_config_hostgroups(cfg)
147 _create_nagios_config_servicegroups(cfg)
148 _create_nagios_config_contactgroups(cfg)
149 _create_nagios_config_commands(cfg)
150 _create_nagios_config_timeperiods(cfg)
152 if config.extra_nagios_conf:
153 outfile.write("\n# extra_nagios_conf\n\n")
154 outfile.write(config.extra_nagios_conf)
157 def _output_conf_header(cfg):
158 cfg.outfile.write("""#
159 # Created by Check_MK. Do not edit.
162 """)
165 def _create_nagios_config_host(cfg, hostname):
166 cfg.outfile.write("\n# ----------------------------------------------------\n")
167 cfg.outfile.write("# %s\n" % hostname)
168 cfg.outfile.write("# ----------------------------------------------------\n")
169 host_attrs = core_config.get_host_attributes(hostname, config.tags_of_host(hostname))
170 if config.generate_hostconf:
171 _create_nagios_hostdefs(cfg, hostname, host_attrs)
172 _create_nagios_servicedefs(cfg, hostname, host_attrs)
175 def _create_nagios_hostdefs(cfg, hostname, attrs):
176 outfile = cfg.outfile
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 outfile.write("\ndefine host {\n")
193 outfile.write(" host_name\t\t\t%s\n" % hostname)
194 outfile.write(
195 " use\t\t\t\t%s\n" % (is_clust and config.cluster_template or config.host_template))
196 outfile.write(" address\t\t\t%s\n" % (ip and cmk_base.utils.make_utf8(ip) or
197 core_config.fallback_ip_for(hostname)))
199 # Add custom macros
200 for key, value in attrs.items():
201 if key[0] == '_':
202 tabs = "\t\t" if len(key) > 13 else "\t\t\t"
203 outfile.write(" %s%s%s\n" % (key, tabs, value))
205 # Host check command might differ from default
206 command = core_config.host_check_command(
207 hostname, ip, is_clust, cfg.hostcheck_commands_to_define, cfg.custom_commands_to_define)
208 if command:
209 outfile.write(" check_command\t\t\t%s\n" % command)
211 # Host groups: If the host has no hostgroups it gets the default
212 # hostgroup (Nagios requires each host to be member of at least on
213 # group.
214 hgs = config.hostgroups_of(hostname)
215 hostgroups = ",".join(hgs)
216 if len(hgs) == 0:
217 hostgroups = config.default_host_group
218 cfg.hostgroups_to_define.add(config.default_host_group)
219 elif config.define_hostgroups:
220 cfg.hostgroups_to_define.update(hgs)
221 outfile.write(" hostgroups\t\t\t%s\n" % cmk_base.utils.make_utf8(hostgroups))
223 # Contact groups
224 cgrs = config.contactgroups_of(hostname)
225 if len(cgrs) > 0:
226 outfile.write(" contact_groups\t\t%s\n" % cmk_base.utils.make_utf8(",".join(cgrs)))
227 cfg.contactgroups_to_define.update(cgrs)
229 if not is_clust:
230 # Parents for non-clusters
232 # Get parents manually defined via extra_host_conf["parents"]. Only honor
233 # variable "parents" and implicit parents if this setting is empty
234 extra_conf_parents = config.host_extra_conf(hostname,
235 config.extra_host_conf.get("parents", []))
237 if not extra_conf_parents:
238 parents_list = config.parents_of(hostname)
239 if parents_list:
240 outfile.write(" parents\t\t\t%s\n" % (",".join(parents_list)))
242 elif is_clust:
243 # Special handling of clusters
244 alias = "cluster of %s" % ", ".join(nodes)
245 outfile.write(" parents\t\t\t%s\n" % ",".join(nodes))
247 # Output alias, but only if it's not defined in extra_host_conf
248 alias = config.alias_of(hostname, None)
249 if alias is None:
250 outfile.write(" alias\t\t\t\t%s\n" % alias)
251 else:
252 alias = cmk_base.utils.make_utf8(alias)
254 # Custom configuration last -> user may override all other values
255 outfile.write(
256 cmk_base.utils.make_utf8(
257 _extra_host_conf_of(hostname, exclude=["parents"] if is_clust else [])))
259 outfile.write("}\n")
260 outfile.write("\n")
263 def _create_nagios_servicedefs(cfg, hostname, host_attrs):
264 outfile = cfg.outfile
265 import cmk_base.check_table as check_table
267 # _____
268 # |___ /
269 # |_ \
270 # ___) |
271 # |____/ 3. Services
273 def do_omit_service(hostname, description):
274 if config.service_ignored(hostname, None, description):
275 return True
276 if hostname != config.host_of_clustered_service(hostname, description):
277 return True
278 return False
280 def get_dependencies(hostname, servicedesc):
281 result = ""
282 for dep in check_table.service_deps(hostname, servicedesc):
283 result += """
284 define servicedependency {
285 use\t\t\t\t%s
286 host_name\t\t\t%s
287 service_description\t%s
288 dependent_host_name\t%s
289 dependent_service_description %s
291 """ % (config.service_dependency_template, hostname, dep, hostname, servicedesc)
293 return result
295 host_checks = check_table.get_check_table(hostname, remove_duplicates=True).items()
296 host_checks.sort() # Create deterministic order
297 have_at_least_one_service = False
298 used_descriptions = {}
299 for ((checkname, item), (params, description, deps)) in host_checks:
300 if checkname not in config.check_info:
301 continue # simply ignore missing checks
303 description = config.get_final_service_description(hostname, description)
304 # Make sure, the service description is unique on this host
305 if description in used_descriptions:
306 cn, it = used_descriptions[description]
307 core_config.warning("ERROR: Duplicate service description '%s' for host '%s'!\n"
308 " - 1st occurrance: checktype = %s, item = %r\n"
309 " - 2nd occurrance: checktype = %s, item = %r\n" %
310 (description, hostname, cn, it, checkname, item))
311 continue
313 else:
314 used_descriptions[description] = (checkname, item)
315 if config.check_info[checkname].get("has_perfdata", False):
316 template = config.passive_service_template_perf
317 else:
318 template = config.passive_service_template
320 # Services Dependencies
321 for dep in deps:
322 outfile.write("define servicedependency {\n"
323 " use\t\t\t\t%s\n"
324 " host_name\t\t\t%s\n"
325 " service_description\t%s\n"
326 " dependent_host_name\t%s\n"
327 " dependent_service_description %s\n"
328 "}\n\n" % (config.service_dependency_template, hostname, dep, hostname,
329 description))
331 # Add the check interval of either the Check_MK service or
332 # (if configured) the snmp_check_interval for snmp based checks
333 check_interval = 1 # default hardcoded interval
334 # Customized interval of Check_MK service
335 values = config.service_extra_conf(hostname, "Check_MK",
336 config.extra_service_conf.get('check_interval', []))
337 if values:
338 try:
339 check_interval = int(values[0])
340 except:
341 check_interval = float(values[0])
342 value = config.check_interval_of(hostname, cmk_base.check_utils.section_name_of(checkname))
343 if value is not None:
344 check_interval = value
346 # Add custom user icons and actions
347 actions = core_config.icons_and_actions_of('service', hostname, description, checkname,
348 params)
349 action_cfg = (' _ACTIONS\t\t\t%s\n' % ','.join(actions)) if actions else ''
351 outfile.write("""define service {
352 use\t\t\t\t%s
353 host_name\t\t\t%s
354 service_description\t\t%s
355 check_interval\t\t%d
356 %s%s check_command\t\t\tcheck_mk-%s
359 """ % (template, hostname, description.encode("utf-8"), check_interval,
360 _extra_service_conf_of(cfg, hostname, description), action_cfg, checkname.encode("utf-8")))
362 cfg.checknames_to_define.add(checkname)
363 have_at_least_one_service = True
365 # Active check for check_mk
366 if have_at_least_one_service:
367 outfile.write("""
368 # Active checks
370 define service {
371 use\t\t\t\t%s
372 host_name\t\t\t%s
373 %s service_description\t\tCheck_MK
375 """ % (config.active_service_template, hostname, _extra_service_conf_of(cfg, hostname, "Check_MK")))
377 # legacy checks via legacy_checks
378 legchecks = config.host_extra_conf(hostname, config.legacy_checks)
379 if len(legchecks) > 0:
380 outfile.write("\n\n# Legacy checks\n")
381 for command, description, has_perfdata in legchecks:
382 description = config.get_final_service_description(hostname, description)
383 if do_omit_service(hostname, description):
384 continue
386 if description in used_descriptions:
387 cn, it = used_descriptions[description]
388 core_config.warning(
389 "ERROR: Duplicate service description (legacy check) '%s' for host '%s'!\n"
390 " - 1st occurrance: checktype = %s, item = %r\n"
391 " - 2nd occurrance: checktype = legacy(%s), item = None\n" % (description, hostname,
392 cn, it, command))
393 continue
395 else:
396 used_descriptions[description] = ("legacy(" + command + ")", description)
398 extraconf = _extra_service_conf_of(cfg, hostname, description)
399 if has_perfdata:
400 template = "check_mk_perf,"
401 else:
402 template = ""
403 outfile.write("""
404 define service {
405 use\t\t\t\t%scheck_mk_default
406 host_name\t\t\t%s
407 service_description\t\t%s
408 check_command\t\t\t%s
409 active_checks_enabled\t\t1
411 """ % (template, hostname, cmk_base.utils.make_utf8(description), _simulate_command(cfg, command),
412 extraconf))
414 # write service dependencies for legacy checks
415 outfile.write(get_dependencies(hostname, description))
417 # legacy checks via active_checks
418 actchecks = []
419 for acttype, rules in config.active_checks.items():
420 entries = config.host_extra_conf(hostname, rules)
421 if entries:
422 # Skip Check_MK HW/SW Inventory for all ping hosts, even when the user has enabled
423 # the inventory for ping only hosts
424 if acttype == "cmk_inv" and config.is_ping_host(hostname):
425 continue
427 cfg.active_checks_to_define.add(acttype)
428 act_info = config.active_check_info[acttype]
429 for params in entries:
430 actchecks.append((acttype, act_info, params))
432 if actchecks:
433 outfile.write("\n\n# Active checks\n")
434 for acttype, act_info, params in actchecks:
435 # Make hostname available as global variable in argument functions
436 check_api_utils.set_hostname(hostname)
438 has_perfdata = act_info.get('has_perfdata', False)
439 description = config.active_check_service_description(hostname, acttype, params)
441 if do_omit_service(hostname, description):
442 continue
444 # compute argument, and quote ! and \ for Nagios
445 args = core_config.active_check_arguments(
446 hostname, description, act_info["argument_function"](params)).replace(
447 "\\", "\\\\").replace("!", "\\!")
449 if description in used_descriptions:
450 cn, it = used_descriptions[description]
451 # If we have the same active check again with the same description,
452 # then we do not regard this as an error, but simply ignore the
453 # second one. That way one can override a check with other settings.
454 if cn == "active(%s)" % acttype:
455 continue
457 core_config.warning(
458 "ERROR: Duplicate service description (active check) '%s' for host '%s'!\n"
459 " - 1st occurrance: checktype = %s, item = %r\n"
460 " - 2nd occurrance: checktype = active(%s), item = None\n" %
461 (description, hostname, cn, it, acttype))
462 continue
464 else:
465 used_descriptions[description] = ("active(" + acttype + ")", description)
467 template = "check_mk_perf," if has_perfdata else ""
468 extraconf = _extra_service_conf_of(cfg, hostname, description)
470 if host_attrs["address"] in ["0.0.0.0", "::"]:
471 command_name = "check-mk-custom"
472 command = command_name + "!echo \"CRIT - Failed to lookup IP address and no explicit IP address configured\" && exit 2"
473 cfg.custom_commands_to_define.add(command_name)
474 else:
475 command = "check_mk_active-%s!%s" % (acttype, args)
477 outfile.write("""
478 define service {
479 use\t\t\t\t%scheck_mk_default
480 host_name\t\t\t%s
481 service_description\t\t%s
482 check_command\t\t\t%s
483 active_checks_enabled\t\t1
485 """ % (template, hostname, cmk_base.utils.make_utf8(description),
486 cmk_base.utils.make_utf8(_simulate_command(cfg, command)), extraconf))
488 # write service dependencies for active checks
489 outfile.write(get_dependencies(hostname, description))
491 # Legacy checks via custom_checks
492 custchecks = config.host_extra_conf(hostname, config.custom_checks)
493 if custchecks:
494 outfile.write("\n\n# Custom checks\n")
495 for entry in custchecks:
496 # entries are dicts with the following keys:
497 # "service_description" Service description to use
498 # "command_line" (optional) Unix command line for executing the check
499 # If this is missing, we create a passive check
500 # "command_name" (optional) Name of Monitoring command to define. If missing,
501 # we use "check-mk-custom"
502 # "has_perfdata" (optional) If present and True, we activate perf_data
503 description = config.get_final_service_description(hostname,
504 entry["service_description"])
505 has_perfdata = entry.get("has_perfdata", False)
506 command_name = entry.get("command_name", "check-mk-custom")
507 command_line = entry.get("command_line", "")
509 if do_omit_service(hostname, description):
510 continue
512 if command_line:
513 command_line = core_config.autodetect_plugin(command_line).replace("\\",
514 "\\\\").replace(
515 "!", "\\!")
517 if "freshness" in entry:
518 freshness = " check_freshness\t\t1\n" + \
519 " freshness_threshold\t\t%d\n" % (60 * entry["freshness"]["interval"])
520 command_line = "echo %s && exit %d" % (_quote_nagios_string(
521 entry["freshness"]["output"]), entry["freshness"]["state"])
522 else:
523 freshness = ""
525 cfg.custom_commands_to_define.add(command_name)
527 if description in used_descriptions:
528 cn, it = used_descriptions[description]
529 # If we have the same active check again with the same description,
530 # then we do not regard this as an error, but simply ignore the
531 # second one.
532 if cn == "custom(%s)" % command_name:
533 continue
534 core_config.warning(
535 "ERROR: Duplicate service description (custom check) '%s' for host '%s'!\n"
536 " - 1st occurrance: checktype = %s, item = %r\n"
537 " - 2nd occurrance: checktype = custom(%s), item = %r\n" %
538 (description, hostname, cn, it, command_name, description))
539 continue
540 else:
541 used_descriptions[description] = ("custom(%s)" % command_name, description)
543 template = "check_mk_perf," if has_perfdata else ""
544 extraconf = _extra_service_conf_of(cfg, hostname, description)
545 command = "%s!%s" % (command_name, command_line)
546 outfile.write("""
547 define service {
548 use\t\t\t\t%scheck_mk_default
549 host_name\t\t\t%s
550 service_description\t\t%s
551 check_command\t\t\t%s
552 active_checks_enabled\t\t%d
553 %s%s}
554 """ % (template, hostname, cmk_base.utils.make_utf8(description), _simulate_command(cfg, command),
555 (command_line and not freshness) and 1 or 0, extraconf, freshness))
557 # write service dependencies for custom checks
558 outfile.write(get_dependencies(hostname, description))
560 # FIXME: Remove old name one day
561 service_discovery_name = 'Check_MK inventory'
562 if 'cmk-inventory' in config.use_new_descriptions_for:
563 service_discovery_name = 'Check_MK Discovery'
565 import cmk_base.discovery as discovery
566 params = discovery.discovery_check_parameters(hostname) or \
567 discovery.default_discovery_check_parameters()
569 # Inventory checks - if user has configured them.
570 if params["check_interval"] \
571 and not config.service_ignored(hostname, None, service_discovery_name) \
572 and not config.is_ping_host(hostname):
573 outfile.write("""
574 define service {
575 use\t\t\t\t%s
576 host_name\t\t\t%s
577 normal_check_interval\t\t%d
578 retry_check_interval\t\t%d
579 %s service_description\t\t%s
581 """ % (config.inventory_check_template, hostname,
582 params["check_interval"], params["check_interval"],
583 _extra_service_conf_of(cfg, hostname, service_discovery_name), service_discovery_name))
585 if have_at_least_one_service:
586 outfile.write("""
587 define servicedependency {
588 use\t\t\t\t%s
589 host_name\t\t\t%s
590 service_description\t\tCheck_MK
591 dependent_host_name\t\t%s
592 dependent_service_description\t%s
594 """ % (config.service_dependency_template, hostname, hostname, service_discovery_name))
596 # No check_mk service, no legacy service -> create PING service
597 if not have_at_least_one_service and not legchecks and not actchecks and not custchecks:
598 _add_ping_service(cfg, hostname, host_attrs["address"],
599 config.is_ipv6_primary(hostname) and 6 or 4, "PING",
600 host_attrs.get("_NODEIPS"))
602 if config.is_ipv4v6_host(hostname):
603 if config.is_ipv6_primary(hostname):
604 _add_ping_service(cfg, hostname, host_attrs["_ADDRESS_4"], 4, "PING IPv4",
605 host_attrs.get("_NODEIPS_4"))
606 else:
607 _add_ping_service(cfg, hostname, host_attrs["_ADDRESS_6"], 6, "PING IPv6",
608 host_attrs.get("_NODEIPS_6"))
611 def _add_ping_service(cfg, hostname, ipaddress, family, descr, node_ips):
612 arguments = core_config.check_icmp_arguments_of(hostname, family=family)
614 ping_command = 'check-mk-ping'
615 if config.is_cluster(hostname):
616 arguments += ' -m 1 ' + node_ips
617 else:
618 arguments += ' ' + ipaddress
620 cfg.outfile.write("""
621 define service {
622 use\t\t\t\t%s
623 service_description\t\t%s
624 check_command\t\t\t%s!%s
625 %s host_name\t\t\t%s
628 """ % (config.pingonly_template, descr, ping_command, arguments,
629 _extra_service_conf_of(cfg, hostname, descr), hostname))
632 def _simulate_command(cfg, command):
633 if config.simulation_mode:
634 cfg.custom_commands_to_define.add("check-mk-simulation")
635 return "check-mk-simulation!echo 'Simulation mode - cannot execute real check'"
636 return command
639 def _create_nagios_config_hostgroups(cfg):
640 outfile = cfg.outfile
641 if config.define_hostgroups:
642 outfile.write("\n# ------------------------------------------------------------\n")
643 outfile.write("# Host groups (controlled by define_hostgroups)\n")
644 outfile.write("# ------------------------------------------------------------\n")
645 hgs = list(cfg.hostgroups_to_define)
646 hgs.sort()
647 for hg in hgs:
648 try:
649 alias = config.define_hostgroups[hg]
650 except:
651 alias = hg
652 outfile.write("""
653 define hostgroup {
654 hostgroup_name\t\t%s
655 alias\t\t\t\t%s
657 """ % (cmk_base.utils.make_utf8(hg), cmk_base.utils.make_utf8(alias)))
659 # No creation of host groups but we need to define
660 # default host group
661 elif config.default_host_group in cfg.hostgroups_to_define:
662 outfile.write("""
663 define hostgroup {
664 hostgroup_name\t\t%s
665 alias\t\t\t\tCheck_MK default hostgroup
667 """ % config.default_host_group)
670 def _create_nagios_config_servicegroups(cfg):
671 outfile = cfg.outfile
672 if config.define_servicegroups:
673 outfile.write("\n# ------------------------------------------------------------\n")
674 outfile.write("# Service groups (controlled by define_servicegroups)\n")
675 outfile.write("# ------------------------------------------------------------\n")
676 sgs = list(cfg.servicegroups_to_define)
677 sgs.sort()
678 for sg in sgs:
679 try:
680 alias = config.define_servicegroups[sg]
681 except:
682 alias = sg
683 outfile.write("""
684 define servicegroup {
685 servicegroup_name\t\t%s
686 alias\t\t\t\t%s
688 """ % (cmk_base.utils.make_utf8(sg), cmk_base.utils.make_utf8(alias)))
691 def _create_nagios_config_contactgroups(cfg):
692 outfile = cfg.outfile
693 if config.define_contactgroups is False:
694 return
696 cgs = list(cfg.contactgroups_to_define)
697 if not cgs:
698 return
700 outfile.write("\n# ------------------------------------------------------------\n")
701 outfile.write("# Contact groups (controlled by define_contactgroups)\n")
702 outfile.write("# ------------------------------------------------------------\n\n")
703 for name in sorted(cgs):
704 if isinstance(config.define_contactgroups, dict):
705 alias = config.define_contactgroups.get(name, name)
706 else:
707 alias = name
709 outfile.write("\ndefine contactgroup {\n"
710 " contactgroup_name\t\t%s\n"
711 " alias\t\t\t\t%s\n" % (cmk_base.utils.make_utf8(name),
712 cmk_base.utils.make_utf8(alias)))
714 members = config.contactgroup_members.get(name)
715 if members:
716 outfile.write(" members\t\t\t%s\n" % ",".join(members))
718 outfile.write("}\n")
721 def _create_nagios_config_commands(cfg):
722 outfile = cfg.outfile
723 if config.generate_dummy_commands:
724 outfile.write("\n# ------------------------------------------------------------\n")
725 outfile.write("# Dummy check commands and active check commands\n")
726 outfile.write("# ------------------------------------------------------------\n\n")
727 for checkname in cfg.checknames_to_define:
728 outfile.write("""define command {
729 command_name\t\t\tcheck_mk-%s
730 command_line\t\t\t%s
733 """ % (checkname, config.dummy_check_commandline))
735 # active_checks
736 for acttype in cfg.active_checks_to_define:
737 act_info = config.active_check_info[acttype]
738 outfile.write("""define command {
739 command_name\t\t\tcheck_mk_active-%s
740 command_line\t\t\t%s
743 """ % (acttype, act_info["command_line"]))
745 # custom_checks
746 for command_name in cfg.custom_commands_to_define:
747 outfile.write("""define command {
748 command_name\t\t\t%s
749 command_line\t\t\t$ARG1$
752 """ % command_name)
754 # custom host checks
755 for command_name, command_line in cfg.hostcheck_commands_to_define:
756 outfile.write("""define command {
757 command_name\t\t\t%s
758 command_line\t\t\t%s
761 """ % (command_name, command_line))
764 def _create_nagios_config_timeperiods(cfg):
765 outfile = cfg.outfile
766 if len(config.timeperiods) > 0:
767 outfile.write("\n# ------------------------------------------------------------\n")
768 outfile.write("# Timeperiod definitions (controlled by variable 'timeperiods')\n")
769 outfile.write("# ------------------------------------------------------------\n\n")
770 tpnames = config.timeperiods.keys()
771 tpnames.sort()
772 for name in tpnames:
773 tp = config.timeperiods[name]
774 outfile.write("define timeperiod {\n timeperiod_name\t\t%s\n" % name)
775 if "alias" in tp:
776 outfile.write(" alias\t\t\t\t%s\n" % cmk_base.utils.make_utf8(tp["alias"]))
777 for key, value in tp.items():
778 if key not in ["alias", "exclude"]:
779 times = ",".join([("%s-%s" % (fr, to)) for (fr, to) in value])
780 if times:
781 outfile.write(" %-20s\t\t%s\n" % (key, times))
782 if "exclude" in tp:
783 outfile.write(" exclude\t\t\t%s\n" % ",".join(tp["exclude"]))
784 outfile.write("}\n\n")
787 def _create_nagios_config_contacts(cfg, hostnames):
788 outfile = cfg.outfile
789 if len(config.contacts) > 0:
790 outfile.write("\n# ------------------------------------------------------------\n")
791 outfile.write("# Contact definitions (controlled by variable 'contacts')\n")
792 outfile.write("# ------------------------------------------------------------\n\n")
793 cnames = config.contacts.keys()
794 cnames.sort()
795 for cname in cnames:
796 contact = config.contacts[cname]
797 # Create contact groups in nagios, even when they are empty. This is needed
798 # for RBN to work correctly when using contactgroups as recipients which are
799 # not assigned to any host
800 cfg.contactgroups_to_define.update(contact.get("contactgroups", []))
801 # If the contact is in no contact group or all of the contact groups
802 # of the contact have neither hosts nor services assigned - in other
803 # words if the contact is not assigned to any host or service, then
804 # we do not create this contact in Nagios. It's useless and will produce
805 # warnings.
806 cgrs = [
807 cgr for cgr in contact.get("contactgroups", [])
808 if cgr in cfg.contactgroups_to_define
810 if not cgrs:
811 continue
813 outfile.write(
814 "define contact {\n contact_name\t\t\t%s\n" % cmk_base.utils.make_utf8(cname))
815 if "alias" in contact:
816 outfile.write(" alias\t\t\t\t%s\n" % cmk_base.utils.make_utf8(contact["alias"]))
817 if "email" in contact:
818 outfile.write(" email\t\t\t\t%s\n" % cmk_base.utils.make_utf8(contact["email"]))
819 if "pager" in contact:
820 outfile.write(" pager\t\t\t\t%s\n" % contact["pager"])
821 if config.enable_rulebased_notifications:
822 not_enabled = False
823 else:
824 not_enabled = contact.get("notifications_enabled", True)
826 for what in ["host", "service"]:
827 no = contact.get(what + "_notification_options", "")
828 if not no or not not_enabled:
829 outfile.write(" %s_notifications_enabled\t0\n" % what)
830 no = "n"
831 outfile.write(" %s_notification_options\t%s\n" % (what, ",".join(list(no))))
832 outfile.write(" %s_notification_period\t%s\n" %
833 (what, contact.get("notification_period", "24X7")))
834 outfile.write(
835 " %s_notification_commands\t%s\n" %
836 (what, contact.get("%s_notification_commands" % what, "check-mk-notify")))
837 # Add custom macros
838 for macro in [m for m in contact.keys() if m.startswith('_')]:
839 outfile.write(" %s\t%s\n" % (macro, contact[macro]))
841 outfile.write(" contactgroups\t\t\t%s\n" % ", ".join(cgrs))
842 outfile.write("}\n\n")
844 if config.enable_rulebased_notifications and hostnames:
845 cfg.contactgroups_to_define.add("check-mk-notify")
846 outfile.write("# Needed for rule based notifications\n"
847 "define contact {\n"
848 " contact_name\t\t\tcheck-mk-notify\n"
849 " alias\t\t\t\tContact for rule based notifications\n"
850 " host_notification_options\td,u,r,f,s\n"
851 " service_notification_options\tu,c,w,r,f,s\n"
852 " host_notification_period\t24X7\n"
853 " service_notification_period\t24X7\n"
854 " host_notification_commands\tcheck-mk-notify\n"
855 " service_notification_commands\tcheck-mk-notify\n"
856 " contactgroups\t\t\tcheck-mk-notify\n"
857 "}\n\n")
860 # Quote string for use in a nagios command execution.
861 # Please note that also quoting for ! and \ vor Nagios
862 # itself takes place here.
863 def _quote_nagios_string(s):
864 return "'" + s.replace('\\', '\\\\').replace("'", "'\"'\"'").replace('!', '\\!') + "'"
867 def _extra_host_conf_of(hostname, exclude=None):
868 if exclude is None:
869 exclude = []
870 return _extra_conf_of(config.extra_host_conf, hostname, None, exclude)
873 # Collect all extra configuration data for a service
874 def _extra_service_conf_of(cfg, hostname, description):
875 conf = ""
877 # Contact groups
878 sercgr = config.service_extra_conf(hostname, description, config.service_contactgroups)
879 cfg.contactgroups_to_define.update(sercgr)
880 if len(sercgr) > 0:
881 if config.enable_rulebased_notifications:
882 sercgr.append("check-mk-notify") # not nessary if not explicit groups defined
883 conf += " contact_groups\t\t" + ",".join(sercgr) + "\n"
885 sergr = config.service_extra_conf(hostname, description, config.service_groups)
886 if len(sergr) > 0:
887 conf += " service_groups\t\t" + ",".join(sergr) + "\n"
888 if config.define_servicegroups:
889 cfg.servicegroups_to_define.update(sergr)
890 conf += _extra_conf_of(config.extra_service_conf, hostname, description)
891 return conf.encode("utf-8")
894 def _extra_conf_of(confdict, hostname, service, exclude=None):
895 if exclude is None:
896 exclude = []
898 result = ""
899 for key, conflist in confdict.items():
900 if service is not None:
901 values = config.service_extra_conf(hostname, service, conflist)
902 else:
903 values = config.host_extra_conf(hostname, conflist)
905 if exclude and key in exclude:
906 continue
908 if values:
909 result += " %-29s %s\n" % (key, values[0])
911 return result
915 # .--Precompile----------------------------------------------------------.
916 # | ____ _ _ |
917 # | | _ \ _ __ ___ ___ ___ _ __ ___ _ __ (_) | ___ |
918 # | | |_) | '__/ _ \/ __/ _ \| '_ ` _ \| '_ \| | |/ _ \ |
919 # | | __/| | | __/ (_| (_) | | | | | | |_) | | | __/ |
920 # | |_| |_| \___|\___\___/|_| |_| |_| .__/|_|_|\___| |
921 # | |_| |
922 # +----------------------------------------------------------------------+
923 # | Precompiling creates on dedicated Python file per host, which just |
924 # | contains that code and information that is needed for executing all |
925 # | checks of that host. Also static data that cannot change during the |
926 # | normal monitoring process is being precomputed and hard coded. This |
927 # | all saves substantial CPU resources as opposed to running Check_MK |
928 # | in adhoc mode (about 75%). |
929 # '----------------------------------------------------------------------'
932 # Find files to be included in precompile host check for a certain
933 # check (for example df or mem.used). In case of checks with a period
934 # (subchecks) we might have to include both "mem" and "mem.used". The
935 # subcheck *may* be implemented in a separate file.
936 def _find_check_plugins(checktype):
937 if '.' in checktype:
938 candidates = [cmk_base.check_utils.section_name_of(checktype), checktype]
939 else:
940 candidates = [checktype]
942 paths = []
943 for candidate in candidates:
944 filename = cmk.utils.paths.local_checks_dir + "/" + candidate
945 if os.path.exists(filename):
946 paths.append(filename)
947 continue
949 filename = cmk.utils.paths.checks_dir + "/" + candidate
950 if os.path.exists(filename):
951 paths.append(filename)
953 return paths
956 def precompile_hostchecks():
957 console.verbose("Creating precompiled host check config...\n")
958 config.PackedConfig().save()
960 if not os.path.exists(cmk.utils.paths.precompiled_hostchecks_dir):
961 os.makedirs(cmk.utils.paths.precompiled_hostchecks_dir)
963 console.verbose("Precompiling host checks...\n")
964 for host in config.all_active_hosts():
965 try:
966 _precompile_hostcheck(host)
967 except Exception as e:
968 if cmk.utils.debug.enabled():
969 raise
970 console.error("Error precompiling checks for host %s: %s\n" % (host, e))
971 sys.exit(5)
974 # read python file and strip comments
975 g_stripped_file_cache = {} # type: Dict[str, str]
978 def stripped_python_file(filename):
979 if filename in g_stripped_file_cache:
980 return g_stripped_file_cache[filename]
981 a = ""
982 for line in file(filename):
983 l = line.strip()
984 if l == "" or l[0] != '#':
985 a += line # not stripped line because of indentation!
986 g_stripped_file_cache[filename] = a
987 return a
990 def _precompile_hostcheck(hostname):
991 import cmk_base.check_table as check_table
993 console.verbose("%s%s%-16s%s:", tty.bold, tty.blue, hostname, tty.normal, stream=sys.stderr)
995 compiled_filename = cmk.utils.paths.precompiled_hostchecks_dir + "/" + hostname
996 source_filename = compiled_filename + ".py"
997 for fname in [compiled_filename, source_filename]:
998 try:
999 os.remove(fname)
1000 except:
1001 pass
1003 # check table, enriched with addition precompiled information.
1004 host_check_table = check_table.get_precompiled_check_table(
1005 hostname, filter_mode="include_clustered", skip_ignored=False)
1006 if not host_check_table:
1007 console.verbose("(no Check_MK checks)\n")
1008 return
1010 output = file(source_filename + ".new", "w")
1011 output.write("#!/usr/bin/env python\n")
1012 output.write("# encoding: utf-8\n\n")
1014 output.write("import sys\n\n")
1016 output.write("if not sys.executable.startswith('/omd'):\n")
1017 output.write(" sys.stdout.write(\"ERROR: Only executable with sites python\\n\")\n")
1018 output.write(" sys.exit(2)\n\n")
1020 # Remove precompiled directory from sys.path. Leaving it in the path
1021 # makes problems when host names (name of precompiled files) are equal
1022 # to python module names like "random"
1023 output.write("sys.path.pop(0)\n")
1025 output.write("import cmk.utils.log\n")
1026 output.write("import cmk.utils.debug\n")
1027 output.write("from cmk.utils.exceptions import MKTerminate\n")
1028 output.write("\n")
1029 output.write("import cmk_base.utils\n")
1030 output.write("import cmk_base.config as config\n")
1031 output.write("import cmk_base.console as console\n")
1032 output.write("import cmk_base.checking as checking\n")
1033 output.write("import cmk_base.check_api as check_api\n")
1034 output.write("import cmk_base.ip_lookup as ip_lookup\n")
1036 # Self-compile: replace symlink with precompiled python-code, if
1037 # we are run for the first time
1038 if config.delay_precompile:
1039 output.write("""
1040 import os
1041 if os.path.islink(%(dst)r):
1042 import py_compile
1043 os.remove(%(dst)r)
1044 py_compile.compile(%(src)r, %(dst)r, %(dst)r, True)
1045 os.chmod(%(dst)r, 0755)
1047 """ % {
1048 "src": source_filename,
1049 "dst": compiled_filename
1052 # Register default Check_MK signal handler
1053 output.write("cmk_base.utils.register_sigint_handler()\n")
1055 # initialize global variables
1056 output.write("""
1057 # very simple commandline parsing: only -v (once or twice) and -d are supported
1059 cmk.utils.log.setup_console_logging()
1060 logger = cmk.utils.log.get_logger("base")
1062 # TODO: This is not really good parsing, because it not cares about syntax like e.g. "-nv".
1063 # The later regular argument parsing is handling this correctly. Try to clean this up.
1064 cmk.utils.log.set_verbosity(verbosity=len([ a for a in sys.argv if a in [ "-v", "--verbose"] ]))
1066 if '-d' in sys.argv:
1067 cmk.utils.debug.enable()
1069 """)
1071 needed_check_plugin_names = _get_needed_check_plugin_names(hostname, host_check_table)
1073 output.write("config.load_checks(check_api.get_check_api_context, %r)\n" %
1074 _get_needed_check_file_names(needed_check_plugin_names))
1076 for check_plugin_name in sorted(needed_check_plugin_names):
1077 console.verbose(" %s%s%s", tty.green, check_plugin_name, tty.normal, stream=sys.stderr)
1079 output.write("config.load_packed_config()\n")
1081 # handling of clusters
1082 if config.is_cluster(hostname):
1083 cluster_nodes = config.nodes_of(hostname)
1084 output.write("config.is_cluster = lambda h: h == %r\n" % hostname)
1086 nodes_of_map = {hostname: cluster_nodes}
1087 for node in config.nodes_of(hostname):
1088 nodes_of_map[node] = None
1089 output.write("config.nodes_of = lambda h: %r[h]\n" % nodes_of_map)
1090 else:
1091 output.write("config.is_cluster = lambda h: False\n")
1092 output.write("config.nodes_of = lambda h: None\n")
1094 # IP addresses
1095 needed_ipaddresses, needed_ipv6addresses, = {}, {}
1096 if config.is_cluster(hostname):
1097 for node in config.nodes_of(hostname):
1098 if config.is_ipv4_host(node):
1099 needed_ipaddresses[node] = ip_lookup.lookup_ipv4_address(node)
1101 if config.is_ipv6_host(node):
1102 needed_ipv6addresses[node] = ip_lookup.lookup_ipv6_address(node)
1104 try:
1105 if config.is_ipv4_host(hostname):
1106 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1107 except:
1108 pass
1110 try:
1111 if config.is_ipv6_host(hostname):
1112 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1113 except:
1114 pass
1115 else:
1116 if config.is_ipv4_host(hostname):
1117 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1119 if config.is_ipv6_host(hostname):
1120 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1122 output.write("config.ipaddresses = %r\n\n" % needed_ipaddresses)
1123 output.write("config.ipv6addresses = %r\n\n" % needed_ipv6addresses)
1125 # datasource programs. Is this host relevant?
1127 # I think this is not needed anymore. Keep it here for reference
1129 ## Parameters for checks: Default values are defined in checks/*. The
1130 ## variables might be overridden by the user in main.mk. We need
1131 ## to set the actual values of those variables here. Otherwise the users'
1132 ## settings would get lost. But we only need to set those variables that
1133 ## influence the check itself - not those needed during inventory.
1134 #for var in config.check_config_variables:
1135 # output.write("%s = %r\n" % (var, getattr(config, var)))
1137 ## The same for those checks that use the new API
1138 #for check_type in needed_check_types:
1139 # # Note: check_type might not be in config.check_info. This is
1140 # # the case, if "mem" has been added to "extra_sections" and thus
1141 # # to "needed_check_types" - despite the fact that only subchecks
1142 # # mem.* exist
1143 # if check_type in config.check_info:
1144 # for var in config.check_info[check_type].get("check_config_variables", []):
1145 # output.write("%s = %r\n" % (var, getattr(config, var)))
1147 # perform actual check with a general exception handler
1148 output.write("try:\n")
1149 output.write(" sys.exit(checking.do_check(%r, None))\n" % hostname)
1150 output.write("except MKTerminate:\n")
1151 output.write(" console.output('<Interrupted>\\n', stream=sys.stderr)\n")
1152 output.write(" sys.exit(1)\n")
1153 output.write("except SystemExit, e:\n")
1154 output.write(" sys.exit(e.code)\n")
1155 output.write("except Exception, e:\n")
1156 output.write(" import traceback, pprint\n")
1158 # status output message
1159 output.write(
1160 " sys.stdout.write(\"UNKNOWN - Exception in precompiled check: %s (details in long output)\\n\" % e)\n"
1163 # generate traceback for long output
1164 output.write(" sys.stdout.write(\"Traceback: %s\\n\" % traceback.format_exc())\n")
1166 output.write("\n")
1167 output.write(" sys.exit(3)\n")
1168 output.close()
1170 # compile python (either now or delayed), but only if the source
1171 # code has not changed. The Python compilation is the most costly
1172 # operation here.
1173 if os.path.exists(source_filename):
1174 if file(source_filename).read() == file(source_filename + ".new").read():
1175 console.verbose(" (%s is unchanged)\n", source_filename, stream=sys.stderr)
1176 os.remove(source_filename + ".new")
1177 return
1178 else:
1179 console.verbose(" (new content)", stream=sys.stderr)
1181 os.rename(source_filename + ".new", source_filename)
1182 if not config.delay_precompile:
1183 py_compile.compile(source_filename, compiled_filename, compiled_filename, True)
1184 os.chmod(compiled_filename, 0755)
1185 else:
1186 if os.path.exists(compiled_filename) or os.path.islink(compiled_filename):
1187 os.remove(compiled_filename)
1188 os.symlink(hostname + ".py", compiled_filename)
1190 console.verbose(" ==> %s.\n", compiled_filename, stream=sys.stderr)
1193 def _get_needed_check_plugin_names(hostname, host_check_table):
1194 import cmk_base.check_table as check_table
1196 needed_check_plugin_names = set([])
1198 # In case the host is monitored as special agent, the check plugin for the special agent needs
1199 # to be loaded
1200 sources = data_sources.DataSources(hostname, ipaddress=None)
1201 for source in sources.get_data_sources():
1202 if isinstance(source, data_sources.programs.SpecialAgentDataSource):
1203 needed_check_plugin_names.add(source.special_agent_plugin_file_name)
1205 # Collect the needed check plugin names using the host check table
1206 for check_plugin_name, _unused_item, _unused_param, _descr in host_check_table:
1207 if check_plugin_name not in config.check_info:
1208 sys.stderr.write('Warning: Ignoring missing check %s.\n' % check_plugin_name)
1209 continue
1211 if config.check_info[check_plugin_name].get("extra_sections"):
1212 for section_name in config.check_info[check_plugin_name]["extra_sections"]:
1213 if section_name in config.check_info:
1214 needed_check_plugin_names.add(section_name)
1216 needed_check_plugin_names.add(check_plugin_name)
1218 # Also include the check plugins of the cluster nodes to be able to load
1219 # the autochecks of the nodes
1220 if config.is_cluster(hostname):
1221 for node in config.nodes_of(hostname):
1222 needed_check_plugin_names.update(
1223 [e[0] for e in check_table.get_precompiled_check_table(node, skip_ignored=False)])
1225 return needed_check_plugin_names
1228 def _get_needed_check_file_names(needed_check_plugin_names):
1229 # check info table
1230 # We need to include all those plugins that are referenced in the host's
1231 # check table.
1232 filenames = []
1233 for check_plugin_name in needed_check_plugin_names:
1234 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
1235 # Add library files needed by check (also look in local)
1236 for lib in set(config.check_includes.get(section_name, [])):
1237 if os.path.exists(cmk.utils.paths.local_checks_dir + "/" + lib):
1238 to_add = cmk.utils.paths.local_checks_dir + "/" + lib
1239 else:
1240 to_add = cmk.utils.paths.checks_dir + "/" + lib
1242 if to_add not in filenames:
1243 filenames.append(to_add)
1245 # Now add check file(s) itself
1246 paths = _find_check_plugins(check_plugin_name)
1247 if not paths:
1248 raise MKGeneralException("Cannot find check file %s needed for check type %s" % \
1249 (section_name, check_plugin_name))
1251 for path in paths:
1252 if path not in filenames:
1253 filenames.append(path)
1255 return filenames