diff --git a/README.md b/README.md index 43fe65b8b9cc737faf5a25dcdd097b332ac91ec0..c429494ea17b15672aee0766fcdfae0e29aab3b7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/unbound-1.2.6-20240527.mkp "unbound-1.2.6-20240527.mkp" +[PACKAGE]: ../../raw/master/mkp/unbound-1.3.3-20240927.mkp "unbound-1.3.3-20240927.mkp" # unbound This agent plugin cheks the state of the unbound dns daemon. For more information about unbound see: https://nlnetlabs.nl/projects/unbound/about/ diff --git a/mkp/unbound-1.3.3-20240927.mkp b/mkp/unbound-1.3.3-20240927.mkp new file mode 100644 index 0000000000000000000000000000000000000000..653151d6d9584aa7a2777cf0180da4cbf9dec470 Binary files /dev/null and b/mkp/unbound-1.3.3-20240927.mkp differ diff --git a/source/agent_based/unbound.py b/source/agent_based/unbound.py index f37ffe99ea98d53aaaa79fbc2eaab1b298899faa..879ebd399304409f338670561a7f5b52ae74775b 100644 --- a/source/agent_based/unbound.py +++ b/source/agent_based/unbound.py @@ -25,37 +25,11 @@ # added output in case get_rate is initialising (unknown state in discovery) # removed default levels for unbound_answers # added params for unwanted replies +# 2024-07-24: split unbound.py in separate files per check - -from time import time as now_time -from typing import ( - Any, - Mapping, -) - -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, - StringTable, -) - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - GetRateError, - Result, - Service, - State, - check_levels, - get_rate, - get_value_store, - register, - render, -) - -UnboundSection = Mapping[str, int | float] - - -def render_qps(x: float) -> str: - return f'{x:.2f}/s' +from cmk.base.plugins.agent_based.agent_based_api.v1 import register +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import StringTable +from cmk.base.plugins.agent_based.utils.unbound import UnboundSection def parse_unbound(string_table: StringTable) -> UnboundSection: @@ -65,6 +39,7 @@ def parse_unbound(string_table: StringTable) -> UnboundSection: section[key] = int(value) except ValueError: section[key] = float(value) + return section @@ -72,175 +47,3 @@ register.agent_section( name='unbound', parse_function=parse_unbound, ) - - -def discover_unbound_cache(section: UnboundSection) -> DiscoveryResult: - if 'total.num.cachehits' in section and 'total.num.cachemiss' in section: - yield Service() - - -def check_unbound_cache( - params: Mapping[str, Any], - section: UnboundSection, -) -> CheckResult: - cumulative_cache_hits = section.get('total.num.cachehits') - cumulative_cache_miss = section.get('total.num.cachemiss') - now = now_time() - - if None in (cumulative_cache_hits, cumulative_cache_miss, now): - return - - cache_hits = get_rate( - get_value_store(), - 'unbound_cache_hits', - now, - cumulative_cache_hits, - raise_overflow=True, - ) - cache_miss = get_rate( - get_value_store(), - 'unbound_cache_miss', - now, - cumulative_cache_miss, - raise_overflow=True, - ) - total = cache_hits + cache_miss - hit_perc = (cache_hits / float(total)) * 100.0 if total != 0 else 100.0 - - yield from check_levels( - value=cache_miss, - metric_name='cache_misses_rate', - levels_upper=params.get('cache_misses'), - render_func=render_qps, - label='Cache Misses', - notice_only=True, - ) - - yield from check_levels( - value=cache_hits, - metric_name='cache_hit_rate', - render_func=render_qps, - label='Cache Hits', - notice_only=True, - ) - - yield from check_levels( - value=hit_perc, - metric_name='cache_hit_ratio', - levels_lower=params.get('cache_hits'), - render_func=render.percent, - label='Cache Hit Ratio', - ) - - -register.check_plugin( - name='unbound_cache', - service_name='Unbound Cache', - sections=['unbound'], - discovery_function=discover_unbound_cache, - check_function=check_unbound_cache, - check_default_parameters={}, - check_ruleset_name='unbound_cache', -) - - -def discover_unbound_answers(section: UnboundSection) -> DiscoveryResult: - if 'num.answer.rcode.SERVFAIL' in section: - yield Service() - - -def check_unbound_answers(params: Mapping, section: UnboundSection) -> CheckResult: - key_prefix = 'num.answer.rcode.' - now = now_time() - - total = sum( - value for key, value in section.items() - if key.startswith(key_prefix) - ) - - init_counters = False - for key, value in section.items(): - if not key.startswith(key_prefix): - continue - answer = key[len(key_prefix):] - - try: - rate = get_rate( - get_value_store(), - f'unbound_answers_{answer}', - now, - value, - raise_overflow=True, - ) - except GetRateError as e: - if not init_counters: - yield Result(state=State.OK, summary=str(e)) - init_counters = True - else: - levels_upper = params.get(f'levels_upper_{answer}') - if levels_upper is not None and len(levels_upper) == 3: - # levels on the ratio of answers - levels_upper = ( - levels_upper[0] * total, - levels_upper[1] * total, - ) - yield from check_levels( - value=rate, - levels_upper=levels_upper, - metric_name=f'unbound_answers_{answer}', - render_func=render_qps, - label=answer, - notice_only=f'levels_upper_{answer}' not in params, - ) - - -register.check_plugin( - name='unbound_answers', - service_name='Unbound Answers', - sections=['unbound'], - discovery_function=discover_unbound_answers, - check_function=check_unbound_answers, - check_default_parameters={ - # 'levels_upper_NOERROR': (101, 101), - # 'levels_upper_SERVFAIL': (10, 100), - # 'levels_upper_REFUSED': (10, 100), - }, - check_ruleset_name='unbound_answers', -) - - -def discover_unbound_unwanted_replies(section: UnboundSection) -> DiscoveryResult: - if 'unwanted.replies' in section: - yield Service() - - -def check_unbound_unwanted_replies(params, section: UnboundSection) -> CheckResult: - if 'unwanted.replies' not in section: - return - - rate = get_rate( - get_value_store(), - 'unbound_unwanted_replies', - now_time(), - section['unwanted.replies'], - raise_overflow=True, - ) - - yield from check_levels( - value=rate, - levels_upper=params.get('unwanted_replies'), - metric_name='unbound_unwanted_replies', - render_func=render_qps, - label='Unwanted Replies', - ) - - -register.check_plugin( - name='unbound_unwanted_replies', - service_name='Unbound Unwanted Replies', - sections=['unbound'], - discovery_function=discover_unbound_unwanted_replies, - check_function=check_unbound_unwanted_replies, - check_default_parameters={}, - check_ruleset_name='unbound_replies', -) diff --git a/source/agent_based/unbound_answers.py b/source/agent_based/unbound_answers.py new file mode 100644 index 0000000000000000000000000000000000000000..09b5366c02840db6e51a9a428a11adbcc87725ab --- /dev/null +++ b/source/agent_based/unbound_answers.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# https://nlnetlabs.nl/projects/unbound/about/ + +# changes by thl-cmk[at]outlook[dot]com +# 2024-04-21: removed Union -> no need "int | float" should do +# added levels_upper_NOERROR to default parameters -> show up in info line +# 2024-05-22: changed time for get_rate from section to system time +# added output in case get_rate is initialising (unknown state in discovery) +# removed default levels for unbound_answers +# added params for unwanted replies + + +from time import time as now_time +from typing import ( + Mapping, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + GetRateError, + Result, + Service, + State, + check_levels, + get_rate, + get_value_store, + register, +) + +from cmk.base.plugins.agent_based.utils.unbound import ( + UnboundSection, + render_qps, +) + + +def discover_unbound_answers(section: UnboundSection) -> DiscoveryResult: + if 'num.answer.rcode.SERVFAIL' in section: + yield Service() + + +def check_unbound_answers(params: Mapping, section: UnboundSection) -> CheckResult: + key_prefix = 'num.answer.rcode.' + now = now_time() + + total = sum( + value for key, value in section.items() + if key.startswith(key_prefix) + ) + + init_counters = False + for key, value in section.items(): + if not key.startswith(key_prefix): + continue + answer = key[len(key_prefix):] + + try: + rate = get_rate( + get_value_store(), + f'unbound_answers_{answer}', + now, + value, + raise_overflow=True, + ) + except GetRateError as e: + if not init_counters: + yield Result(state=State.OK, summary=str(e)) + init_counters = True + else: + levels_upper = params.get(f'levels_upper_{answer}') + if levels_upper is not None and len(levels_upper) == 3: + # levels on the ratio of answers + levels_upper = ( + levels_upper[0] * total, + levels_upper[1] * total, + ) + yield from check_levels( + value=rate, + levels_upper=levels_upper, + metric_name=f'unbound_answers_{answer}', + render_func=render_qps, + label=answer, + notice_only=f'levels_upper_{answer}' not in params, + ) + + +register.check_plugin( + name='unbound_answers', + service_name='Unbound Answers', + sections=['unbound'], + discovery_function=discover_unbound_answers, + check_function=check_unbound_answers, + check_default_parameters={ + # 'levels_upper_NOERROR': (101, 101), + # 'levels_upper_SERVFAIL': (10, 100), + # 'levels_upper_REFUSED': (10, 100), + }, + check_ruleset_name='unbound_answers', +) diff --git a/source/agent_based/unbound_cache.py b/source/agent_based/unbound_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..995cee3018c55324ba41271c1d87ef6304c5606a --- /dev/null +++ b/source/agent_based/unbound_cache.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# https://nlnetlabs.nl/projects/unbound/about/ + +# changes by thl-cmk[at]outlook[dot]com +# 2024-04-21: removed Union -> no need "int | float" should do +# added levels_upper_NOERROR to default parameters -> show up in info line +# 2024-05-22: changed time for get_rate from section to system time +# added output in case get_rate is initialising (unknown state in discovery) +# removed default levels for unbound_answers +# added params for unwanted replies + + +from time import time as now_time +from typing import ( + Any, + Mapping, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + Service, + check_levels, + get_rate, + get_value_store, + register, + render, +) + +from cmk.base.plugins.agent_based.utils.unbound import ( + UnboundSection, + render_qps, +) + + +def discover_unbound_cache(section: UnboundSection) -> DiscoveryResult: + if 'total.num.cachehits' in section and 'total.num.cachemiss' in section: + yield Service() + + +def check_unbound_cache( + params: Mapping[str, Any], + section: UnboundSection, +) -> CheckResult: + cumulative_cache_hits = section.get('total.num.cachehits') + cumulative_cache_miss = section.get('total.num.cachemiss') + now = now_time() + + if None in (cumulative_cache_hits, cumulative_cache_miss, now): + return + + cache_hits = get_rate( + get_value_store(), + 'unbound_cache_hits', + now, + cumulative_cache_hits, + raise_overflow=True, + ) + cache_miss = get_rate( + get_value_store(), + 'unbound_cache_miss', + now, + cumulative_cache_miss, + raise_overflow=True, + ) + total = cache_hits + cache_miss + hit_perc = (cache_hits / float(total)) * 100.0 if total != 0 else 100.0 + + yield from check_levels( + value=cache_miss, + metric_name='cache_misses_rate', + levels_upper=params.get('cache_misses'), + render_func=render_qps, + label='Cache Misses', + notice_only=True, + ) + + yield from check_levels( + value=cache_hits, + metric_name='cache_hit_rate', + render_func=render_qps, + label='Cache Hits', + notice_only=True, + ) + + yield from check_levels( + value=hit_perc, + metric_name='cache_hit_ratio', + levels_lower=params.get('cache_hits'), + render_func=render.percent, + label='Cache Hit Ratio', + ) + + +register.check_plugin( + name='unbound_cache', + service_name='Unbound Cache', + sections=['unbound'], + discovery_function=discover_unbound_cache, + check_function=check_unbound_cache, + check_default_parameters={}, + check_ruleset_name='unbound_cache', +) + diff --git a/source/agent_based/unbound_status.py b/source/agent_based/unbound_status.py index 3cc99411c5bb5998be6010e5d18c45ee2326067b..08d89ef018547d1311bfcd673af01c6d639d934b 100644 --- a/source/agent_based/unbound_status.py +++ b/source/agent_based/unbound_status.py @@ -8,13 +8,12 @@ # Date : 2024-05-21 # File : unbound_status.py -from dataclasses import dataclass +# 2024-09-10: fixed crash if unbound is not running -from typing import ( - Any, - Mapping, - Sequence -) +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from re import match as re_match +from typing import Any from cmk.utils import debug from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( @@ -24,28 +23,38 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + Result, Service, + State, check_levels, register, render, - Result, - State, ) @dataclass(frozen=True) class UnboundStatus: - version: str - verbosity: int - threads: int - modules: Sequence[str] - uptime: int - options: Sequence[str] status: str - pid: int + modules: Sequence[str] | None = None + options: Sequence[str] | None = None + pid: int | None = None + threads: int | None = None + uptime: int | None = None + verbosity: int | None = None + version: str | None = None @classmethod def parse(cls, string_table: StringTable): + # set defaults + modules = None + options = None + pid = None + status = None + threads = None + uptime = None + verbosity = None + version = None + for line in string_table: key, value = line[0].split(' ', 1) key = key.rstrip(':') @@ -67,25 +76,30 @@ class UnboundStatus: options = value.split(' ') case 'unbound': # (pid 520) is running... - pid = int(value.split(') ')[0].replace('(pid ', '')) - status = value.split(') ', -1)[-1].strip('.') + # is stopped + re_pid = r'\(pid (\d+)\).*' + re_status = r'.*(is \w+)' + if pid := re_match(re_pid, value): + pid = pid[1] + if status := re_match(re_status, value): + status = status[1] case _: pass try: return cls( - version=version, - verbosity=verbosity, - threads=threads, modules=modules, - uptime=uptime, options=options, pid=pid, status=status, + threads=threads, + uptime=uptime, + verbosity=verbosity, + version=version, ) except NameError as e: if debug.enabled: - print(f'name error {e}') + print(f'name error: {e}') return @@ -100,20 +114,25 @@ register.agent_section( def discover_unbound_status(section: UnboundStatus) -> DiscoveryResult: + print(section) yield Service() # UnboundStatus( -# version='1.13.1', -# verbosity=0, -# threads=1, # modules=['subnet', 'validator', 'iterator'], -# uptime=1759, # options=['reuseport', 'control(ssl)'], -# status='(pid 520) is running', # pid=520 +# status='(pid 520) is running', +# threads=1, +# uptime=1759, +# verbosity=0, +# version='1.13.1', # ) def check_unbound_status(params: Mapping[str, Any], section: UnboundStatus, ) -> CheckResult: + if section.status != 'is running': + yield Result(state=State(params['status_not_running']), summary=f'Status: {section.status}') + return + yield Result(state=State.OK, summary=f'Status: {section.status}') yield from check_levels( value=section.uptime, @@ -129,6 +148,8 @@ register.check_plugin( service_name="Unbound", discovery_function=discover_unbound_status, check_function=check_unbound_status, - check_default_parameters={}, + check_default_parameters={ + 'status_not_running': 2 + }, check_ruleset_name="unbound_status", ) diff --git a/source/agent_based/unbound_type_stats.py b/source/agent_based/unbound_type_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..2e3af1b60f92d22b0f83c2ac310c128bd9f014fc --- /dev/null +++ b/source/agent_based/unbound_type_stats.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-05-21 +# File : unbound_type_stats.py + +# based on the unbound plugin by Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# and the work of ruettimann[at]init7[dot]net + +# https://nlnetlabs.nl/projects/unbound/about/ + +from collections.abc import Mapping, Sequence, MutableSequence +from time import time as now_time +from typing import ( + List, + Tuple, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + GetRateError, + Result, + Service, + State, + check_levels, + get_rate, + get_value_store, + register, +) + +from cmk.base.plugins.agent_based.utils.unbound import ( + UnboundSection, + render_qps, +) + +# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml +ANSWER_TYPES_MOST_COMMON: List[str] = [ + 'A', + 'AAAA', + 'ANY', + 'CNAME', + 'DNSKEY', + 'DS', + 'HINFO', + 'HTTPS', + 'MX', + 'NAPTR', + 'NS', + 'PTR', + 'RRSIG', + 'SOA', + 'SRV', + 'SVCB', + 'TXT', + # 'TYPE65', # in summary for HTTPS +] +ANSWER_TYPES_COMMON: List[str] = [ + 'AFSDB', + 'APL', + 'CAA', + 'CDNSKEY', + 'CDS', + 'CERT', + 'CSYNC', + 'DHCID', + 'DLV', + 'DNAME', + 'EUI48', + 'EUI64', + 'HIP', + 'IPSECKEY', + 'KEY', + 'KX', + 'LOC', + 'NSEC', + 'NSEC3', + 'NSEC3PARAM', + 'OPENPGPKEY', + 'RP', + 'SIG', + 'SMIMEA', + 'SSHFP', + 'TA', + 'TKEY', + 'TLSA', + 'TSIG', + 'URI', + 'WALLET', + 'ZONEMD', +] +ANSWER_TYPES_OBSOLETE: List[str] = [ + 'A6', + 'ATMA', + 'DOA', + 'EID', + 'GID', + 'GPOS', + 'ISDN', + 'L32', + 'L64', + 'LP', + 'MAILA', + 'MAILB', + 'MB', + 'MD', + 'MF', + 'MG', + 'MINFO', + 'MR', + 'NB', + 'NBSTAT', + 'NID', + 'NIMLOC', + 'NINFO', + 'NSAP', + 'NSAP-PTR', + 'NULL', + 'NXT', + 'PX', + 'RKEY', + 'RT', + 'SINK', + 'SPF', + 'TALINK', + 'UID', + 'UINFO', + 'UNSPEC', + 'WKS', + 'X25', +] + +ANSWER_TYPES: List[str] = ANSWER_TYPES_MOST_COMMON + ANSWER_TYPES_COMMON + ANSWER_TYPES_OBSOLETE + +SUM_TYPES: Mapping[str: Sequence[str]] = { + 'AAAA': ['AAAA', 'A6'], + 'HTTPS': ['HTTPS', 'TYPE65'], +} + + +def discover_unbound_type_stats(section: UnboundSection) -> DiscoveryResult: + if 'num.query.type.A' in section: + yield Service() + + +def yield_answer( + answer: str, + levels_upper: Tuple[int, int] | None, + total: float, + value: float, + notice: bool = True, + metric: bool = True, +): + now = now_time() + try: + rate = get_rate( + get_value_store(), + f'unbound_type_stats_{answer}', + now, + value, + raise_overflow=True, + ) + except GetRateError as e: + yield Result(state=State.OK, notice=str(e)) + return + else: + # if rate == 0: + # return + if levels_upper is not None and len(levels_upper) == 3: + # levels on the ratio of answers + levels_upper = ( + levels_upper[0] * total, + levels_upper[1] * total, + ) + # if levels_upper: + # notice = False + metric_name = f'unbound_type_stats_{answer.replace("-", "_").replace(" ", "_")}' if metric else None + yield from check_levels( + value=rate, + levels_upper=levels_upper, + metric_name=metric_name, + render_func=render_qps, + label=answer, + notice_only=notice, + ) + + +# ToDo: values are converted to answers/s, total is an absolute value -> should be changed to rate/1? +def check_unbound_type_stats(params: Mapping, section: UnboundSection) -> CheckResult: + key_prefix = 'num.query.type.' + total_common: float = 0.0 + total_obsolete: float = 0.0 + other_value: float = 0.0 + other_types_found: MutableSequence[str] = [] + + for sum_type in SUM_TYPES: + summary = 0.0 + for answer_type in SUM_TYPES[sum_type]: + key = f'{key_prefix}{answer_type}' + summary += section.get(key, 0.0) + try: + section.pop(key) + except KeyError: + pass + + if summary > 0.0: + section.update({f'{key_prefix}{sum_type}': summary}) + + section = dict(sorted(section.items())) + + total = sum( + value for key, value in section.items() + if key.startswith(key_prefix) + ) + + for key, value in section.items(): + if not key.startswith(key_prefix): + continue + answer = key[len(key_prefix):] + + if answer in ANSWER_TYPES: + if answer in ANSWER_TYPES_COMMON: + total_common += value + elif answer in ANSWER_TYPES_OBSOLETE: # + total_obsolete += value + + yield from yield_answer( + answer=answer, + total=total, + value=value, + levels_upper=params.get(f'levels_upper_{answer}') + ) + else: + other_types_found.append(answer) + other_value += value + yield from yield_answer( + answer=answer, + total=total, + value=value, + levels_upper=None, + metric=False, + ) + + if other_types_found: + answer = 'others' + yield from yield_answer( + answer=answer, + total=total, + value=other_value, + levels_upper=params.get(f'levels_upper_{answer}') + ) + answer_types = ', '.join(other_types_found) + yield Result( + state=State(params.get('state_other_type', 0)), + notice=f'Other DNS answer typ(e) found: {answer_types}' + ) + + for answer, value in [ + ('Total', total), + ('Total_common', total_common), + ('Total_obsolete', total_obsolete), + ]: + if value > 0: + yield from yield_answer( + answer=answer, + total=total, + value=value, + levels_upper=params.get(f'levels_upper_{answer}'), + notice=False, + ) + + +register.check_plugin( + name='unbound_type_stats', + service_name='Unbound Type Stats', + sections=['unbound'], + discovery_function=discover_unbound_type_stats, + check_function=check_unbound_type_stats, + check_default_parameters={ + }, + check_ruleset_name='unbound_type_stats', +) diff --git a/source/agent_based/unbound_unwanted_replies.py b/source/agent_based/unbound_unwanted_replies.py new file mode 100644 index 0000000000000000000000000000000000000000..2c9b207c966a328f728706772331f7fb39627472 --- /dev/null +++ b/source/agent_based/unbound_unwanted_replies.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# https://nlnetlabs.nl/projects/unbound/about/ + +# changes by thl-cmk[at]outlook[dot]com +# 2024-04-21: removed Union -> no need "int | float" should do +# added levels_upper_NOERROR to default parameters -> show up in info line +# 2024-05-22: changed time for get_rate from section to system time +# added output in case get_rate is initialising (unknown state in discovery) +# removed default levels for unbound_answers +# added params for unwanted replies + + +from time import time as now_time + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + Service, + check_levels, + get_rate, + get_value_store, + register, +) + +from cmk.base.plugins.agent_based.utils.unbound import ( + UnboundSection, + render_qps, +) + + +def discover_unbound_unwanted_replies(section: UnboundSection) -> DiscoveryResult: + if 'unwanted.replies' in section: + yield Service() + + +def check_unbound_unwanted_replies(params, section: UnboundSection) -> CheckResult: + if 'unwanted.replies' not in section: + return + + rate = get_rate( + get_value_store(), + 'unbound_unwanted_replies', + now_time(), + section['unwanted.replies'], + raise_overflow=True, + ) + + yield from check_levels( + value=rate, + levels_upper=params.get('unwanted_replies'), + metric_name='unbound_unwanted_replies', + render_func=render_qps, + label='Unwanted Replies', + ) + + +register.check_plugin( + name='unbound_unwanted_replies', + service_name='Unbound Unwanted Replies', + sections=['unbound'], + discovery_function=discover_unbound_unwanted_replies, + check_function=check_unbound_unwanted_replies, + check_default_parameters={}, + check_ruleset_name='unbound_replies', +) diff --git a/source/agent_based/utils/unbound.py b/source/agent_based/utils/unbound.py new file mode 100644 index 0000000000000000000000000000000000000000..4833a50e11f217cf5be55d096a4a8c1ce8a11cfc --- /dev/null +++ b/source/agent_based/utils/unbound.py @@ -0,0 +1,17 @@ +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-12 +# File : plugins/agent_based/utils/unbound.py + + +from collections.abc import MutableMapping + +UnboundSection = MutableMapping[str, int | float] + + +def render_qps(x: float) -> str: + return f'{x:.2f}/s' + + diff --git a/source/gui/metrics/unbound.py b/source/gui/metrics/unbound_answers.py similarity index 77% rename from source/gui/metrics/unbound.py rename to source/gui/metrics/unbound_answers.py index deec35d470fd1f93698eac4e257808afdf9fe597..a9bddfb07f78fc242039430791347e5c70900611 100644 --- a/source/gui/metrics/unbound.py +++ b/source/gui/metrics/unbound_answers.py @@ -77,7 +77,15 @@ graph_info['unbound_answers'] = { 'title': _('Rate of answers'), 'metrics': [ (f'unbound_answers_{answer}', 'line') - for answer in ('NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMPL', 'REFUSED', 'nodata') + for answer in ( + 'FORMERR', + 'NOERROR', + 'NOTIMPL', + 'NXDOMAIN', + 'REFUSED', + 'SERVFAIL', + 'nodata', + ) ], } @@ -95,32 +103,3 @@ perfometer_info.append(('stacked', [ 'exponent': 2, }, ])) - -metric_info['cache_hit_rate'] = { - 'title': _('Cache hits per second'), - 'unit': '1/s', - 'color': '26/a', -} - -graph_info['cache_hit_misses'] = { - 'title': _('Cache Hits and Misses'), - 'metrics': [('cache_hit_rate', 'line'), ('cache_misses_rate', 'line')], -} - -metric_info['unbound_unwanted_replies'] = { - 'title': _('Unwanted replies'), - 'unit': '1/s', - 'color': '26/a', -} - -graph_info['unbound_unwanted_replies'] = { - 'title': _('Unwanted replies'), - 'metrics': [('unbound_unwanted_replies', 'area')], -} - -perfometer_info.append({ - 'type': 'logarithmic', - 'metric': 'unbound_unwanted_replies', - 'half_value': 100.0, # ome year - 'exponent': 2, -}) diff --git a/source/gui/metrics/unbound_cache.py b/source/gui/metrics/unbound_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..d485617f600ef6e8062c7e66540586b5c0130101 --- /dev/null +++ b/source/gui/metrics/unbound_cache.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# modifications by thl-cmk[at]outlook[dot]com +# 2024-02-21: changed import path form mk.gui.plugins.metrics to mk.gui.plugins.metrics.utils +# added metrics/graph for unbound_unwanted_replies +# moved to ~/local/lib/check_mk/gui/plugins/metrics (from local/share/check_mk/web/plugins/metrics) +# added perfometer for unbound_answers (NOERROR/SERVFAIL) and unbound_unwanted_replies + +from cmk.gui.i18n import _ + +from cmk.gui.plugins.metrics.utils import ( + metric_info, + graph_info, + perfometer_info, +) + +metric_info['cache_hit_rate'] = { + 'title': _('Cache hits per second'), + 'unit': '1/s', + 'color': '26/a', +} + +graph_info['cache_hit_misses'] = { + 'title': _('Cache Hits and Misses'), + 'metrics': [ + ('cache_hit_rate', 'line'), + ('cache_misses_rate', 'line'), + ], +} diff --git a/source/gui/metrics/unbound_type_stats.py b/source/gui/metrics/unbound_type_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..b28b17d6ce252d5dfc63e51fc9a6f1097c5f8ec5 --- /dev/null +++ b/source/gui/metrics/unbound_type_stats.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-08-02 +# File : unbound_type_stats.py + +# based on the work of ruettimann[at]init7[dot]net + +# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml + +# 2024-08-02: optimized answer_types and metrics creation +# 2024-08-18: added common and obsolete answer types +# 2024-08-27: removed duplicate answer types +# added METRIC_TYPE and ADD-TOTAL options + +from collections.abc import Mapping +from typing import Final +from cmk.gui.i18n import _ + +from cmk.gui.plugins.metrics.utils import ( + check_metrics, + graph_info, + metric_info, + perfometer_info, +) + +METRIC_TYPE: Final[str] = 'stack' # can be stack or line +ADD_TOTAL: Final[bool] = True + +answer_types_most_common: Mapping[str, str] = { + 'Total': '26/a', + 'Total common': '26/b', + 'Total obsolete': '16/b', + 'A': '11/a', + 'AAAA': '21/a', + 'ANY': '31/a', + 'CNAME': '41/a', + 'DNSKEY': '12/a', + 'DS': '22/a', + 'HINFO': '24/a', + 'HTTPS': '15/a', + 'MX': '32/a', + 'NAPTR': '42/a', + 'NS': '13/a', + 'PTR': '23/a', + 'RRSIG': '34/a', + 'SOA': '33/a', + 'SRV': '43/a', + 'SVCB': '44/a', + 'TXT': '14/a', + 'others': '36/a', +} + +# https://en.wikipedia.org/wiki/List_of_DNS_record_types +answer_types_common: Mapping[str, str] = { + 'AFSDB': '11/a', + 'APL': '12/a', + 'CAA': '13/a', + 'CDNSKEY': '14/a', + 'CDS': '15/a', + 'CERT': '16/a', + 'CSYNC': '21/a', + 'DHCID': '22/a', + 'DLV': '23/a', + 'DNAME': '24/a', + 'EUI48': '25/a', + 'EUI64': '31/a', + 'HIP': '32/a', + 'IPSECKEY': '33/a', + 'KEY': '34/a', + 'KX': '35/a', + 'LOC': '36/a', + 'NSEC': '42/a', + 'NSEC3': '43/a', + 'NSEC3PARAM': '44/a', + 'OPENPGPKEY': '45/a', + 'RP': '46/a', + 'SIG': '11/b', + 'SMIMEA': '12/b', + 'SSHFP': '13/b', + 'TA': '14/b', + 'TKEY': '15/b', + 'TLSA': '16/b', + 'TSIG': '21/b', + 'URI': '22/b', + 'WALLET': '23/b', + 'ZONEMD': '24/b', +} + +# https://en.wikipedia.org/wiki/List_of_DNS_record_types +answer_types_obsolete: Mapping[str, str] = { + 'MD': '11/b', + 'MF': '12/b', + 'MAILA': '13/b', + 'MB': '14/b', + 'MG': '15/b', + 'MR': '16/b', + 'MINFO': '21/b', + 'MAILB': '22/b', + 'WKS': '23/b', + 'NB': '24/b', + 'NBSTAT': '25/b', + 'NULL': '26/b', + 'A6': '31/b', + 'NXT': '32/b', + 'X25': '41/b', + 'ISDN': '42/b', + 'RT': '43/b', + 'NSAP': '44/b', + 'NSAP-PTR': '45/b', + 'PX': '46/b', + 'EID': '11/a', + 'NIMLOC': '12/a', + 'ATMA': '13/a', + 'SINK': '15/a', + 'GPOS': '16/a', + 'UINFO': '21/a', + 'UID': '22/a', + 'GID': '23/a', + 'UNSPEC': '24/a', + 'SPF': '25/a', + 'NINFO': '26/a', + 'RKEY': '31/a', + 'TALINK': '32/a', + 'NID': '33/qa', + 'L32': '34/a', + 'L64': '35/a', + 'LP': '36/a', + 'DOA': '41/a', +} + +check_metrics["check_mk-unbound_type_stats"] = {} + + +def clean_metric(answer: str) -> str: + return f'unbound_type_stats_{answer.replace(" ", "_").replace("-", "_")}' + + +for answer_type, color in answer_types_most_common.items(): + metric_info[clean_metric(answer_type)] = { + 'title': _(f'Rate of {answer_type} type stats'), + 'unit': '1/s', + 'color': color, + } + check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}}) + +for answer_type, color in answer_types_common.items(): + metric_info[clean_metric(answer_type)] = { + 'title': _(f'Rate of {answer_type} type stats'), + 'unit': '1/s', + 'color': color, + } + check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}}) + +for answer_type, color in answer_types_obsolete.items(): + metric_info[clean_metric(answer_type)] = { + 'title': _(f'Rate of {answer_type} type stats'), + 'unit': '1/s', + 'color': color, + } + check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}}) + +graph_info['unbound_type_stats'] = { + 'title': _('Rate of type stats'), + 'metrics': [( + clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_most_common.keys())[3:], reverse=True) + ], + 'optional_metrics': [clean_metric(answer) for answer in answer_types_most_common] +} + +graph_info['unbound_type_stats_common'] = { + 'title': _('Rate of type stats (common)'), + 'metrics': [( + clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_common.keys()), reverse=True) + ], + 'optional_metrics': [clean_metric(answer) for answer in answer_types_common] +} + +graph_info['unbound_type_stats_obsolete'] = { + 'title': _('Rate of type stats (obsolete)'), + 'metrics': [( + clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_obsolete.keys()), reverse=True) + ], + 'optional_metrics': [clean_metric(answer) for answer in answer_types_obsolete] +} + +if ADD_TOTAL: + graph_info['unbound_type_stats']['metrics'] = graph_info['unbound_type_stats']['metrics'] + [ + (f'unbound_type_stats_Total_obsolete', METRIC_TYPE), + (f'unbound_type_stats_Total_common', METRIC_TYPE), + (f'unbound_type_stats_Total', 'line'), + ] + graph_info['unbound_type_stats_common']['metrics'] = graph_info['unbound_type_stats_common']['metrics'] + [ + (f'unbound_type_stats_Total_common', 'line')] + graph_info['unbound_type_stats_obsolete']['metrics'] = graph_info['unbound_type_stats_obsolete']['metrics'] + [ + (f'unbound_type_stats_Total_obsolete', 'line')] + +perfometer_info.append( + { + 'type': 'logarithmic', + 'metric': 'unbound_type_stats_Total', + 'half_value': 100.0, + 'exponent': 2, + } +) diff --git a/source/gui/metrics/unbound_unwanted_replies.py b/source/gui/metrics/unbound_unwanted_replies.py new file mode 100644 index 0000000000000000000000000000000000000000..26ad2d463a5cb41458e0b91fe0e6eea6f049c39a --- /dev/null +++ b/source/gui/metrics/unbound_unwanted_replies.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# modifications by thl-cmk[at]outlook[dot]com +# 2024-02-21: changed import path form mk.gui.plugins.metrics to mk.gui.plugins.metrics.utils +# added metrics/graph for unbound_unwanted_replies +# moved to ~/local/lib/check_mk/gui/plugins/metrics (from local/share/check_mk/web/plugins/metrics) +# added perfometer for unbound_answers (NOERROR/SERVFAIL) and unbound_unwanted_replies + +from cmk.gui.i18n import _ + +from cmk.gui.plugins.metrics.utils import ( + metric_info, + graph_info, + perfometer_info, +) + +metric_info['cache_hit_rate'] = { + 'title': _('Cache hits per second'), + 'unit': '1/s', + 'color': '26/a', +} + +graph_info['cache_hit_misses'] = { + 'title': _('Cache Hits and Misses'), + 'metrics': [ + ('cache_hit_rate', 'line'), + ('cache_misses_rate', 'line'), + ], +} + +metric_info['unbound_unwanted_replies'] = { + 'title': _('Unwanted replies'), + 'unit': '1/s', + 'color': '26/a', +} + +graph_info['unbound_unwanted_replies'] = { + 'title': _('Unwanted replies'), + 'metrics': [ + ('unbound_unwanted_replies', 'area') + ], +} + +perfometer_info.append({ + 'type': 'logarithmic', + 'metric': 'unbound_unwanted_replies', + 'half_value': 100.0, # ome year + 'exponent': 2, +}) diff --git a/source/gui/wato/check_parameters/unbound.py b/source/gui/wato/check_parameters/unbound_answers.py similarity index 67% rename from source/gui/wato/check_parameters/unbound.py rename to source/gui/wato/check_parameters/unbound_answers.py index f20bb6e60c204a7c1ce94bccc3ff765e1436b5bd..457d8965db0e9de4bcc941cc20811f95c862b1ee 100644 --- a/source/gui/wato/check_parameters/unbound.py +++ b/source/gui/wato/check_parameters/unbound_answers.py @@ -44,38 +44,6 @@ from cmk.gui.valuespec import ( ) -def _parameter_valuespec_unbound_cache(): - return Dictionary( - title=_('Unbound: Cache'), - elements=[ - ('cache_misses', - Tuple( - title='Levels on cache misses per second', - elements=[ - Float(title='warn', ), - Float(title='crit', ), - ])), - ('cache_hits', - Tuple( - title='Lower levels for hits in %', - elements=[ - Percentage(title='warn', ), - Percentage(title='crit', ), - ])), - ]) - - -rulespec_registry.register( - CheckParameterRulespecWithoutItem( - check_group_name='unbound_cache', - group=RulespecGroupCheckParametersApplications, - match_type='dict', - parameter_valuespec=_parameter_valuespec_unbound_cache, - title=lambda: _('Unbound Cache'), - ) -) - - def _parameter_valuespec_unbound_answers(): return Dictionary( title=_('Unbound answers'), @@ -91,6 +59,7 @@ def _parameter_valuespec_unbound_answers(): Float(title=_('Critical at'), unit=_('qps')), ], title=_('Upper levels in qps'), + orientation='horizontal', ), Tuple( elements=[ @@ -99,15 +68,16 @@ def _parameter_valuespec_unbound_answers(): FixedValue(value='%', totext=''), # needed to decide between both variants ], title=_('Upper levels in %'), + orientation='horizontal', ), ])) for answer in ( - 'NOERROR', 'FORMERR', - 'SERVFAIL', - 'NXDOMAIN', + 'NOERROR', 'NOTIMPL', + 'NXDOMAIN', 'REFUSED', + 'SERVFAIL', 'nodata', ) ], @@ -123,28 +93,3 @@ rulespec_registry.register( title=lambda: _('Unbound Answers'), ) ) - - -def _parameter_valuespec_unbound_replies(): - return Dictionary( - title=_('Unbound: Replies'), - elements=[ - ('unwanted_replies', - Tuple( - title='Levels on unwanted replies per second', - elements=[ - Float(title='warn', ), - Float(title='crit', ), - ])), - ]) - - -rulespec_registry.register( - CheckParameterRulespecWithoutItem( - check_group_name='unbound_replies', - group=RulespecGroupCheckParametersApplications, - match_type='dict', - parameter_valuespec=_parameter_valuespec_unbound_replies, - title=lambda: _('Unbound Replies'), - ) -) diff --git a/source/gui/wato/check_parameters/unbound_bakery.py b/source/gui/wato/check_parameters/unbound_bakery.py index d42570f1461550a8e41345ae48392e0cfbd453a3..24f04cb573b1d4d97f212617c81541d6a51b3845 100644 --- a/source/gui/wato/check_parameters/unbound_bakery.py +++ b/source/gui/wato/check_parameters/unbound_bakery.py @@ -8,13 +8,14 @@ # Date : unbound_bakery.py # File : unbound_bakery.py -# modifications by thl-cmk[at]outlook[dot]com # 2024-04-21: fixed missing FixedValue in import -# changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them +# changed Alternative titles to "Upper levels in qps"/"Upper levels in %" +# to better differentiate between them # added explicit unit "%2 # added ruleset for bakery # renamed to unbound.py (from unbound_parameters.py) -# moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato) +# moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters +# from local/share/check_mk/web/plugins/wato # 2024-05-14: separated WATO for bakery and check in two files # 2024-05-27: fixed crash in CRE version (has no cee elements) diff --git a/source/gui/wato/check_parameters/unbound_cache.py b/source/gui/wato/check_parameters/unbound_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..51f6f5bb8a587520aca0a67d94e3ad43e52b1ebc --- /dev/null +++ b/source/gui/wato/check_parameters/unbound_cache.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# modifications by thl-cmk[at]outlook[dot]com +# 2024-04-21: fixed missing FixedValue in import +# changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them +# added explicit unit "%2 +# added ruleset for bakery +# renamed to unbound.py (from unbound_parameters.py) +# moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato) +# 2024-05-14: separated WATO for bakery and check in two files +# 2024-05-22: added ruleset for unwanted replies + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithoutItem, + RulespecGroupCheckParametersApplications, + rulespec_registry, +) + +from cmk.gui.valuespec import ( + Dictionary, + Float, + Percentage, + Tuple, +) + + +def _parameter_valuespec_unbound_cache(): + return Dictionary( + title=_('Unbound: Cache'), + elements=[ + ('cache_misses', + Tuple( + title='Levels on cache misses per second', + elements=[ + Float(title='warn', ), + Float(title='crit', ), + ])), + ('cache_hits', + Tuple( + title='Lower levels for hits in %', + elements=[ + Percentage(title='warn', ), + Percentage(title='crit', ), + ])), + ]) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name='unbound_cache', + group=RulespecGroupCheckParametersApplications, + match_type='dict', + parameter_valuespec=_parameter_valuespec_unbound_cache, + title=lambda: _('Unbound Cache'), + ) +) diff --git a/source/gui/wato/check_parameters/unbound_replies.py b/source/gui/wato/check_parameters/unbound_replies.py new file mode 100644 index 0000000000000000000000000000000000000000..f4bad54ac218d53cb21296d23290847b52ea9ee6 --- /dev/null +++ b/source/gui/wato/check_parameters/unbound_replies.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# modifications by thl-cmk[at]outlook[dot]com +# 2024-04-21: fixed missing FixedValue in import +# changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them +# added explicit unit "%2 +# added ruleset for bakery +# renamed to unbound.py (from unbound_parameters.py) +# moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato) +# 2024-05-14: separated WATO for bakery and check in two files +# 2024-05-22: added ruleset for unwanted replies + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithoutItem, + RulespecGroupCheckParametersApplications, + rulespec_registry, +) + +from cmk.gui.valuespec import ( + Dictionary, + Float, + Tuple, +) + + +def _parameter_valuespec_unbound_replies(): + return Dictionary( + title=_('Unbound: Replies'), + elements=[ + ('unwanted_replies', + Tuple( + title='Levels on unwanted replies per second', + elements=[ + Float(title='warn', ), + Float(title='crit', ), + ])), + ]) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name='unbound_replies', + group=RulespecGroupCheckParametersApplications, + match_type='dict', + parameter_valuespec=_parameter_valuespec_unbound_replies, + title=lambda: _('Unbound Replies'), + ) +) diff --git a/source/gui/wato/check_parameters/unbound_status.py b/source/gui/wato/check_parameters/unbound_status.py new file mode 100644 index 0000000000000000000000000000000000000000..a58edde2d358d59536a853fae1d7ae6ba0184f46 --- /dev/null +++ b/source/gui/wato/check_parameters/unbound_status.py @@ -0,0 +1,41 @@ +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-09-19 +# File : unbound_status.py + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithoutItem, + RulespecGroupCheckParametersApplications, + rulespec_registry, +) + +from cmk.gui.valuespec import ( + Dictionary, + MonitoringState, +) + + +def _parameter_valuespec_unbound_status(): + return Dictionary( + title=_('Unbound: status'), + elements=[ + ('status_not_running', + MonitoringState( + title='Monitoring state if unbound is not running', + default_value=2, + )), + ]) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name='unbound_status', + group=RulespecGroupCheckParametersApplications, + match_type='dict', + parameter_valuespec=_parameter_valuespec_unbound_status, + title=lambda: _('Unbound status'), + ) +) diff --git a/source/gui/wato/check_parameters/unbound_type_stats.py b/source/gui/wato/check_parameters/unbound_type_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..2e34f0f6c21b532d69efce97efd56aefeb87cd4a --- /dev/null +++ b/source/gui/wato/check_parameters/unbound_type_stats.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-08-02 +# File : unbound_type_stats.py + +# based on the work of ruettimann[at]init7[dot]net + + +from collections.abc import Sequence +from typing import Final + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithoutItem, + RulespecGroupCheckParametersApplications, + rulespec_registry, +) + +from cmk.gui.valuespec import ( + Alternative, + Dictionary, + FixedValue, + Float, + Percentage, + Tuple, +) + +# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml +DNS_TYPES: Final[Sequence[str]] = [ + 'A', + 'PTR', + 'AAAA', + 'ANY', + 'CNAME', + 'DNSKEY', + 'DS', + 'HINFO', + 'HTTPS', + 'MX', + 'NAPTR', + 'NS', + 'RRSIG', + 'SOA', + 'SRV', + 'SVCB', + 'TXT', + # 'NULL', + # 'TYPE0', + # 'WKS', + 'unknown', +] + +DNS_TYPES_CHOICES: Final[Sequence[str]] = [(answer, answer) for answer in DNS_TYPES] + +_answer_levels = [ + (f'levels_upper_{dns_type}', + Alternative( + title=f'Upper levels for {dns_type} answers', + show_alternative_title=True, + elements=[ + Tuple( + elements=[ + Float(title=_('Warning at'), unit=_('qps')), + Float(title=_('Critical at'), unit=_('qps')), + ], + title=_('Upper levels in qps'), + orientation='horizontal', + ), + Tuple( + elements=[ + Percentage(title=_('Warning at'), unit=_('%')), + Percentage(title=_('Critical at'), unit=_('%')), + FixedValue(value='%', totext=''), # needed to decide between both variants + ], + title=_('Upper levels in %'), + orientation='horizontal', + ), + ])) + for dns_type in DNS_TYPES +] + +_elements = _answer_levels +# _elements.append( +# ('other_answer_types', +# DualListChoice( +# title=_('Other answer types'), +# help=_('Selected answer types will be summarized under "other"'), +# choices=DNS_TYPES_CHOICES, +# rows=len(DNS_TYPES_CHOICES), +# allow_empty=False, +# )) +# ) + + +def _parameter_valuespec_unbound_type_stats(): + return Dictionary( + title=_('Unbound type stats'), + elements=_elements, + ) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name='unbound_type_stats', + group=RulespecGroupCheckParametersApplications, + match_type='dict', + parameter_valuespec=_parameter_valuespec_unbound_type_stats, + title=lambda: _('Unbound Type Stats'), + ) +) diff --git a/source/packages/unbound b/source/packages/unbound index f04deacef6242002ca37ec8f84e17c871f0af313..646b36a772b9421d3e55a5849602f25a8cc669ef 100644 --- a/source/packages/unbound +++ b/source/packages/unbound @@ -11,17 +11,33 @@ 'This plugin is based on the work of Jan-Philipp Litza ' '(PLUTEX) jpl[at]plutex[dor]de.\n' 'See: https://exchange.checkmk.com/p/unbound for more ' - 'details.\n', + 'details.\n' + '\n' + 'ThX to ruettimann[at]init7[dot]net for the unbound_type_stats ' + 'check\n', 'download_url': 'https://thl-cmk.hopto.org', - 'files': {'agent_based': ['unbound.py', 'unbound_status.py'], + 'files': {'agent_based': ['unbound_status.py', + 'unbound.py', + 'unbound_answers.py', + 'unbound_cache.py', + 'unbound_unwanted_replies.py', + 'utils/unbound.py', + 'unbound_type_stats.py'], 'agents': ['plugins/unbound'], - 'gui': ['metrics/unbound.py', - 'wato/check_parameters/unbound.py', - 'wato/check_parameters/unbound_bakery.py'], + 'gui': ['wato/check_parameters/unbound_bakery.py', + 'wato/check_parameters/unbound_answers.py', + 'wato/check_parameters/unbound_cache.py', + 'wato/check_parameters/unbound_replies.py', + 'metrics/unbound_answers.py', + 'metrics/unbound_cache.py', + 'metrics/unbound_unwanted_replies.py', + 'metrics/unbound_type_stats.py', + 'wato/check_parameters/unbound_type_stats.py', + 'wato/check_parameters/unbound_status.py'], 'lib': ['python3/cmk/base/cee/plugins/bakery/unbound.py']}, 'name': 'unbound', 'title': 'Unbound', - 'version': '1.2.6-20240527', + 'version': '1.3.3-20240927', 'version.min_required': '2.2.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', 'version.usable_until': '2.4.0b1'}