def get_cluster_attributes(config_cache: config.ConfigCache, host_config: config.HostConfig, nodes: List[str]) -> Dict: sorted_nodes = sorted(nodes) attrs = { "_NODENAMES": " ".join(sorted_nodes), } node_ips_4 = [] if host_config.is_ipv4_host: for h in sorted_nodes: node_config = config_cache.get_host_config(h) addr = ip_address_of(node_config, 4) if addr is not None: node_ips_4.append(addr) else: node_ips_4.append(fallback_ip_for(node_config, 4)) node_ips_6 = [] if host_config.is_ipv6_host: for h in sorted_nodes: node_config = config_cache.get_host_config(h) addr = ip_address_of(node_config, 6) if addr is not None: node_ips_6.append(addr) else: node_ips_6.append(fallback_ip_for(node_config, 6)) node_ips = node_ips_6 if host_config.is_ipv6_primary else node_ips_4 for suffix, val in [("", node_ips), ("_4", node_ips_4), ("_6", node_ips_6)]: attrs["_NODEIPS%s" % suffix] = " ".join(val) return attrs
def get_host_attributes(hostname: HostName, config_cache: ConfigCache) -> ObjectAttributes: host_config = config_cache.get_host_config(hostname) attrs = host_config.extra_host_attributes # Pre 1.6 legacy attribute. We have changed our whole code to use the # livestatus column "tags" which is populated by all attributes starting with # "__TAG_" instead. We may deprecate this is one day. attrs["_TAGS"] = " ".join(sorted(config_cache.get_host_config(hostname).tags)) attrs.update(_get_tag_attributes(host_config.tag_groups, "TAG")) attrs.update(_get_tag_attributes(host_config.labels, "LABEL")) attrs.update(_get_tag_attributes(host_config.label_sources, "LABELSOURCE")) if "alias" not in attrs: attrs["alias"] = host_config.alias # Now lookup configured IP addresses v4address: Optional[str] = None if host_config.is_ipv4_host: v4address = ip_address_of(host_config, socket.AF_INET) if v4address is None: v4address = "" attrs["_ADDRESS_4"] = v4address v6address: Optional[str] = None if host_config.is_ipv6_host: v6address = ip_address_of(host_config, socket.AF_INET6) if v6address is None: v6address = "" attrs["_ADDRESS_6"] = v6address ipv6_primary = host_config.is_ipv6_primary if ipv6_primary: attrs["address"] = attrs["_ADDRESS_6"] attrs["_ADDRESS_FAMILY"] = "6" else: attrs["address"] = attrs["_ADDRESS_4"] attrs["_ADDRESS_FAMILY"] = "4" add_ipv4addrs, add_ipv6addrs = host_config.additional_ipaddresses _set_addresses(attrs, add_ipv4addrs, "4") _set_addresses(attrs, add_ipv6addrs, "6") # Add the optional WATO folder path path = config.host_paths.get(hostname) if path: attrs["_FILENAME"] = path # Add custom user icons and actions actions = host_config.icons_and_actions if actions: attrs["_ACTIONS"] = ",".join(actions) if cmk_version.is_managed_edition(): attrs["_CUSTOMER"] = config.current_customer # type: ignore[attr-defined] return attrs
def _get_clustered_services( config_cache: config.ConfigCache, host_config: config.HostConfig, skip_autochecks: bool, ) -> Iterable[Service]: for node in host_config.nodes or []: # TODO: Cleanup this to work exactly like the logic above (for a single host) # (mo): in particular: this means that autochecks will win over static checks. # for a single host the static ones win. node_config = config_cache.get_host_config(node) node_checks = list(_get_static_check_entries(node_config)) if not (skip_autochecks or host_config.is_ping_host): node_checks += config_cache.get_autochecks_of(node) for service in node_checks: services_host = config_cache.host_of_clustered_service( node, service.description) if services_host != host_config.hostname: continue cluster_params = config.compute_check_parameters( host_config.hostname, service.check_plugin_name, service.item, service.parameters, ) yield Service( service.check_plugin_name, service.item, service.description, cluster_params, service.service_labels, )
def _get_clustered_services( self, config_cache: config.ConfigCache, host_config: config.HostConfig, hostname: str, skip_autochecks: bool, ) -> Iterable[Service]: for node in host_config.nodes or []: # TODO: Cleanup this to work exactly like the logic above (for a single host) node_config = config_cache.get_host_config(node) node_checks = list(self._get_static_check_entries(node_config)) if not skip_autochecks: node_checks += config_cache.get_autochecks_of(node) for service in node_checks: if config_cache.host_of_clustered_service( node, service.description) != hostname: continue cluster_params = config.compute_check_parameters( hostname, service.check_plugin_name, service.item, service.parameters, ) yield Service( service.check_plugin_name, service.item, service.description, cluster_params, service.service_labels, )
def _verify_cluster_address_family( nodes: List[HostName], config_cache: config.ConfigCache, host_config: config.HostConfig, ) -> None: cluster_host_family = "IPv6" if host_config.is_ipv6_primary else "IPv4" address_families = [ "%s: %s" % (host_config.hostname, cluster_host_family), ] address_family = cluster_host_family mixed = False for nodename in nodes: node_config = config_cache.get_host_config(nodename) family = "IPv6" if node_config.is_ipv6_primary else "IPv4" address_families.append("%s: %s" % (nodename, family)) if address_family is None: address_family = family elif address_family != family: mixed = True if mixed: warning("Cluster '%s' has different primary address families: %s" % (host_config.hostname, ", ".join(address_families)))
def _preprocess_hostnames( arg_host_names: Set[HostName], config_cache: config.ConfigCache, only_host_labels: bool, ) -> Set[HostName]: """Default to all hosts and expand cluster names to their nodes""" if not arg_host_names: console.verbose("Discovering %shost labels on all hosts\n" % ("services and " if not only_host_labels else "")) arg_host_names = config_cache.all_active_realhosts() else: console.verbose("Discovering %shost labels on: %s\n" % ("services and " if not only_host_labels else "", ", ".join(sorted(arg_host_names)))) host_names: Set[HostName] = set() # For clusters add their nodes to the list. Clusters itself # cannot be discovered but the user is allowed to specify # them and we do discovery on the nodes instead. for host_name, host_config in [(hn, config_cache.get_host_config(hn)) for hn in arg_host_names]: if not host_config.is_cluster: host_names.add(host_name) continue if host_config.nodes is None: raise MKGeneralException("Invalid cluster configuration") host_names.update(host_config.nodes) return host_names
def _get_dns_cache_lookup_hosts(config_cache: config.ConfigCache) -> List[IPLookupCacheId]: hosts = [] for hostname in config_cache.all_active_hosts(): host_config = config_cache.get_host_config(hostname) if host_config.is_ipv4_host: hosts.append((hostname, 4)) if host_config.is_ipv6_host: hosts.append((hostname, 6)) return hosts
def get_cluster_attributes( config_cache: config.ConfigCache, host_config: config.HostConfig, nodes: Sequence[HostName], ) -> Dict: sorted_nodes = sorted(nodes) attrs = { "_NODENAMES": " ".join(sorted_nodes), } node_ips_4 = [] if host_config.is_ipv4_host: family = socket.AF_INET for h in sorted_nodes: node_config = config_cache.get_host_config(h) addr = ip_address_of(node_config, family) if addr is not None: node_ips_4.append(addr) else: node_ips_4.append(ip_lookup.fallback_ip_for(family)) node_ips_6 = [] if host_config.is_ipv6_host: family = socket.AF_INET6 for h in sorted_nodes: node_config = config_cache.get_host_config(h) addr = ip_address_of(node_config, family) if addr is not None: node_ips_6.append(addr) else: node_ips_6.append(ip_lookup.fallback_ip_for(family)) node_ips = node_ips_6 if host_config.is_ipv6_primary else node_ips_4 for suffix, val in [("", node_ips), ("_4", node_ips_4), ("_6", node_ips_6)]: attrs["_NODEIPS%s" % suffix] = " ".join(val) return attrs
def make_cluster_sources( config_cache: config.ConfigCache, host_config: HostConfig, ) -> Sequence[Source]: """Abstract clusters/nodes/hosts""" assert host_config.nodes is not None return [ source for host_name in host_config.nodes for source in make_sources( HostConfig.make_host_config(host_name), config.lookup_ip_address(config_cache.get_host_config(host_name)), force_snmp_cache_refresh=False, ) ]
def _ip_to_hostname(config_cache: config.ConfigCache, ip: Optional[HostAddress]) -> Optional[HostName]: if "ip_to_hostname" not in _config_cache: cache = _config_cache.get("ip_to_hostname") for host in config_cache.all_active_realhosts(): host_config = config_cache.get_host_config(host) try: cache[config.lookup_ip_address(host_config, family=socket.AF_INET)] = host except Exception: pass else: cache = _config_cache.get("ip_to_hostname") return cache.get(ip)
def _ip_to_hostname(config_cache: config.ConfigCache, ip: Optional[HostAddress]) -> Optional[HostName]: if not _config_cache.exists("ip_to_hostname"): cache = _config_cache.get_dict("ip_to_hostname") for host in config_cache.all_active_realhosts(): host_config = config_cache.get_host_config(host) try: cache[ip_lookup.lookup_ipv4_address(host_config)] = host except Exception: pass else: cache = _config_cache.get_dict("ip_to_hostname") return cache.get(ip)
def _verify_cluster_datasource(nodes: List[str], config_cache: config.ConfigCache, host_config: config.HostConfig) -> None: cluster_tg = host_config.tag_groups cluster_agent_ds = cluster_tg.get("agent") cluster_snmp_ds = cluster_tg.get("snmp_ds") for nodename in nodes: node_tg = config_cache.get_host_config(nodename).tag_groups node_agent_ds = node_tg.get("agent") node_snmp_ds = node_tg.get("snmp_ds") warn_text = "Cluster '%s' has different datasources as its node" % host_config.hostname if node_agent_ds != cluster_agent_ds: warning("%s '%s': %s vs. %s" % (warn_text, nodename, cluster_agent_ds, node_agent_ds)) if node_snmp_ds != cluster_snmp_ds: warning("%s '%s': %s vs. %s" % (warn_text, nodename, cluster_snmp_ds, node_snmp_ds))
def _make_piggyback_nodes( config_cache: config.ConfigCache, host_config: HostConfig) -> Iterable[Tuple[HostName, Optional[HostAddress], DataSources]]: """Abstract clusters/nodes/hosts""" assert host_config.nodes is not None nodes = [] for hostname in host_config.nodes: node_config = config_cache.get_host_config(hostname) ipaddress = ip_lookup.lookup_ip_address(node_config) sources = make_sources( HostConfig.make_host_config(hostname), ipaddress, ) nodes.append((hostname, ipaddress, sources)) return nodes
def _get_clustered_services( config_cache: config.ConfigCache, host_config: config.HostConfig, skip_autochecks: bool, ) -> Iterable[Service]: for node in host_config.nodes or []: # TODO: Cleanup this to work exactly like the logic above (for a single host) # (mo): in particular: this means that autochecks will win over static checks. # for a single host the static ones win. node_config = config_cache.get_host_config(node) node_checks = list(_get_static_check_entries(config_cache, node_config)) if not (skip_autochecks or host_config.is_ping_host): node_checks += config_cache.get_autochecks_of(node) yield from (service for service in node_checks if config_cache.host_of_clustered_service( node, service.description) == host_config.hostname)
def _make_cluster_nodes( config_cache: config.ConfigCache, host_config: HostConfig, ) -> Sequence[Tuple[HostName, Optional[HostAddress], Sequence[Source]]]: """Abstract clusters/nodes/hosts""" assert host_config.nodes is not None nodes = [] for hostname in host_config.nodes: node_config = config_cache.get_host_config(hostname) ipaddress = config.lookup_ip_address(node_config) sources = make_sources( HostConfig.make_host_config(hostname), ipaddress, force_snmp_cache_refresh=False, ) nodes.append((hostname, ipaddress, sources)) return nodes
def check_icmp_arguments_of( config_cache: ConfigCache, hostname: HostName, add_defaults: bool = True, family: Optional[int] = None, ) -> str: host_config = config_cache.get_host_config(hostname) levels = host_config.ping_levels if not add_defaults and not levels: return "" if family is None: family = 6 if host_config.is_ipv6_primary else 4 args = [] if family == 6: args.append("-6") rta = 200.0, 500.0 loss = 80.0, 100.0 for key, value in levels.items(): if key == "timeout": if not isinstance(value, int): raise TypeError() args.append("-t %d" % value) elif key == "packets": if not isinstance(value, int): raise TypeError() args.append("-n %d" % value) elif key == "rta": if not isinstance(value, tuple): raise TypeError() rta = value elif key == "loss": if not isinstance(value, tuple): raise TypeError() loss = value args.append("-w %.2f,%.2f%%" % (rta[0], loss[0])) args.append("-c %.2f,%.2f%%" % (rta[1], loss[1])) return " ".join(args)
def _make_piggyback_nodes( mode: Mode, config_cache: config.ConfigCache, host_config: HostConfig, ) -> Sequence[Tuple[HostName, Optional[HostAddress], Sequence[Source]]]: """Abstract clusters/nodes/hosts""" assert host_config.nodes is not None nodes = [] for hostname in host_config.nodes: node_config = config_cache.get_host_config(hostname) ipaddress = ip_lookup.lookup_ip_address( node_config, family=node_config.default_address_family, ) sources = make_sources( HostConfig.make_host_config(hostname), ipaddress, mode=mode, ) nodes.append((hostname, ipaddress, sources)) return nodes
def scan_parents_of(config_cache: config.ConfigCache, hosts: List[HostName], silent: bool = False, settings: Optional[Dict[str, int]] = None) -> Gateways: if settings is None: settings = {} if config.monitoring_host: host_config = config_cache.get_host_config(config.monitoring_host) nagios_ip = ip_lookup.lookup_ipv4_address(host_config) else: nagios_ip = None os.putenv("LANG", "") os.putenv("LC_ALL", "") # Start processes in parallel procs: List[Tuple[HostName, Optional[HostAddress], Union[str, subprocess.Popen]]] = [] for host in hosts: console.verbose("%s " % host) host_config = config_cache.get_host_config(host) try: ip = ip_lookup.lookup_ipv4_address(host_config) if ip is None: raise RuntimeError() command = [ "traceroute", "-w", "%d" % settings.get("timeout", 8), "-q", "%d" % settings.get("probes", 2), "-m", "%d" % settings.get("max_ttl", 10), "-n", ip ] console.vverbose("Running '%s'\n" % subprocess.list2cmdline(command)) procs.append((host, ip, subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, encoding="utf-8"))) except Exception as e: if cmk.utils.debug.enabled(): raise procs.append((host, None, "ERROR: %s" % e)) # Output marks with status of each single scan def dot(color: str, dot: str = 'o') -> None: if not silent: out.output(tty.bold + color + dot + tty.normal) # Now all run and we begin to read the answers. For each host # we add a triple to gateways: the gateway, a scan state and a diagnostic output gateways: Gateways = [] for host, ip, proc_or_error in procs: if isinstance(proc_or_error, str): lines = [proc_or_error] exitstatus = 1 else: exitstatus = proc_or_error.wait() if proc_or_error.stdout is None: raise RuntimeError() lines = [l.strip() for l in proc_or_error.stdout.readlines()] if exitstatus: dot(tty.red, '*') gateways.append( (None, "failed", 0, "Traceroute failed with exit code %d" % (exitstatus & 255))) continue if len(lines) == 1 and lines[0].startswith("ERROR:"): message = lines[0][6:].strip() console.verbose("%s: %s\n", host, message, stream=sys.stderr) dot(tty.red, "D") gateways.append((None, "dnserror", 0, message)) continue if len(lines) == 0: if cmk.utils.debug.enabled(): raise MKGeneralException( "Cannot execute %s. Is traceroute installed? Are you root?" % command) dot(tty.red, '!') continue if len(lines) < 2: if not silent: console.error("%s: %s\n" % (host, ' '.join(lines))) gateways.append((None, "garbled", 0, "The output of traceroute seem truncated:\n%s" % ("".join(lines)))) dot(tty.blue) continue # Parse output of traceroute: # traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 40 byte packets # 1 * * * # 2 10.0.0.254 0.417 ms 0.459 ms 0.670 ms # 3 172.16.0.254 0.967 ms 1.031 ms 1.544 ms # 4 217.0.116.201 23.118 ms 25.153 ms 26.959 ms # 5 217.0.76.134 32.103 ms 32.491 ms 32.337 ms # 6 217.239.41.106 32.856 ms 35.279 ms 36.170 ms # 7 74.125.50.149 45.068 ms 44.991 ms * # 8 * 66.249.94.86 41.052 ms 66.249.94.88 40.795 ms # 9 209.85.248.59 43.739 ms 41.106 ms 216.239.46.240 43.208 ms # 10 216.239.48.53 45.608 ms 47.121 ms 64.233.174.29 43.126 ms # 11 209.85.255.245 49.265 ms 40.470 ms 39.870 ms # 12 8.8.8.8 28.339 ms 28.566 ms 28.791 ms routes: List[Optional[str]] = [] for line in lines[1:]: parts = line.split() route = parts[1] if route.count('.') == 3: routes.append(route) elif route == '*': routes.append(None) # No answer from this router else: if not silent: console.error( "%s: invalid output line from traceroute: '%s'\n" % (host, line)) if len(routes) == 0: error = "incomplete output from traceroute. No routes found." console.error("%s: %s\n" % (host, error)) gateways.append((None, "garbled", 0, error)) dot(tty.red) continue # Only one entry -> host is directly reachable and gets nagios as parent - # if nagios is not the parent itself. Problem here: How can we determine # if the host in question is the monitoring host? The user must configure # this in monitoring_host. if len(routes) == 1: if ip == nagios_ip: gateways.append( (None, "root", 0, "")) # We are the root-monitoring host dot(tty.white, 'N') elif config.monitoring_host: gateways.append(((config.monitoring_host, nagios_ip, None), "direct", 0, "")) dot(tty.cyan, 'L') else: gateways.append((None, "direct", 0, "")) continue # Try far most route which is not identical with host itself ping_probes = settings.get("ping_probes", 5) skipped_gateways = 0 this_route: Optional[HostAddress] = None for r in routes[::-1]: if not r or (r == ip): continue # Do (optional) PING check in order to determine if that # gateway can be monitored via the standard host check if ping_probes: if not gateway_reachable_via_ping(r, ping_probes): console.verbose("(not using %s, not reachable)\n", r, stream=sys.stderr) skipped_gateways += 1 continue this_route = r break if not this_route: error = "No usable routing information" if not silent: console.error("%s: %s\n" % (host, error)) gateways.append((None, "notfound", 0, error)) dot(tty.blue) continue # TTLs already have been filtered out) gateway_ip = this_route gateway = _ip_to_hostname(config_cache, this_route) if gateway: console.verbose("%s(%s) ", gateway, gateway_ip) else: console.verbose("%s ", gateway_ip) # Try to find DNS name of host via reverse DNS lookup dns_name = _ip_to_dnsname(gateway_ip) gateways.append( ((gateway, gateway_ip, dns_name), "gateway", skipped_gateways, "")) dot(tty.green, 'G') return gateways