2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
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.
31 from typing
import Any
, List
# pylint: disable=unused-import
33 import cmk
.utils
.paths
34 import cmk
.utils
.tty
as tty
35 import cmk
.utils
.password_store
36 from cmk
.utils
.exceptions
import MKGeneralException
38 import cmk_base
.console
as console
39 import cmk_base
.config
as config
40 import cmk_base
.ip_lookup
as ip_lookup
43 class MonitoringCore(object):
44 __metaclass__
= abc
.ABCMeta
47 def create_config(self
):
55 _ignore_ip_lookup_failures
= False
56 _failed_ip_lookups
= []
59 # .--Warnings------------------------------------------------------------.
61 # | \ \ / /_ _ _ __ _ __ (_)_ __ __ _ ___ |
62 # | \ \ /\ / / _` | '__| '_ \| | '_ \ / _` / __| |
63 # | \ V V / (_| | | | | | | | | | | (_| \__ \ |
64 # | \_/\_/ \__,_|_| |_| |_|_|_| |_|\__, |___/ |
66 # +----------------------------------------------------------------------+
67 # | Managing of warning messages occuring during configuration building |
68 # '----------------------------------------------------------------------'
70 g_configuration_warnings
= [] # type: List[Any]
73 def initialize_warnings():
74 global g_configuration_warnings
75 g_configuration_warnings
= []
79 g_configuration_warnings
.append(text
)
80 console
.warning("\n%s", text
, stream
=sys
.stdout
)
83 def get_configuration_warnings():
84 num_warnings
= len(g_configuration_warnings
)
87 warnings
= g_configuration_warnings
[:10] + \
88 [ "%d further warnings have been omitted" % (num_warnings
- 10) ]
90 warnings
= g_configuration_warnings
95 # TODO: Cleanup the hostcheck_commands_to_define, custom_commands_to_define thing
96 def host_check_command(hostname
,
99 hostcheck_commands_to_define
=None,
100 custom_commands_to_define
=None):
101 # Check dedicated host check command
102 values
= config
.host_extra_conf(hostname
, config
.host_check_commands
)
105 elif config
.is_no_ip_host(hostname
):
107 elif config
.monitoring_core
== "cmc":
112 if config
.monitoring_core
!= "cmc" and value
== "smart":
113 value
= "ping" # avoid problems when switching back to nagios core
115 if value
== "smart" and not is_clust
:
116 return "check-mk-host-smart"
118 elif value
in ["ping", "smart"]: # Cluster host
119 ping_args
= check_icmp_arguments_of(hostname
)
121 if is_clust
and ip
: # Do check cluster IP address if one is there
122 return "check-mk-host-ping!%s" % ping_args
123 elif ping_args
and is_clust
: # use check_icmp in cluster mode
124 return "check-mk-host-ping-cluster!%s" % ping_args
125 elif ping_args
: # use special arguments
126 return "check-mk-host-ping!%s" % ping_args
131 return "check-mk-host-ok"
133 elif value
== "agent" or value
[0] == "service":
134 service
= "Check_MK" if value
== "agent" else value
[1]
136 if config
.monitoring_core
== "cmc":
137 return "check-mk-host-service!" + service
139 command
= "check-mk-host-custom-%d" % (len(hostcheck_commands_to_define
) + 1)
140 hostcheck_commands_to_define
.append(
141 (command
, 'echo "$SERVICEOUTPUT:%s:%s$" && exit $SERVICESTATEID:%s:%s$' %
142 (hostname
, service
.replace('$HOSTNAME$', hostname
), hostname
,
143 service
.replace('$HOSTNAME$', hostname
))))
146 elif value
[0] == "tcp":
147 return "check-mk-host-tcp!" + str(value
[1])
149 elif value
[0] == "custom":
151 custom_commands_to_define
.add("check-mk-custom")
153 pass # not needed and not available with CMC
154 return "check-mk-custom!" + autodetect_plugin(value
[1])
156 raise MKGeneralException(
157 "Invalid value %r for host_check_command of host %s." % (value
, hostname
))
160 def autodetect_plugin(command_line
):
161 plugin_name
= command_line
.split()[0]
162 if command_line
[0] not in ['$', '/']:
164 for directory
in ["/local", ""]:
165 path
= cmk
.utils
.paths
.omd_root
+ directory
+ "/lib/nagios/plugins/"
166 if os
.path
.exists(path
+ plugin_name
):
167 command_line
= path
+ command_line
174 def icons_and_actions_of(what
, hostname
, svcdesc
=None, checkname
=None, params
=None):
176 return list(set(config
.host_extra_conf(hostname
, config
.host_icons_and_actions
)))
179 config
.service_extra_conf(hostname
, svcdesc
, config
.service_icons_and_actions
))
181 # Some WATO rules might register icons on their own
183 checkgroup
= config
.check_info
[checkname
]["group"]
184 if checkgroup
in ['ps', 'services'] and isinstance(params
, dict):
185 icon
= params
.get('icon')
192 def check_icmp_arguments_of(hostname
, add_defaults
=True, family
=None):
193 values
= config
.host_extra_conf(hostname
, config
.ping_levels
)
195 for value
in values
[::-1]: # make first rules have precedence
197 if not add_defaults
and not levels
:
201 family
= 6 if config
.is_ipv6_primary(hostname
) else 4
210 for key
, value
in levels
.items():
212 args
.append("-t %d" % value
)
213 elif key
== "packets":
214 args
.append("-n %d" % value
)
219 args
.append("-w %.2f,%.2f%%" % (rta
[0], loss
[0]))
220 args
.append("-c %.2f,%.2f%%" % (rta
[1], loss
[1]))
221 return " ".join(args
)
225 # .--Core Config---------------------------------------------------------.
227 # | / ___|___ _ __ ___ / ___|___ _ __ / _(_) __ _ |
228 # | | | / _ \| '__/ _ \ | | / _ \| '_ \| |_| |/ _` | |
229 # | | |__| (_) | | | __/ | |__| (_) | | | | _| | (_| | |
230 # | \____\___/|_| \___| \____\___/|_| |_|_| |_|\__, | |
232 # +----------------------------------------------------------------------+
233 # | Code for managing the core configuration creation. |
234 # '----------------------------------------------------------------------'
237 # TODO: Move to modes?
238 def do_create_config(core
, with_agents
):
239 console
.output("Generating configuration for core (type %s)..." % config
.monitoring_core
)
240 create_core_config(core
)
241 console
.output(tty
.ok
+ "\n")
245 import cmk_base
.cee
.agent_bakery
246 cmk_base
.cee
.agent_bakery
.bake_on_restart()
251 def create_core_config(core
):
252 initialize_warnings()
254 _verify_non_duplicate_hosts()
255 _verify_non_deprecated_checkgroups()
257 cmk
.utils
.password_store
.save(config
.stored_passwords
)
259 return get_configuration_warnings()
262 # Verify that the user has no deprecated check groups configured.
263 def _verify_non_deprecated_checkgroups():
264 groups
= config
.checks_by_checkgroup()
266 for checkgroup
in config
.checkgroup_parameters
:
267 if checkgroup
not in groups
:
269 "Found configured rules of deprecated check group \"%s\". These rules are not used "
270 "by any check. Maybe this check group has been renamed during an update, "
271 "in this case you will have to migrate your configuration to the new ruleset manually. "
272 "Please check out the release notes of the involved versions. "
273 "You may use the page \"Deprecated rules\" in WATO to view your rules and move them to "
274 "the new rulesets." % checkgroup
)
277 def _verify_non_duplicate_hosts():
278 duplicates
= config
.duplicate_hosts()
280 warning("The following host names have duplicates: %s. "
281 "This might lead to invalid/incomplete monitoring for these hosts." %
282 ", ".join(duplicates
))
285 def do_update(core
, with_precompile
):
287 do_create_config(core
, with_agents
=with_precompile
)
291 except Exception as e
:
292 console
.error("Configuration Error: %s\n" % e
)
293 if cmk
.utils
.debug
.enabled():
299 # .--Active Checks-------------------------------------------------------.
301 # | / \ ___| |_(_)_ _____ / ___| |__ ___ ___| | _____ |
302 # | / _ \ / __| __| \ \ / / _ \ | | | '_ \ / _ \/ __| |/ / __| |
303 # | / ___ \ (__| |_| |\ V / __/ | |___| | | | __/ (__| <\__ \ |
304 # | /_/ \_\___|\__|_| \_/ \___| \____|_| |_|\___|\___|_|\_\___/ |
306 # +----------------------------------------------------------------------+
307 # | Active check specific functions |
308 # '----------------------------------------------------------------------'
311 def active_check_arguments(hostname
, description
, args
):
312 if not isinstance(args
, (str, unicode, list)):
313 raise MKGeneralException(
314 "The check argument function needs to return either a list of arguments or a "
315 "string of the concatenated arguments (Host: %s, Service: %s)." % (hostname
,
318 return config
.prepare_check_command(args
, hostname
, description
)
322 # .--HostAttributes------------------------------------------------------.
323 # | _ _ _ _ _ _ _ _ _ |
324 # || | | | ___ ___| |_ / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___ |
325 # || |_| |/ _ \/ __| __| / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __| |
326 # || _ | (_) \__ \ |_ / ___ \ |_| |_| | | | |_) | |_| | || __/\__ \ |
327 # ||_| |_|\___/|___/\__/_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/ |
329 # +----------------------------------------------------------------------+
330 # | Managing of host attributes |
331 # '----------------------------------------------------------------------'
334 def get_host_attributes(hostname
, tags
):
335 attrs
= _extra_host_attributes(hostname
)
337 attrs
["_TAGS"] = " ".join(tags
)
339 if "alias" not in attrs
:
340 attrs
["alias"] = config
.alias_of(hostname
, hostname
)
342 # Now lookup configured IP addresses
343 if config
.is_ipv4_host(hostname
):
344 attrs
["_ADDRESS_4"] = ip_address_of(hostname
, 4)
345 if attrs
["_ADDRESS_4"] is None:
346 attrs
["_ADDRESS_4"] = ""
348 attrs
["_ADDRESS_4"] = ""
350 if config
.is_ipv6_host(hostname
):
351 attrs
["_ADDRESS_6"] = ip_address_of(hostname
, 6)
352 if attrs
["_ADDRESS_6"] is None:
353 attrs
["_ADDRESS_6"] = ""
355 attrs
["_ADDRESS_6"] = ""
357 ipv6_primary
= config
.is_ipv6_primary(hostname
)
359 attrs
["address"] = attrs
["_ADDRESS_6"]
360 attrs
["_ADDRESS_FAMILY"] = "6"
362 attrs
["address"] = attrs
["_ADDRESS_4"]
363 attrs
["_ADDRESS_FAMILY"] = "4"
365 add_ipv4addrs
, add_ipv6addrs
= config
.get_additional_ipaddresses_of(hostname
)
367 attrs
["_ADDRESSES_4"] = " ".join(add_ipv4addrs
)
368 for nr
, ipv4_address
in enumerate(add_ipv4addrs
):
369 key
= "_ADDRESS_4_%s" % (nr
+ 1)
370 attrs
[key
] = ipv4_address
373 attrs
["_ADDRESSES_6"] = " ".join(add_ipv6addrs
)
374 for nr
, ipv6_address
in enumerate(add_ipv6addrs
):
375 key
= "_ADDRESS_6_%s" % (nr
+ 1)
376 attrs
[key
] = ipv6_address
378 # Add the optional WATO folder path
379 path
= config
.host_paths
.get(hostname
)
381 attrs
["_FILENAME"] = path
383 # Add custom user icons and actions
384 actions
= icons_and_actions_of("host", hostname
)
386 attrs
["_ACTIONS"] = ",".join(actions
)
388 if cmk
.is_managed_edition():
389 attrs
["_CUSTOMER"] = config
.current_customer
# pylint: disable=no-member
394 def _extra_host_attributes(hostname
):
396 for key
, conflist
in config
.extra_host_conf
.items():
397 values
= config
.host_extra_conf(hostname
, conflist
)
402 if values
[0] is not None:
403 attrs
[key
] = values
[0]
407 def get_cluster_attributes(hostname
, nodes
):
408 sorted_nodes
= sorted(nodes
)
411 "_NODENAMES": " ".join(sorted_nodes
),
414 if config
.is_ipv4_host(hostname
):
415 for h
in sorted_nodes
:
416 addr
= ip_address_of(h
, 4)
418 node_ips_4
.append(addr
)
420 node_ips_4
.append(fallback_ip_for(hostname
, 4))
423 if config
.is_ipv6_host(hostname
):
424 for h
in sorted_nodes
:
425 addr
= ip_address_of(h
, 6)
427 node_ips_6
.append(addr
)
429 node_ips_6
.append(fallback_ip_for(hostname
, 6))
431 if config
.is_ipv6_primary(hostname
):
432 node_ips
= node_ips_6
434 node_ips
= node_ips_4
436 for suffix
, val
in [("", node_ips
), ("_4", node_ips_4
), ("_6", node_ips_6
)]:
437 attrs
["_NODEIPS%s" % suffix
] = " ".join(val
)
442 def get_cluster_nodes_for_config(hostname
):
443 _verify_cluster_address_family(hostname
)
445 nodes
= config
.nodes_of(hostname
)[:]
447 if node
not in config
.all_active_realhosts():
448 warning("Node '%s' of cluster '%s' is not a monitored host in this site." % (node
,
454 def _verify_cluster_address_family(hostname
):
455 cluster_host_family
= "IPv6" if config
.is_ipv6_primary(hostname
) else "IPv4"
458 "%s: %s" % (hostname
, cluster_host_family
),
461 address_family
= cluster_host_family
463 for nodename
in config
.nodes_of(hostname
):
464 family
= "IPv6" if config
.is_ipv6_primary(nodename
) else "IPv4"
465 address_families
.append("%s: %s" % (nodename
, family
))
466 if address_family
is None:
467 address_family
= family
468 elif address_family
!= family
:
472 warning("Cluster '%s' has different primary address families: %s" %
473 (hostname
, ", ".join(address_families
)))
476 def ip_address_of(hostname
, family
=None):
478 return ip_lookup
.lookup_ip_address(hostname
, family
)
479 except Exception as e
:
480 if config
.is_cluster(hostname
):
483 _failed_ip_lookups
.append(hostname
)
484 if not _ignore_ip_lookup_failures
:
485 warning("Cannot lookup IP address of '%s' (%s). "
486 "The host will not be monitored correctly." % (hostname
, e
))
487 return fallback_ip_for(hostname
, family
)
490 def ignore_ip_lookup_failures():
491 global _ignore_ip_lookup_failures
492 _ignore_ip_lookup_failures
= True
495 def failed_ip_lookups():
496 return _failed_ip_lookups
499 def fallback_ip_for(hostname
, family
=None):
501 family
= 6 if config
.is_ipv6_primary(hostname
) else 4
509 def get_host_macros_from_attributes(hostname
, attrs
):
511 "$HOSTNAME$": hostname
,
512 "$HOSTADDRESS$": attrs
['address'],
513 "$HOSTALIAS$": attrs
['alias'],
517 for macro_name
, value
in attrs
.items():
518 if macro_name
[0] == '_':
519 macros
["$HOST" + macro_name
+ "$"] = value
520 # Be compatible to nagios making $_HOST<VARNAME>$ out of the config _<VARNAME> configs
521 macros
["$_HOST" + macro_name
[1:] + "$"] = value
526 def replace_macros(s
, macros
):
527 for key
, value
in macros
.items():
528 if isinstance(value
, (numbers
.Integral
, float)):
529 value
= str(value
) # e.g. in _EC_SL (service level)
531 # TODO: Clean this up
533 s
= s
.replace(key
, value
)
534 except: # Might have failed due to binary UTF-8 encoding in value
536 s
= s
.replace(key
, value
.decode("utf-8"))
538 # If this does not help, do not replace
539 if cmk
.utils
.debug
.enabled():