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.
32 from typing
import Any
, List
# pylint: disable=unused-import
34 import cmk
.utils
.paths
35 import cmk
.utils
.tty
as tty
36 import cmk
.utils
.password_store
37 from cmk
.utils
.exceptions
import MKGeneralException
39 import cmk_base
.console
as console
40 import cmk_base
.config
as config
41 import cmk_base
.ip_lookup
as ip_lookup
44 class MonitoringCore(object):
45 __metaclass__
= abc
.ABCMeta
48 def create_config(self
):
56 _ignore_ip_lookup_failures
= False
57 _failed_ip_lookups
= []
60 # .--Warnings------------------------------------------------------------.
62 # | \ \ / /_ _ _ __ _ __ (_)_ __ __ _ ___ |
63 # | \ \ /\ / / _` | '__| '_ \| | '_ \ / _` / __| |
64 # | \ V V / (_| | | | | | | | | | | (_| \__ \ |
65 # | \_/\_/ \__,_|_| |_| |_|_|_| |_|\__, |___/ |
67 # +----------------------------------------------------------------------+
68 # | Managing of warning messages occuring during configuration building |
69 # '----------------------------------------------------------------------'
71 g_configuration_warnings
= [] # type: List[Any]
74 def initialize_warnings():
75 global g_configuration_warnings
76 g_configuration_warnings
= []
80 g_configuration_warnings
.append(text
)
81 console
.warning("\n%s", text
, stream
=sys
.stdout
)
84 def get_configuration_warnings():
85 num_warnings
= len(g_configuration_warnings
)
88 warnings
= g_configuration_warnings
[:10] + \
89 [ "%d further warnings have been omitted" % (num_warnings
- 10) ]
91 warnings
= g_configuration_warnings
96 # TODO: Cleanup the hostcheck_commands_to_define, custom_commands_to_define thing
97 def host_check_command(hostname
,
100 hostcheck_commands_to_define
=None,
101 custom_commands_to_define
=None):
102 # Check dedicated host check command
103 values
= config
.host_extra_conf(hostname
, config
.host_check_commands
)
106 elif config
.is_no_ip_host(hostname
):
108 elif config
.monitoring_core
== "cmc":
113 if config
.monitoring_core
!= "cmc" and value
== "smart":
114 value
= "ping" # avoid problems when switching back to nagios core
116 if value
== "smart" and not is_clust
:
117 return "check-mk-host-smart"
119 elif value
in ["ping", "smart"]: # Cluster host
120 ping_args
= check_icmp_arguments_of(hostname
)
122 if is_clust
and ip
: # Do check cluster IP address if one is there
123 return "check-mk-host-ping!%s" % ping_args
124 elif ping_args
and is_clust
: # use check_icmp in cluster mode
125 return "check-mk-host-ping-cluster!%s" % ping_args
126 elif ping_args
: # use special arguments
127 return "check-mk-host-ping!%s" % ping_args
132 return "check-mk-host-ok"
134 elif value
== "agent" or value
[0] == "service":
135 service
= "Check_MK" if value
== "agent" else value
[1]
137 if config
.monitoring_core
== "cmc":
138 return "check-mk-host-service!" + service
140 command
= "check-mk-host-custom-%d" % (len(hostcheck_commands_to_define
) + 1)
141 hostcheck_commands_to_define
.append(
142 (command
, 'echo "$SERVICEOUTPUT:%s:%s$" && exit $SERVICESTATEID:%s:%s$' %
143 (hostname
, service
.replace('$HOSTNAME$', hostname
), hostname
,
144 service
.replace('$HOSTNAME$', hostname
))))
147 elif value
[0] == "tcp":
148 return "check-mk-host-tcp!" + str(value
[1])
150 elif value
[0] == "custom":
152 custom_commands_to_define
.add("check-mk-custom")
154 pass # not needed and not available with CMC
155 return "check-mk-custom!" + autodetect_plugin(value
[1])
157 raise MKGeneralException(
158 "Invalid value %r for host_check_command of host %s." % (value
, hostname
))
161 def autodetect_plugin(command_line
):
162 plugin_name
= command_line
.split()[0]
163 if command_line
[0] not in ['$', '/']:
165 for directory
in ["/local", ""]:
166 path
= cmk
.utils
.paths
.omd_root
+ directory
+ "/lib/nagios/plugins/"
167 if os
.path
.exists(path
+ plugin_name
):
168 command_line
= path
+ command_line
175 def icons_and_actions_of(what
, hostname
, svcdesc
=None, checkname
=None, params
=None):
177 return list(set(config
.host_extra_conf(hostname
, config
.host_icons_and_actions
)))
180 config
.service_extra_conf(hostname
, svcdesc
, config
.service_icons_and_actions
))
182 # Some WATO rules might register icons on their own
184 checkgroup
= config
.check_info
[checkname
]["group"]
185 if checkgroup
in ['ps', 'services'] and isinstance(params
, dict):
186 icon
= params
.get('icon')
193 def check_icmp_arguments_of(hostname
, add_defaults
=True, family
=None):
194 values
= config
.host_extra_conf(hostname
, config
.ping_levels
)
196 for value
in values
[::-1]: # make first rules have precedence
198 if not add_defaults
and not levels
:
202 family
= 6 if config
.is_ipv6_primary(hostname
) else 4
211 for key
, value
in levels
.items():
213 args
.append("-t %d" % value
)
214 elif key
== "packets":
215 args
.append("-n %d" % value
)
220 args
.append("-w %.2f,%.2f%%" % (rta
[0], loss
[0]))
221 args
.append("-c %.2f,%.2f%%" % (rta
[1], loss
[1]))
222 return " ".join(args
)
226 # .--Core Config---------------------------------------------------------.
228 # | / ___|___ _ __ ___ / ___|___ _ __ / _(_) __ _ |
229 # | | | / _ \| '__/ _ \ | | / _ \| '_ \| |_| |/ _` | |
230 # | | |__| (_) | | | __/ | |__| (_) | | | | _| | (_| | |
231 # | \____\___/|_| \___| \____\___/|_| |_|_| |_|\__, | |
233 # +----------------------------------------------------------------------+
234 # | Code for managing the core configuration creation. |
235 # '----------------------------------------------------------------------'
238 # TODO: Move to modes?
239 def do_create_config(core
, with_agents
):
240 console
.output("Generating configuration for core (type %s)..." % config
.monitoring_core
)
241 create_core_config(core
)
242 console
.output(tty
.ok
+ "\n")
246 import cmk_base
.cee
.agent_bakery
247 cmk_base
.cee
.agent_bakery
.bake_on_restart()
252 def create_core_config(core
):
253 initialize_warnings()
255 _verify_non_duplicate_hosts()
256 _verify_non_deprecated_checkgroups()
258 cmk
.utils
.password_store
.save(config
.stored_passwords
)
260 return get_configuration_warnings()
263 # Verify that the user has no deprecated check groups configured.
264 def _verify_non_deprecated_checkgroups():
265 groups
= config
.checks_by_checkgroup()
267 for checkgroup
in config
.checkgroup_parameters
:
268 if checkgroup
not in groups
:
270 "Found configured rules of deprecated check group \"%s\". These rules are not used "
271 "by any check. Maybe this check group has been renamed during an update, "
272 "in this case you will have to migrate your configuration to the new ruleset manually. "
273 "Please check out the release notes of the involved versions. "
274 "You may use the page \"Deprecated rules\" in WATO to view your rules and move them to "
275 "the new rulesets." % checkgroup
)
278 def _verify_non_duplicate_hosts():
279 duplicates
= config
.duplicate_hosts()
281 warning("The following host names have duplicates: %s. "
282 "This might lead to invalid/incomplete monitoring for these hosts." %
283 ", ".join(duplicates
))
286 def do_update(core
, with_precompile
):
288 do_create_config(core
, with_agents
=with_precompile
)
292 except Exception as e
:
293 console
.error("Configuration Error: %s\n" % e
)
294 if cmk
.utils
.debug
.enabled():
300 # .--Active Checks-------------------------------------------------------.
302 # | / \ ___| |_(_)_ _____ / ___| |__ ___ ___| | _____ |
303 # | / _ \ / __| __| \ \ / / _ \ | | | '_ \ / _ \/ __| |/ / __| |
304 # | / ___ \ (__| |_| |\ V / __/ | |___| | | | __/ (__| <\__ \ |
305 # | /_/ \_\___|\__|_| \_/ \___| \____|_| |_|\___|\___|_|\_\___/ |
307 # +----------------------------------------------------------------------+
308 # | Active check specific functions |
309 # '----------------------------------------------------------------------'
312 def active_check_arguments(hostname
, description
, args
):
313 if not isinstance(args
, (str, unicode, list)):
314 raise MKGeneralException(
315 "The check argument function needs to return either a list of arguments or a "
316 "string of the concatenated arguments (Host: %s, Service: %s)." % (hostname
,
319 return config
.prepare_check_command(args
, hostname
, description
)
323 # .--ServiceAttrs.-------------------------------------------------------.
325 # | / ___| ___ _ ____ _(_) ___ ___ / \ | |_| |_ _ __ ___ |
326 # | \___ \ / _ \ '__\ \ / / |/ __/ _ \ / _ \| __| __| '__/ __| |
327 # | ___) | __/ | \ V /| | (_| __// ___ \ |_| |_| | \__ \_ |
328 # | |____/ \___|_| \_/ |_|\___\___/_/ \_\__|\__|_| |___(_) |
330 # +----------------------------------------------------------------------+
331 # | Management of service attributes |
332 # '----------------------------------------------------------------------'
335 def get_service_attributes(hostname
, description
, config_cache
, checkname
=None, params
=None):
336 attrs
= _extra_service_attributes(hostname
, description
, config_cache
, checkname
, params
)
337 attrs
.update(_get_tag_attributes(config_cache
.tags_of_service(hostname
, description
)))
341 def _extra_service_attributes(hostname
, description
, config_cache
, checkname
, params
):
344 # Add service custom_variables. Name conflicts are prevented by the GUI, but just
345 # to be sure, add them first. The other definitions will override the custom attributes.
346 for varname
, value
in custom_service_attributes_of(hostname
, description
).iteritems():
347 attrs
["_%s" % varname
.upper()] = value
349 for key
, conflist
in config
.extra_service_conf
.items():
350 values
= config_cache
.service_extra_conf(hostname
, description
, conflist
)
354 attrs
[key
] = values
[0]
356 # Add explicit custom_variables
357 for varname
, value
in config
.get_explicit_service_custom_variables(hostname
,
358 description
).iteritems():
359 attrs
["_%s" % varname
.upper()] = value
361 # Add custom user icons and actions
362 actions
= icons_and_actions_of("service", hostname
, description
, checkname
, params
)
364 attrs
["_ACTIONS"] = ','.join(actions
)
368 # TODO: Hand over config_cache and use it instead of config.service_extra_conf
369 def custom_service_attributes_of(hostname
, service_description
):
371 itertools
.chain(*config
.service_extra_conf(hostname
, service_description
,
372 config
.custom_service_attributes
)))
376 # .--HostAttributes------------------------------------------------------.
377 # | _ _ _ _ _ _ _ _ _ |
378 # || | | | ___ ___| |_ / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___ |
379 # || |_| |/ _ \/ __| __| / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __| |
380 # || _ | (_) \__ \ |_ / ___ \ |_| |_| | | | |_) | |_| | || __/\__ \ |
381 # ||_| |_|\___/|___/\__/_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/ |
383 # +----------------------------------------------------------------------+
384 # | Managing of host attributes |
385 # '----------------------------------------------------------------------'
388 def get_host_attributes(hostname
, config_cache
):
389 attrs
= _extra_host_attributes(hostname
)
391 # Pre 1.6 legacy attribute. We have changed our whole code to use the
392 # livestatus column "tags" which is populated by all attributes starting with
393 # "__TAG_" instead. We may deprecate this is one day.
394 attrs
["_TAGS"] = " ".join(sorted(config_cache
.tag_list_of_host(hostname
)))
396 attrs
.update(_get_tag_attributes(config_cache
.tags_of_host(hostname
)))
398 if "alias" not in attrs
:
399 attrs
["alias"] = config
.alias_of(hostname
, hostname
)
401 # Now lookup configured IP addresses
402 if config
.is_ipv4_host(hostname
):
403 attrs
["_ADDRESS_4"] = ip_address_of(hostname
, 4)
404 if attrs
["_ADDRESS_4"] is None:
405 attrs
["_ADDRESS_4"] = ""
407 attrs
["_ADDRESS_4"] = ""
409 if config
.is_ipv6_host(hostname
):
410 attrs
["_ADDRESS_6"] = ip_address_of(hostname
, 6)
411 if attrs
["_ADDRESS_6"] is None:
412 attrs
["_ADDRESS_6"] = ""
414 attrs
["_ADDRESS_6"] = ""
416 ipv6_primary
= config
.is_ipv6_primary(hostname
)
418 attrs
["address"] = attrs
["_ADDRESS_6"]
419 attrs
["_ADDRESS_FAMILY"] = "6"
421 attrs
["address"] = attrs
["_ADDRESS_4"]
422 attrs
["_ADDRESS_FAMILY"] = "4"
424 add_ipv4addrs
, add_ipv6addrs
= config
.get_additional_ipaddresses_of(hostname
)
426 attrs
["_ADDRESSES_4"] = " ".join(add_ipv4addrs
)
427 for nr
, ipv4_address
in enumerate(add_ipv4addrs
):
428 key
= "_ADDRESS_4_%s" % (nr
+ 1)
429 attrs
[key
] = ipv4_address
432 attrs
["_ADDRESSES_6"] = " ".join(add_ipv6addrs
)
433 for nr
, ipv6_address
in enumerate(add_ipv6addrs
):
434 key
= "_ADDRESS_6_%s" % (nr
+ 1)
435 attrs
[key
] = ipv6_address
437 # Add the optional WATO folder path
438 path
= config
.host_paths
.get(hostname
)
440 attrs
["_FILENAME"] = path
442 # Add custom user icons and actions
443 actions
= icons_and_actions_of("host", hostname
)
445 attrs
["_ACTIONS"] = ",".join(actions
)
447 if cmk
.is_managed_edition():
448 attrs
["_CUSTOMER"] = config
.current_customer
# pylint: disable=no-member
453 def _get_tag_attributes(tags
):
454 return {u
"__TAG_%s" % k
: unicode(v
) for k
, v
in tags
.iteritems()}
457 def _extra_host_attributes(hostname
):
459 for key
, conflist
in config
.extra_host_conf
.items():
460 values
= config
.host_extra_conf(hostname
, conflist
)
465 if values
[0] is not None:
466 attrs
[key
] = values
[0]
470 def get_cluster_attributes(hostname
, nodes
):
471 sorted_nodes
= sorted(nodes
)
474 "_NODENAMES": " ".join(sorted_nodes
),
477 if config
.is_ipv4_host(hostname
):
478 for h
in sorted_nodes
:
479 addr
= ip_address_of(h
, 4)
481 node_ips_4
.append(addr
)
483 node_ips_4
.append(fallback_ip_for(hostname
, 4))
486 if config
.is_ipv6_host(hostname
):
487 for h
in sorted_nodes
:
488 addr
= ip_address_of(h
, 6)
490 node_ips_6
.append(addr
)
492 node_ips_6
.append(fallback_ip_for(hostname
, 6))
494 if config
.is_ipv6_primary(hostname
):
495 node_ips
= node_ips_6
497 node_ips
= node_ips_4
499 for suffix
, val
in [("", node_ips
), ("_4", node_ips_4
), ("_6", node_ips_6
)]:
500 attrs
["_NODEIPS%s" % suffix
] = " ".join(val
)
505 def get_cluster_nodes_for_config(hostname
):
506 _verify_cluster_address_family(hostname
)
508 nodes
= config
.nodes_of(hostname
)[:]
510 if node
not in config
.all_active_realhosts():
511 warning("Node '%s' of cluster '%s' is not a monitored host in this site." % (node
,
517 def _verify_cluster_address_family(hostname
):
518 cluster_host_family
= "IPv6" if config
.is_ipv6_primary(hostname
) else "IPv4"
521 "%s: %s" % (hostname
, cluster_host_family
),
524 address_family
= cluster_host_family
526 for nodename
in config
.nodes_of(hostname
):
527 family
= "IPv6" if config
.is_ipv6_primary(nodename
) else "IPv4"
528 address_families
.append("%s: %s" % (nodename
, family
))
529 if address_family
is None:
530 address_family
= family
531 elif address_family
!= family
:
535 warning("Cluster '%s' has different primary address families: %s" %
536 (hostname
, ", ".join(address_families
)))
539 def ip_address_of(hostname
, family
=None):
541 return ip_lookup
.lookup_ip_address(hostname
, family
)
542 except Exception as e
:
543 if config
.is_cluster(hostname
):
546 _failed_ip_lookups
.append(hostname
)
547 if not _ignore_ip_lookup_failures
:
548 warning("Cannot lookup IP address of '%s' (%s). "
549 "The host will not be monitored correctly." % (hostname
, e
))
550 return fallback_ip_for(hostname
, family
)
553 def ignore_ip_lookup_failures():
554 global _ignore_ip_lookup_failures
555 _ignore_ip_lookup_failures
= True
558 def failed_ip_lookups():
559 return _failed_ip_lookups
562 def fallback_ip_for(hostname
, family
=None):
564 family
= 6 if config
.is_ipv6_primary(hostname
) else 4
572 def get_host_macros_from_attributes(hostname
, attrs
):
574 "$HOSTNAME$": hostname
,
575 "$HOSTADDRESS$": attrs
['address'],
576 "$HOSTALIAS$": attrs
['alias'],
580 for macro_name
, value
in attrs
.items():
581 if macro_name
[0] == '_':
582 macros
["$HOST" + macro_name
+ "$"] = value
583 # Be compatible to nagios making $_HOST<VARNAME>$ out of the config _<VARNAME> configs
584 macros
["$_HOST" + macro_name
[1:] + "$"] = value
589 def replace_macros(s
, macros
):
590 for key
, value
in macros
.items():
591 if isinstance(value
, (numbers
.Integral
, float)):
592 value
= str(value
) # e.g. in _EC_SL (service level)
594 # TODO: Clean this up
596 s
= s
.replace(key
, value
)
597 except: # Might have failed due to binary UTF-8 encoding in value
599 s
= s
.replace(key
, value
.decode("utf-8"))
601 # If this does not help, do not replace
602 if cmk
.utils
.debug
.enabled():