Refactoring: Changed all remaining check parameters to the new rulespec registry...
[check_mk.git] / cmk_base / core_nagios.py
blob409352c0ef79a27f2029f64c0d415c33fd2a394e
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)
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 config_cache = config.get_config_cache()
275 def do_omit_service(hostname, description):
276 if config.service_ignored(hostname, None, description):
277 return True
278 if hostname != config_cache.host_of_clustered_service(hostname, description):
279 return True
280 return False
282 def get_dependencies(hostname, servicedesc):
283 result = ""
284 for dep in check_table.service_deps(hostname, servicedesc):
285 result += """
286 define servicedependency {
287 use\t\t\t\t%s
288 host_name\t\t\t%s
289 service_description\t%s
290 dependent_host_name\t%s
291 dependent_service_description %s
293 """ % (config.service_dependency_template, hostname, dep, hostname, servicedesc)
295 return result
297 host_checks = check_table.get_check_table(hostname, remove_duplicates=True).items()
298 host_checks.sort() # Create deterministic order
299 have_at_least_one_service = False
300 used_descriptions = {}
301 for ((checkname, item), (params, description, deps)) in host_checks:
302 if checkname not in config.check_info:
303 continue # simply ignore missing checks
305 description = config.get_final_service_description(hostname, description)
306 # Make sure, the service description is unique on this host
307 if description in used_descriptions:
308 cn, it = used_descriptions[description]
309 core_config.warning("ERROR: Duplicate service description '%s' for host '%s'!\n"
310 " - 1st occurrance: checktype = %s, item = %r\n"
311 " - 2nd occurrance: checktype = %s, item = %r\n" %
312 (description, hostname, cn, it, checkname, item))
313 continue
315 else:
316 used_descriptions[description] = (checkname, item)
317 if config.check_info[checkname].get("has_perfdata", False):
318 template = config.passive_service_template_perf
319 else:
320 template = config.passive_service_template
322 # Services Dependencies
323 for dep in deps:
324 outfile.write("define servicedependency {\n"
325 " use\t\t\t\t%s\n"
326 " host_name\t\t\t%s\n"
327 " service_description\t%s\n"
328 " dependent_host_name\t%s\n"
329 " dependent_service_description %s\n"
330 "}\n\n" % (config.service_dependency_template, hostname, dep, hostname,
331 description))
333 # Add the check interval of either the Check_MK service or
334 # (if configured) the snmp_check_interval for snmp based checks
335 check_interval = 1 # default hardcoded interval
336 # Customized interval of Check_MK service
337 values = config.service_extra_conf(hostname, "Check_MK",
338 config.extra_service_conf.get('check_interval', []))
339 if values:
340 try:
341 check_interval = int(values[0])
342 except:
343 check_interval = float(values[0])
344 value = config.check_interval_of(hostname, cmk_base.check_utils.section_name_of(checkname))
345 if value is not None:
346 check_interval = value
348 # Add custom user icons and actions
349 actions = core_config.icons_and_actions_of('service', hostname, description, checkname,
350 params)
351 action_cfg = (' _ACTIONS\t\t\t%s\n' % ','.join(actions)) if actions else ''
353 outfile.write("""define service {
354 use\t\t\t\t%s
355 host_name\t\t\t%s
356 service_description\t\t%s
357 check_interval\t\t%d
358 %s%s check_command\t\t\tcheck_mk-%s
361 """ % (template, hostname, description.encode("utf-8"), check_interval,
362 _extra_service_conf_of(cfg, hostname, description), action_cfg, checkname.encode("utf-8")))
364 cfg.checknames_to_define.add(checkname)
365 have_at_least_one_service = True
367 # Active check for check_mk
368 if have_at_least_one_service:
369 outfile.write("""
370 # Active checks
372 define service {
373 use\t\t\t\t%s
374 host_name\t\t\t%s
375 %s service_description\t\tCheck_MK
377 """ % (config.active_service_template, hostname, _extra_service_conf_of(cfg, hostname, "Check_MK")))
379 # legacy checks via legacy_checks
380 legchecks = config.host_extra_conf(hostname, config.legacy_checks)
381 if len(legchecks) > 0:
382 outfile.write("\n\n# Legacy checks\n")
383 for command, description, has_perfdata in legchecks:
384 description = config.get_final_service_description(hostname, description)
385 if do_omit_service(hostname, description):
386 continue
388 if description in used_descriptions:
389 cn, it = used_descriptions[description]
390 core_config.warning(
391 "ERROR: Duplicate service description (legacy check) '%s' for host '%s'!\n"
392 " - 1st occurrance: checktype = %s, item = %r\n"
393 " - 2nd occurrance: checktype = legacy(%s), item = None\n" % (description, hostname,
394 cn, it, command))
395 continue
397 else:
398 used_descriptions[description] = ("legacy(" + command + ")", description)
400 extraconf = _extra_service_conf_of(cfg, hostname, description)
401 if has_perfdata:
402 template = "check_mk_perf,"
403 else:
404 template = ""
405 outfile.write("""
406 define service {
407 use\t\t\t\t%scheck_mk_default
408 host_name\t\t\t%s
409 service_description\t\t%s
410 check_command\t\t\t%s
411 active_checks_enabled\t\t1
413 """ % (template, hostname, cmk_base.utils.make_utf8(description), _simulate_command(cfg, command),
414 extraconf))
416 # write service dependencies for legacy checks
417 outfile.write(get_dependencies(hostname, description))
419 # legacy checks via active_checks
420 actchecks = []
421 for acttype, rules in config.active_checks.items():
422 entries = config.host_extra_conf(hostname, rules)
423 if entries:
424 # Skip Check_MK HW/SW Inventory for all ping hosts, even when the user has enabled
425 # the inventory for ping only hosts
426 if acttype == "cmk_inv" and config.is_ping_host(hostname):
427 continue
429 cfg.active_checks_to_define.add(acttype)
430 act_info = config.active_check_info[acttype]
431 for params in entries:
432 actchecks.append((acttype, act_info, params))
434 if actchecks:
435 outfile.write("\n\n# Active checks\n")
436 for acttype, act_info, params in actchecks:
437 # Make hostname available as global variable in argument functions
438 check_api_utils.set_hostname(hostname)
440 has_perfdata = act_info.get('has_perfdata', False)
441 description = config.active_check_service_description(hostname, acttype, params)
443 if do_omit_service(hostname, description):
444 continue
446 # compute argument, and quote ! and \ for Nagios
447 args = core_config.active_check_arguments(
448 hostname, description, act_info["argument_function"](params)).replace(
449 "\\", "\\\\").replace("!", "\\!")
451 if description in used_descriptions:
452 cn, it = used_descriptions[description]
453 # If we have the same active check again with the same description,
454 # then we do not regard this as an error, but simply ignore the
455 # second one. That way one can override a check with other settings.
456 if cn == "active(%s)" % acttype:
457 continue
459 core_config.warning(
460 "ERROR: Duplicate service description (active check) '%s' for host '%s'!\n"
461 " - 1st occurrance: checktype = %s, item = %r\n"
462 " - 2nd occurrance: checktype = active(%s), item = None\n" %
463 (description, hostname, cn, it, acttype))
464 continue
466 else:
467 used_descriptions[description] = ("active(" + acttype + ")", description)
469 template = "check_mk_perf," if has_perfdata else ""
470 extraconf = _extra_service_conf_of(cfg, hostname, description)
472 if host_attrs["address"] in ["0.0.0.0", "::"]:
473 command_name = "check-mk-custom"
474 command = command_name + "!echo \"CRIT - Failed to lookup IP address and no explicit IP address configured\" && exit 2"
475 cfg.custom_commands_to_define.add(command_name)
476 else:
477 command = "check_mk_active-%s!%s" % (acttype, args)
479 outfile.write("""
480 define service {
481 use\t\t\t\t%scheck_mk_default
482 host_name\t\t\t%s
483 service_description\t\t%s
484 check_command\t\t\t%s
485 active_checks_enabled\t\t1
487 """ % (template, hostname, cmk_base.utils.make_utf8(description),
488 cmk_base.utils.make_utf8(_simulate_command(cfg, command)), extraconf))
490 # write service dependencies for active checks
491 outfile.write(get_dependencies(hostname, description))
493 # Legacy checks via custom_checks
494 custchecks = config.host_extra_conf(hostname, config.custom_checks)
495 if custchecks:
496 outfile.write("\n\n# Custom checks\n")
497 for entry in custchecks:
498 # entries are dicts with the following keys:
499 # "service_description" Service description to use
500 # "command_line" (optional) Unix command line for executing the check
501 # If this is missing, we create a passive check
502 # "command_name" (optional) Name of Monitoring command to define. If missing,
503 # we use "check-mk-custom"
504 # "has_perfdata" (optional) If present and True, we activate perf_data
505 description = config.get_final_service_description(hostname,
506 entry["service_description"])
507 has_perfdata = entry.get("has_perfdata", False)
508 command_name = entry.get("command_name", "check-mk-custom")
509 command_line = entry.get("command_line", "")
511 if do_omit_service(hostname, description):
512 continue
514 if command_line:
515 command_line = core_config.autodetect_plugin(command_line).replace("\\",
516 "\\\\").replace(
517 "!", "\\!")
519 if "freshness" in entry:
520 freshness = " check_freshness\t\t1\n" + \
521 " freshness_threshold\t\t%d\n" % (60 * entry["freshness"]["interval"])
522 command_line = "echo %s && exit %d" % (_quote_nagios_string(
523 entry["freshness"]["output"]), entry["freshness"]["state"])
524 else:
525 freshness = ""
527 cfg.custom_commands_to_define.add(command_name)
529 if description in used_descriptions:
530 cn, it = used_descriptions[description]
531 # If we have the same active check again with the same description,
532 # then we do not regard this as an error, but simply ignore the
533 # second one.
534 if cn == "custom(%s)" % command_name:
535 continue
536 core_config.warning(
537 "ERROR: Duplicate service description (custom check) '%s' for host '%s'!\n"
538 " - 1st occurrance: checktype = %s, item = %r\n"
539 " - 2nd occurrance: checktype = custom(%s), item = %r\n" %
540 (description, hostname, cn, it, command_name, description))
541 continue
542 else:
543 used_descriptions[description] = ("custom(%s)" % command_name, description)
545 template = "check_mk_perf," if has_perfdata else ""
546 extraconf = _extra_service_conf_of(cfg, hostname, description)
547 command = "%s!%s" % (command_name, command_line)
548 outfile.write("""
549 define service {
550 use\t\t\t\t%scheck_mk_default
551 host_name\t\t\t%s
552 service_description\t\t%s
553 check_command\t\t\t%s
554 active_checks_enabled\t\t%d
555 %s%s}
556 """ % (template, hostname, cmk_base.utils.make_utf8(description), _simulate_command(cfg, command),
557 (command_line and not freshness) and 1 or 0, extraconf, freshness))
559 # write service dependencies for custom checks
560 outfile.write(get_dependencies(hostname, description))
562 # FIXME: Remove old name one day
563 service_discovery_name = 'Check_MK inventory'
564 if 'cmk-inventory' in config.use_new_descriptions_for:
565 service_discovery_name = 'Check_MK Discovery'
567 import cmk_base.discovery as discovery
568 params = discovery.discovery_check_parameters(hostname) or \
569 discovery.default_discovery_check_parameters()
571 # Inventory checks - if user has configured them.
572 if params["check_interval"] \
573 and not config.service_ignored(hostname, None, service_discovery_name) \
574 and not config.is_ping_host(hostname):
575 outfile.write("""
576 define service {
577 use\t\t\t\t%s
578 host_name\t\t\t%s
579 normal_check_interval\t\t%d
580 retry_check_interval\t\t%d
581 %s service_description\t\t%s
583 """ % (config.inventory_check_template, hostname,
584 params["check_interval"], params["check_interval"],
585 _extra_service_conf_of(cfg, hostname, service_discovery_name), service_discovery_name))
587 if have_at_least_one_service:
588 outfile.write("""
589 define servicedependency {
590 use\t\t\t\t%s
591 host_name\t\t\t%s
592 service_description\t\tCheck_MK
593 dependent_host_name\t\t%s
594 dependent_service_description\t%s
596 """ % (config.service_dependency_template, hostname, hostname, service_discovery_name))
598 # No check_mk service, no legacy service -> create PING service
599 if not have_at_least_one_service and not legchecks and not actchecks and not custchecks:
600 _add_ping_service(cfg, hostname, host_attrs["address"],
601 config.is_ipv6_primary(hostname) and 6 or 4, "PING",
602 host_attrs.get("_NODEIPS"))
604 if config.is_ipv4v6_host(hostname):
605 if config.is_ipv6_primary(hostname):
606 _add_ping_service(cfg, hostname, host_attrs["_ADDRESS_4"], 4, "PING IPv4",
607 host_attrs.get("_NODEIPS_4"))
608 else:
609 _add_ping_service(cfg, hostname, host_attrs["_ADDRESS_6"], 6, "PING IPv6",
610 host_attrs.get("_NODEIPS_6"))
613 def _add_ping_service(cfg, hostname, ipaddress, family, descr, node_ips):
614 arguments = core_config.check_icmp_arguments_of(hostname, family=family)
616 ping_command = 'check-mk-ping'
617 if config.is_cluster(hostname):
618 arguments += ' -m 1 ' + node_ips
619 else:
620 arguments += ' ' + ipaddress
622 cfg.outfile.write("""
623 define service {
624 use\t\t\t\t%s
625 service_description\t\t%s
626 check_command\t\t\t%s!%s
627 %s host_name\t\t\t%s
630 """ % (config.pingonly_template, descr, ping_command, arguments,
631 _extra_service_conf_of(cfg, hostname, descr), hostname))
634 def _simulate_command(cfg, command):
635 if config.simulation_mode:
636 cfg.custom_commands_to_define.add("check-mk-simulation")
637 return "check-mk-simulation!echo 'Simulation mode - cannot execute real check'"
638 return command
641 def _create_nagios_config_hostgroups(cfg):
642 outfile = cfg.outfile
643 if config.define_hostgroups:
644 outfile.write("\n# ------------------------------------------------------------\n")
645 outfile.write("# Host groups (controlled by define_hostgroups)\n")
646 outfile.write("# ------------------------------------------------------------\n")
647 hgs = list(cfg.hostgroups_to_define)
648 hgs.sort()
649 for hg in hgs:
650 try:
651 alias = config.define_hostgroups[hg]
652 except:
653 alias = hg
654 outfile.write("""
655 define hostgroup {
656 hostgroup_name\t\t%s
657 alias\t\t\t\t%s
659 """ % (cmk_base.utils.make_utf8(hg), cmk_base.utils.make_utf8(alias)))
661 # No creation of host groups but we need to define
662 # default host group
663 elif config.default_host_group in cfg.hostgroups_to_define:
664 outfile.write("""
665 define hostgroup {
666 hostgroup_name\t\t%s
667 alias\t\t\t\tCheck_MK default hostgroup
669 """ % config.default_host_group)
672 def _create_nagios_config_servicegroups(cfg):
673 outfile = cfg.outfile
674 if config.define_servicegroups:
675 outfile.write("\n# ------------------------------------------------------------\n")
676 outfile.write("# Service groups (controlled by define_servicegroups)\n")
677 outfile.write("# ------------------------------------------------------------\n")
678 sgs = list(cfg.servicegroups_to_define)
679 sgs.sort()
680 for sg in sgs:
681 try:
682 alias = config.define_servicegroups[sg]
683 except:
684 alias = sg
685 outfile.write("""
686 define servicegroup {
687 servicegroup_name\t\t%s
688 alias\t\t\t\t%s
690 """ % (cmk_base.utils.make_utf8(sg), cmk_base.utils.make_utf8(alias)))
693 def _create_nagios_config_contactgroups(cfg):
694 outfile = cfg.outfile
695 if config.define_contactgroups is False:
696 return
698 cgs = list(cfg.contactgroups_to_define)
699 if not cgs:
700 return
702 outfile.write("\n# ------------------------------------------------------------\n")
703 outfile.write("# Contact groups (controlled by define_contactgroups)\n")
704 outfile.write("# ------------------------------------------------------------\n\n")
705 for name in sorted(cgs):
706 if isinstance(config.define_contactgroups, dict):
707 alias = config.define_contactgroups.get(name, name)
708 else:
709 alias = name
711 outfile.write("\ndefine contactgroup {\n"
712 " contactgroup_name\t\t%s\n"
713 " alias\t\t\t\t%s\n" % (cmk_base.utils.make_utf8(name),
714 cmk_base.utils.make_utf8(alias)))
716 members = config.contactgroup_members.get(name)
717 if members:
718 outfile.write(" members\t\t\t%s\n" % ",".join(members))
720 outfile.write("}\n")
723 def _create_nagios_config_commands(cfg):
724 outfile = cfg.outfile
725 if config.generate_dummy_commands:
726 outfile.write("\n# ------------------------------------------------------------\n")
727 outfile.write("# Dummy check commands and active check commands\n")
728 outfile.write("# ------------------------------------------------------------\n\n")
729 for checkname in cfg.checknames_to_define:
730 outfile.write("""define command {
731 command_name\t\t\tcheck_mk-%s
732 command_line\t\t\t%s
735 """ % (checkname, config.dummy_check_commandline))
737 # active_checks
738 for acttype in cfg.active_checks_to_define:
739 act_info = config.active_check_info[acttype]
740 outfile.write("""define command {
741 command_name\t\t\tcheck_mk_active-%s
742 command_line\t\t\t%s
745 """ % (acttype, act_info["command_line"]))
747 # custom_checks
748 for command_name in cfg.custom_commands_to_define:
749 outfile.write("""define command {
750 command_name\t\t\t%s
751 command_line\t\t\t$ARG1$
754 """ % command_name)
756 # custom host checks
757 for command_name, command_line in cfg.hostcheck_commands_to_define:
758 outfile.write("""define command {
759 command_name\t\t\t%s
760 command_line\t\t\t%s
763 """ % (command_name, command_line))
766 def _create_nagios_config_timeperiods(cfg):
767 outfile = cfg.outfile
768 if len(config.timeperiods) > 0:
769 outfile.write("\n# ------------------------------------------------------------\n")
770 outfile.write("# Timeperiod definitions (controlled by variable 'timeperiods')\n")
771 outfile.write("# ------------------------------------------------------------\n\n")
772 tpnames = config.timeperiods.keys()
773 tpnames.sort()
774 for name in tpnames:
775 tp = config.timeperiods[name]
776 outfile.write("define timeperiod {\n timeperiod_name\t\t%s\n" % name)
777 if "alias" in tp:
778 outfile.write(" alias\t\t\t\t%s\n" % cmk_base.utils.make_utf8(tp["alias"]))
779 for key, value in tp.items():
780 if key not in ["alias", "exclude"]:
781 times = ",".join([("%s-%s" % (fr, to)) for (fr, to) in value])
782 if times:
783 outfile.write(" %-20s\t\t%s\n" % (key, times))
784 if "exclude" in tp:
785 outfile.write(" exclude\t\t\t%s\n" % ",".join(tp["exclude"]))
786 outfile.write("}\n\n")
789 def _create_nagios_config_contacts(cfg, hostnames):
790 outfile = cfg.outfile
791 if len(config.contacts) > 0:
792 outfile.write("\n# ------------------------------------------------------------\n")
793 outfile.write("# Contact definitions (controlled by variable 'contacts')\n")
794 outfile.write("# ------------------------------------------------------------\n\n")
795 cnames = config.contacts.keys()
796 cnames.sort()
797 for cname in cnames:
798 contact = config.contacts[cname]
799 # Create contact groups in nagios, even when they are empty. This is needed
800 # for RBN to work correctly when using contactgroups as recipients which are
801 # not assigned to any host
802 cfg.contactgroups_to_define.update(contact.get("contactgroups", []))
803 # If the contact is in no contact group or all of the contact groups
804 # of the contact have neither hosts nor services assigned - in other
805 # words if the contact is not assigned to any host or service, then
806 # we do not create this contact in Nagios. It's useless and will produce
807 # warnings.
808 cgrs = [
809 cgr for cgr in contact.get("contactgroups", [])
810 if cgr in cfg.contactgroups_to_define
812 if not cgrs:
813 continue
815 outfile.write(
816 "define contact {\n contact_name\t\t\t%s\n" % cmk_base.utils.make_utf8(cname))
817 if "alias" in contact:
818 outfile.write(" alias\t\t\t\t%s\n" % cmk_base.utils.make_utf8(contact["alias"]))
819 if "email" in contact:
820 outfile.write(" email\t\t\t\t%s\n" % cmk_base.utils.make_utf8(contact["email"]))
821 if "pager" in contact:
822 outfile.write(" pager\t\t\t\t%s\n" % contact["pager"])
823 if config.enable_rulebased_notifications:
824 not_enabled = False
825 else:
826 not_enabled = contact.get("notifications_enabled", True)
828 for what in ["host", "service"]:
829 no = contact.get(what + "_notification_options", "")
830 if not no or not not_enabled:
831 outfile.write(" %s_notifications_enabled\t0\n" % what)
832 no = "n"
833 outfile.write(" %s_notification_options\t%s\n" % (what, ",".join(list(no))))
834 outfile.write(" %s_notification_period\t%s\n" %
835 (what, contact.get("notification_period", "24X7")))
836 outfile.write(
837 " %s_notification_commands\t%s\n" %
838 (what, contact.get("%s_notification_commands" % what, "check-mk-notify")))
839 # Add custom macros
840 for macro in [m for m in contact.keys() if m.startswith('_')]:
841 outfile.write(" %s\t%s\n" % (macro, contact[macro]))
843 outfile.write(" contactgroups\t\t\t%s\n" % ", ".join(cgrs))
844 outfile.write("}\n\n")
846 if config.enable_rulebased_notifications and hostnames:
847 cfg.contactgroups_to_define.add("check-mk-notify")
848 outfile.write("# Needed for rule based notifications\n"
849 "define contact {\n"
850 " contact_name\t\t\tcheck-mk-notify\n"
851 " alias\t\t\t\tContact for rule based notifications\n"
852 " host_notification_options\td,u,r,f,s\n"
853 " service_notification_options\tu,c,w,r,f,s\n"
854 " host_notification_period\t24X7\n"
855 " service_notification_period\t24X7\n"
856 " host_notification_commands\tcheck-mk-notify\n"
857 " service_notification_commands\tcheck-mk-notify\n"
858 " contactgroups\t\t\tcheck-mk-notify\n"
859 "}\n\n")
862 # Quote string for use in a nagios command execution.
863 # Please note that also quoting for ! and \ vor Nagios
864 # itself takes place here.
865 def _quote_nagios_string(s):
866 return "'" + s.replace('\\', '\\\\').replace("'", "'\"'\"'").replace('!', '\\!') + "'"
869 def _extra_host_conf_of(hostname, exclude=None):
870 if exclude is None:
871 exclude = []
872 return _extra_conf_of(config.extra_host_conf, hostname, None, exclude)
875 # Collect all extra configuration data for a service
876 def _extra_service_conf_of(cfg, hostname, description):
877 conf = ""
879 # Contact groups
880 sercgr = config.service_extra_conf(hostname, description, config.service_contactgroups)
881 cfg.contactgroups_to_define.update(sercgr)
882 if len(sercgr) > 0:
883 if config.enable_rulebased_notifications:
884 sercgr.append("check-mk-notify") # not nessary if not explicit groups defined
885 conf += " contact_groups\t\t" + ",".join(sercgr) + "\n"
887 sergr = config.service_extra_conf(hostname, description, config.service_groups)
888 if len(sergr) > 0:
889 conf += " service_groups\t\t" + ",".join(sergr) + "\n"
890 if config.define_servicegroups:
891 cfg.servicegroups_to_define.update(sergr)
892 conf += _extra_conf_of(config.extra_service_conf, hostname, description)
893 return conf.encode("utf-8")
896 def _extra_conf_of(confdict, hostname, service, exclude=None):
897 if exclude is None:
898 exclude = []
900 result = ""
901 for key, conflist in confdict.items():
902 if service is not None:
903 values = config.service_extra_conf(hostname, service, conflist)
904 else:
905 values = config.host_extra_conf(hostname, conflist)
907 if exclude and key in exclude:
908 continue
910 if values:
911 result += " %-29s %s\n" % (key, values[0])
913 return result
917 # .--Precompile----------------------------------------------------------.
918 # | ____ _ _ |
919 # | | _ \ _ __ ___ ___ ___ _ __ ___ _ __ (_) | ___ |
920 # | | |_) | '__/ _ \/ __/ _ \| '_ ` _ \| '_ \| | |/ _ \ |
921 # | | __/| | | __/ (_| (_) | | | | | | |_) | | | __/ |
922 # | |_| |_| \___|\___\___/|_| |_| |_| .__/|_|_|\___| |
923 # | |_| |
924 # +----------------------------------------------------------------------+
925 # | Precompiling creates on dedicated Python file per host, which just |
926 # | contains that code and information that is needed for executing all |
927 # | checks of that host. Also static data that cannot change during the |
928 # | normal monitoring process is being precomputed and hard coded. This |
929 # | all saves substantial CPU resources as opposed to running Check_MK |
930 # | in adhoc mode (about 75%). |
931 # '----------------------------------------------------------------------'
934 # Find files to be included in precompile host check for a certain
935 # check (for example df or mem.used). In case of checks with a period
936 # (subchecks) we might have to include both "mem" and "mem.used". The
937 # subcheck *may* be implemented in a separate file.
938 def _find_check_plugins(checktype):
939 if '.' in checktype:
940 candidates = [cmk_base.check_utils.section_name_of(checktype), checktype]
941 else:
942 candidates = [checktype]
944 paths = []
945 for candidate in candidates:
946 filename = cmk.utils.paths.local_checks_dir + "/" + candidate
947 if os.path.exists(filename):
948 paths.append(filename)
949 continue
951 filename = cmk.utils.paths.checks_dir + "/" + candidate
952 if os.path.exists(filename):
953 paths.append(filename)
955 return paths
958 def precompile_hostchecks():
959 console.verbose("Creating precompiled host check config...\n")
960 config.PackedConfig().save()
962 if not os.path.exists(cmk.utils.paths.precompiled_hostchecks_dir):
963 os.makedirs(cmk.utils.paths.precompiled_hostchecks_dir)
965 console.verbose("Precompiling host checks...\n")
966 for host in config.all_active_hosts():
967 try:
968 _precompile_hostcheck(host)
969 except Exception as e:
970 if cmk.utils.debug.enabled():
971 raise
972 console.error("Error precompiling checks for host %s: %s\n" % (host, e))
973 sys.exit(5)
976 # read python file and strip comments
977 g_stripped_file_cache = {} # type: Dict[str, str]
980 def stripped_python_file(filename):
981 if filename in g_stripped_file_cache:
982 return g_stripped_file_cache[filename]
983 a = ""
984 for line in file(filename):
985 l = line.strip()
986 if l == "" or l[0] != '#':
987 a += line # not stripped line because of indentation!
988 g_stripped_file_cache[filename] = a
989 return a
992 def _precompile_hostcheck(hostname):
993 import cmk_base.check_table as check_table
995 console.verbose("%s%s%-16s%s:", tty.bold, tty.blue, hostname, tty.normal, stream=sys.stderr)
997 compiled_filename = cmk.utils.paths.precompiled_hostchecks_dir + "/" + hostname
998 source_filename = compiled_filename + ".py"
999 for fname in [compiled_filename, source_filename]:
1000 try:
1001 os.remove(fname)
1002 except:
1003 pass
1005 # check table, enriched with addition precompiled information.
1006 host_check_table = check_table.get_precompiled_check_table(
1007 hostname, filter_mode="include_clustered", skip_ignored=False)
1008 if not host_check_table:
1009 console.verbose("(no Check_MK checks)\n")
1010 return
1012 output = file(source_filename + ".new", "w")
1013 output.write("#!/usr/bin/env python\n")
1014 output.write("# encoding: utf-8\n\n")
1016 output.write("import sys\n\n")
1018 output.write("if not sys.executable.startswith('/omd'):\n")
1019 output.write(" sys.stdout.write(\"ERROR: Only executable with sites python\\n\")\n")
1020 output.write(" sys.exit(2)\n\n")
1022 # Remove precompiled directory from sys.path. Leaving it in the path
1023 # makes problems when host names (name of precompiled files) are equal
1024 # to python module names like "random"
1025 output.write("sys.path.pop(0)\n")
1027 output.write("import cmk.utils.log\n")
1028 output.write("import cmk.utils.debug\n")
1029 output.write("from cmk.utils.exceptions import MKTerminate\n")
1030 output.write("\n")
1031 output.write("import cmk_base.utils\n")
1032 output.write("import cmk_base.config as config\n")
1033 output.write("import cmk_base.console as console\n")
1034 output.write("import cmk_base.checking as checking\n")
1035 output.write("import cmk_base.check_api as check_api\n")
1036 output.write("import cmk_base.ip_lookup as ip_lookup\n")
1038 # Self-compile: replace symlink with precompiled python-code, if
1039 # we are run for the first time
1040 if config.delay_precompile:
1041 output.write("""
1042 import os
1043 if os.path.islink(%(dst)r):
1044 import py_compile
1045 os.remove(%(dst)r)
1046 py_compile.compile(%(src)r, %(dst)r, %(dst)r, True)
1047 os.chmod(%(dst)r, 0755)
1049 """ % {
1050 "src": source_filename,
1051 "dst": compiled_filename
1054 # Register default Check_MK signal handler
1055 output.write("cmk_base.utils.register_sigint_handler()\n")
1057 # initialize global variables
1058 output.write("""
1059 # very simple commandline parsing: only -v (once or twice) and -d are supported
1061 cmk.utils.log.setup_console_logging()
1062 logger = cmk.utils.log.get_logger("base")
1064 # TODO: This is not really good parsing, because it not cares about syntax like e.g. "-nv".
1065 # The later regular argument parsing is handling this correctly. Try to clean this up.
1066 cmk.utils.log.set_verbosity(verbosity=len([ a for a in sys.argv if a in [ "-v", "--verbose"] ]))
1068 if '-d' in sys.argv:
1069 cmk.utils.debug.enable()
1071 """)
1073 needed_check_plugin_names = _get_needed_check_plugin_names(hostname, host_check_table)
1075 output.write("config.load_checks(check_api.get_check_api_context, %r)\n" %
1076 _get_needed_check_file_names(needed_check_plugin_names))
1078 for check_plugin_name in sorted(needed_check_plugin_names):
1079 console.verbose(" %s%s%s", tty.green, check_plugin_name, tty.normal, stream=sys.stderr)
1081 output.write("config.load_packed_config()\n")
1083 # handling of clusters
1084 if config.is_cluster(hostname):
1085 cluster_nodes = config.nodes_of(hostname)
1086 output.write("config.is_cluster = lambda h: h == %r\n" % hostname)
1088 nodes_of_map = {hostname: cluster_nodes}
1089 for node in config.nodes_of(hostname):
1090 nodes_of_map[node] = None
1091 output.write("config.nodes_of = lambda h: %r[h]\n" % nodes_of_map)
1092 else:
1093 output.write("config.is_cluster = lambda h: False\n")
1094 output.write("config.nodes_of = lambda h: None\n")
1096 # IP addresses
1097 needed_ipaddresses, needed_ipv6addresses, = {}, {}
1098 if config.is_cluster(hostname):
1099 for node in config.nodes_of(hostname):
1100 if config.is_ipv4_host(node):
1101 needed_ipaddresses[node] = ip_lookup.lookup_ipv4_address(node)
1103 if config.is_ipv6_host(node):
1104 needed_ipv6addresses[node] = ip_lookup.lookup_ipv6_address(node)
1106 try:
1107 if config.is_ipv4_host(hostname):
1108 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1109 except:
1110 pass
1112 try:
1113 if config.is_ipv6_host(hostname):
1114 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1115 except:
1116 pass
1117 else:
1118 if config.is_ipv4_host(hostname):
1119 needed_ipaddresses[hostname] = ip_lookup.lookup_ipv4_address(hostname)
1121 if config.is_ipv6_host(hostname):
1122 needed_ipv6addresses[hostname] = ip_lookup.lookup_ipv6_address(hostname)
1124 output.write("config.ipaddresses = %r\n\n" % needed_ipaddresses)
1125 output.write("config.ipv6addresses = %r\n\n" % needed_ipv6addresses)
1127 # datasource programs. Is this host relevant?
1129 # I think this is not needed anymore. Keep it here for reference
1131 ## Parameters for checks: Default values are defined in checks/*. The
1132 ## variables might be overridden by the user in main.mk. We need
1133 ## to set the actual values of those variables here. Otherwise the users'
1134 ## settings would get lost. But we only need to set those variables that
1135 ## influence the check itself - not those needed during inventory.
1136 #for var in config.check_config_variables:
1137 # output.write("%s = %r\n" % (var, getattr(config, var)))
1139 ## The same for those checks that use the new API
1140 #for check_type in needed_check_types:
1141 # # Note: check_type might not be in config.check_info. This is
1142 # # the case, if "mem" has been added to "extra_sections" and thus
1143 # # to "needed_check_types" - despite the fact that only subchecks
1144 # # mem.* exist
1145 # if check_type in config.check_info:
1146 # for var in config.check_info[check_type].get("check_config_variables", []):
1147 # output.write("%s = %r\n" % (var, getattr(config, var)))
1149 # perform actual check with a general exception handler
1150 output.write("try:\n")
1151 output.write(" sys.exit(checking.do_check(%r, None))\n" % hostname)
1152 output.write("except MKTerminate:\n")
1153 output.write(" console.output('<Interrupted>\\n', stream=sys.stderr)\n")
1154 output.write(" sys.exit(1)\n")
1155 output.write("except SystemExit, e:\n")
1156 output.write(" sys.exit(e.code)\n")
1157 output.write("except Exception, e:\n")
1158 output.write(" import traceback, pprint\n")
1160 # status output message
1161 output.write(
1162 " sys.stdout.write(\"UNKNOWN - Exception in precompiled check: %s (details in long output)\\n\" % e)\n"
1165 # generate traceback for long output
1166 output.write(" sys.stdout.write(\"Traceback: %s\\n\" % traceback.format_exc())\n")
1168 output.write("\n")
1169 output.write(" sys.exit(3)\n")
1170 output.close()
1172 # compile python (either now or delayed), but only if the source
1173 # code has not changed. The Python compilation is the most costly
1174 # operation here.
1175 if os.path.exists(source_filename):
1176 if file(source_filename).read() == file(source_filename + ".new").read():
1177 console.verbose(" (%s is unchanged)\n", source_filename, stream=sys.stderr)
1178 os.remove(source_filename + ".new")
1179 return
1180 else:
1181 console.verbose(" (new content)", stream=sys.stderr)
1183 os.rename(source_filename + ".new", source_filename)
1184 if not config.delay_precompile:
1185 py_compile.compile(source_filename, compiled_filename, compiled_filename, True)
1186 os.chmod(compiled_filename, 0755)
1187 else:
1188 if os.path.exists(compiled_filename) or os.path.islink(compiled_filename):
1189 os.remove(compiled_filename)
1190 os.symlink(hostname + ".py", compiled_filename)
1192 console.verbose(" ==> %s.\n", compiled_filename, stream=sys.stderr)
1195 def _get_needed_check_plugin_names(hostname, host_check_table):
1196 import cmk_base.check_table as check_table
1198 needed_check_plugin_names = set([])
1200 # In case the host is monitored as special agent, the check plugin for the special agent needs
1201 # to be loaded
1202 sources = data_sources.DataSources(hostname, ipaddress=None)
1203 for source in sources.get_data_sources():
1204 if isinstance(source, data_sources.programs.SpecialAgentDataSource):
1205 needed_check_plugin_names.add(source.special_agent_plugin_file_name)
1207 # Collect the needed check plugin names using the host check table
1208 for check_plugin_name, _unused_item, _unused_param, _descr in host_check_table:
1209 if check_plugin_name not in config.check_info:
1210 sys.stderr.write('Warning: Ignoring missing check %s.\n' % check_plugin_name)
1211 continue
1213 if config.check_info[check_plugin_name].get("extra_sections"):
1214 for section_name in config.check_info[check_plugin_name]["extra_sections"]:
1215 if section_name in config.check_info:
1216 needed_check_plugin_names.add(section_name)
1218 needed_check_plugin_names.add(check_plugin_name)
1220 # Also include the check plugins of the cluster nodes to be able to load
1221 # the autochecks of the nodes
1222 if config.is_cluster(hostname):
1223 for node in config.nodes_of(hostname):
1224 needed_check_plugin_names.update(
1225 [e[0] for e in check_table.get_precompiled_check_table(node, skip_ignored=False)])
1227 return needed_check_plugin_names
1230 def _get_needed_check_file_names(needed_check_plugin_names):
1231 # check info table
1232 # We need to include all those plugins that are referenced in the host's
1233 # check table.
1234 filenames = []
1235 for check_plugin_name in needed_check_plugin_names:
1236 section_name = cmk_base.check_utils.section_name_of(check_plugin_name)
1237 # Add library files needed by check (also look in local)
1238 for lib in set(config.check_includes.get(section_name, [])):
1239 if os.path.exists(cmk.utils.paths.local_checks_dir + "/" + lib):
1240 to_add = cmk.utils.paths.local_checks_dir + "/" + lib
1241 else:
1242 to_add = cmk.utils.paths.checks_dir + "/" + lib
1244 if to_add not in filenames:
1245 filenames.append(to_add)
1247 # Now add check file(s) itself
1248 paths = _find_check_plugins(check_plugin_name)
1249 if not paths:
1250 raise MKGeneralException("Cannot find check file %s needed for check type %s" % \
1251 (section_name, check_plugin_name))
1253 for path in paths:
1254 if path not in filenames:
1255 filenames.append(path)
1257 return filenames