From 255bc34a2c0f3a64900e33cabab5659eae886fd1 Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Sun, 30 Mar 2025 17:08:27 +0200 Subject: [PATCH] modified for check APIv2, ruleset APIv1, graphing APIv1 --- README.md | 2 +- mkp/bgp_peer-2.3.0-20250329.mkp | Bin 0 -> 18055 bytes source/agent_based/bgp_peer.py | 328 +--------------- .../bgp_peer/agent_based/bgp_peer.py | 352 ++++++++++++++++++ .../bgp_peer}/agent_based/inv_bgp_peer.py | 18 +- .../bgp_peer}/checkman/bgp_peer | 0 .../bgp_peer/graphing/bgp_peer.py | 290 +++++++++++++++ .../bgp_peer/lib}/bgp_peer.py | 0 .../bgp_peer/rulesets/bgp_peer.py | 336 +++++++++++++++++ .../bgp_peer/rulesets/inv_bgp_peer.py | 124 ++++++ source/gui/metrics/bgp_peer.py | 253 ------------- source/gui/wato/check_parameters/bgp_peer.py | 254 +------------ .../gui/wato/check_parameters/inv_bgp_peer.py | 109 ------ source/packages/bgp_peer | 20 +- 14 files changed, 1125 insertions(+), 961 deletions(-) create mode 100644 mkp/bgp_peer-2.3.0-20250329.mkp create mode 100644 source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py rename source/{ => cmk_addons_plugins/bgp_peer}/agent_based/inv_bgp_peer.py (94%) rename source/{ => cmk_addons_plugins/bgp_peer}/checkman/bgp_peer (100%) create mode 100644 source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py rename source/{agent_based/utils => cmk_addons_plugins/bgp_peer/lib}/bgp_peer.py (100%) create mode 100644 source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py create mode 100644 source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py delete mode 100644 source/gui/metrics/bgp_peer.py delete mode 100644 source/gui/wato/check_parameters/inv_bgp_peer.py diff --git a/README.md b/README.md index 424b923..66e44bc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/bgp_peer-2.2.8-20250117.mkp "bgp_peer-2.2.8-20250117.mkp" +[PACKAGE]: ../../raw/master/mkp/bgp_peer-2.3.0-20250329.mkp "bgp_peer-2.3.0-20250329.mkp" # BGP Peer Check plugin to monitor the status of BGP peers and inventory plugin for static BGP peer data. diff --git a/mkp/bgp_peer-2.3.0-20250329.mkp b/mkp/bgp_peer-2.3.0-20250329.mkp new file mode 100644 index 0000000000000000000000000000000000000000..e3f51db18d304f8228fdfbd54a2c2c9c0632aed8 GIT binary patch literal 18055 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 MeY2<p%<%yJ22tfRlK=n! literal 0 HcmV?d00001 diff --git a/source/agent_based/bgp_peer.py b/source/agent_based/bgp_peer.py index df8172b..32f65c7 100644 --- a/source/agent_based/bgp_peer.py +++ b/source/agent_based/bgp_peer.py @@ -1,328 +1,2 @@ #!/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 : 2021-08-20 - -# based on the BGP peer plugin by Thomas Wollner (tw@wollner-net.de) -# -# 2021-08-20: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com -# 2021-08-21: added more perfdata, added metrics file -# 2021-08-22: added WATO options, streamlined with cisco_bgp_peer -# 2021-08-29: moved static (longoutput) to inventory plugin -# moved helper functions to utils/bgp_peer -# 2021-11-14: merged check function with cisco_bgp_peer -# moved parse function to utils/bgp_peer -# 2021-04-02: rewritten bgp neighbor state handling (made configurable) -# 2022-04-29: added upper/lower prefix limits from wato -# added info if device is admin prefix limit capable (device_admin_limit) -# 2022-05-09: made item name configurable (don't use address-family/routing-instance/VRF) -# 2022-05-11: changed bgp_get_peer_entry to get proper parameters instead of Nontransparent list -# added remote_as to BgpPeerItem -# 2022-09-05: added missing wato parameters to register.check_plugin check_default_parameters -# 2022-09-10: made more reliable on limited data (fsm_established_time and admin_state) -# (THX to martin[dot]pechstein[at]posteo[dot]de) -# 2022-09-11: optimized internal flow: > alias > not found > admin down > peer state > ... -# 2023-01-21: changed to always yield fsm_established_time (not only if beep connects, but also on all other states) -# 2023-01-22: fix output for admin_state -# 2023-02-16: changed for CMK 2.1 (moved gui files from local/share/.. to local/lib/..) -# fix type error in discovery (CMK2.1 GUI only) -# 2023-02-20: fix crash if metric data is None -# 2023-03-26: optimized output of metrics, GetRateError will not be set to 0 anymore -# 2023-08-17: fix removed internal_item form check_default_parameters (THX mail[at]bastian-kuhn[dot]de) -# added warning if internal_item is missing in params -# 2024-06-07: fixed crash on configured state mapping -# 2024-12-20: added local address/as/identifier for bgp_topology - -# Example Agent Output: -# BGP4-MIB - -# .1.3.6.1.2.1.15.3.1.1.192.168.254.2 = IpAddress: 192.168.254.2 -# .1.3.6.1.2.1.15.3.1.2.192.168.254.2 = INTEGER: 3 -# .1.3.6.1.2.1.15.3.1.3.192.168.254.2 = INTEGER: 2 -# .1.3.6.1.2.1.15.3.1.4.192.168.254.2 = INTEGER: 4 -# .1.3.6.1.2.1.15.3.1.5.192.168.254.2 = IpAddress: 0.0.0.0 -# .1.3.6.1.2.1.15.3.1.7.192.168.254.2 = IpAddress: 192.168.254.2 -# .1.3.6.1.2.1.15.3.1.9.192.168.254.2 = INTEGER: 65301 -# .1.3.6.1.2.1.15.3.1.10.192.168.254.2 = Counter32: 0 -# .1.3.6.1.2.1.15.3.1.11.192.168.254.2 = Counter32: 5 -# .1.3.6.1.2.1.15.3.1.14.192.168.254.2 = Hex-STRING: 04 00 -# .1.3.6.1.2.1.15.3.1.16.192.168.254.2 = Gauge32: 6586822 -# - -import time -from typing import Dict, List - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - Result, - check_levels, - State, - SNMPTree, - exists, - get_rate, - GetRateError, - get_value_store, - # IgnoreResultsError, - Metric, - render, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, - CheckResult, - StringTable, -) -from cmk.base.plugins.agent_based.utils.bgp_peer import ( - BgpPeer, - bgp_get_peer_entry, - bgp_adminstate, - bgp_peerstate, -) - - -def parse_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] | None: - peer_table = {} - try: - peer_info, local_info = string_table - except ValueError: - return - - try: - local_as, local_id = local_info[0] - except (IndexError, ValueError): - local_as, local_id = None, None - - for entry in peer_info: - remote_addr, remote_id, peer_state, admin_state, local_addr, remote_as, in_updates, out_updates, \ - in_messages, out_messages, fsm_established_transitions, fsm_established_time, in_update_elapsed_time = entry - - bgp_peer = bgp_get_peer_entry( - admin_state=admin_state, - fsm_established_time=fsm_established_time, - fsm_established_transitions=fsm_established_transitions, - in_messages=in_messages, - in_update_elapsed_time=in_update_elapsed_time, - in_updates=in_updates, - local_addr=local_addr, - out_messages=out_messages, - out_updates=out_updates, - peer_state=peer_state, - remote_addr=remote_addr, - remote_as=remote_as, - remote_id=remote_id, - local_as=local_as, - local_id=local_id, - ) - if bgp_peer: - peer_table.update(bgp_peer) - return peer_table - - -def discovery_bgp_peer(params, section: Dict[str, BgpPeer]) -> DiscoveryResult: - _item_parts = [ - 'remote_address', - 'remote_as', - 'address_family', - 'routing_instance', - ] - for key in section.keys(): - parameters = {'internal_item': key} - item = '' - for item_part in _item_parts: - if item_part not in params['build_item']: - item += f'{section[key].item[item_part]} ' - item = item.strip(' ') - yield Service(item=item, parameters=parameters) - - -def bgp_peer_base_info(peer: BgpPeer) -> GeneratorExit: - for message, value in [ - ('Local AS:', peer.local_as), - ('Local address:', peer.local_addr), - ('Local identifier:', peer.local_id), - ('Remote AS:', peer.remote_as), - ('Remote address:', peer.remote_addr), - ('Remote identifier:', peer.remote_id), - ]: - if value is not None: - yield Result(state=State.OK, notice=f'{message} {value}') - - -def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult: - if not params.get('internal_item'): - yield Result( - state=State.WARN, - summary='This service is using old parameters (see details)', - details='This service is using old parameters. To refresh the parameters you need to do a "Tabula rasa" ' - '(Discover services -> Remove all and find new)', - ) - else: - item = params['internal_item'] - - neighborstate = { - '1': 2, # idle - '2': 1, # connect - '3': 1, # active - '4': 1, # opensent - '5': 1, # openconfirm - '6': 0, # established - } - - neighborstate.update(params['neighborstate']) - peer_not_found_state = params['peernotfound'] - - for bgp_connection, bgp_alias, not_found_state in params.get('peer_list', []): - if item == bgp_connection: - if bgp_alias != '': - yield Result(state=State.OK, summary=f'[{bgp_alias}]', details=' ') - peer_not_found_state = not_found_state - - # get item data - try: - peer = section[item] - except KeyError: - yield Result(state=State(peer_not_found_state), summary='Item not found in SNMP data') - return - - if peer.description: - yield Result(state=State.OK, summary=f'[{peer.description}]', details=' ') - - if peer.admin_state == 1: # shutdown - yield Result(state=State(params['admindown']), summary=f'Admin state: {bgp_adminstate(peer.admin_state)}') - yield from bgp_peer_base_info(peer) - return - - yield Result( - state=State(neighborstate.get(str(peer.peer_state))), - notice=f'Peer state: {bgp_peerstate(peer.peer_state)}' - ) - - if peer.fsm_established_time: # always yield fsm_established time - yield Metric( - name=f'bgp_peer_fsmestablishedtime', - value=peer.fsm_established_time, - boundaries=(0, None) - ) - - if not peer.peer_state == 6: # not established - yield from bgp_peer_base_info(peer) - return - - if peer.fsm_established_time: # if peer is established use fsm_established_time as uptime - yield from check_levels( - value=peer.fsm_established_time, - label='Uptime', - levels_lower=params['minuptime'], - render_func=render.timespan, - metric_name='bgp_peer_fsmestablishedtime', - ) - - if peer.peer_unavail_reason != 0: # huawei peer unavailable state - yield Result(state=State.CRIT, notice=F'Peer unavailable reason: {peer.peer_unavail_reason_str}') - - if peer.device_admin_limit and peer.prefix_admin_limit is None: - yield Result( - state=State(params['noprefixlimit']), - notice='Prefix limit/warn threshold not configured on the device.', - ) - - acceptedprefixes = peer.accepted_prefixes - prefixadminlimit = peer.prefix_admin_limit - prefixthreshold = peer.prefix_threshold - warnthreshold = None - - if prefixadminlimit is not None and prefixthreshold is not None: - warnthreshold = int(prefixadminlimit / 100.0 * prefixthreshold) # use float (100.0) to get xx.xx in division - if acceptedprefixes is not None and peer.peer_state == 6: # peer established and prefixes accepted - yield from check_levels( - value=acceptedprefixes, - metric_name='bgp_peer_acceptedprefixes', - levels_upper=params.get('accepted_prefixes_upper_levels', (warnthreshold, prefixadminlimit)), - levels_lower=params.get('accepted_prefixes_lower_levels'), - label='Prefixes accepted', - render_func=lambda v: f'{v}' - ) - - now_time = time.time() - value_store = get_value_store() - - for key, value in peer.metric_rate: - try: - value = get_rate(value_store, f'{key}', now_time, value, raise_overflow=True) - except GetRateError: - continue - yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None)) - - for key, value in peer.metric_count: - if value is not None: - yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None)) - - yield from bgp_peer_base_info(peer) - - -register.snmp_section( - name='bgp_peer', - parse_function=parse_bgp_peer, - fetch=[ - SNMPTree( - base='.1.3.6.1.2.1.15.3.1', # BGP4-MIB::BgpPeerEntry - oids=[ - '7', # bgpPeerRemoteAddr - '1', # bgpPeerIdentifier - '2', # bgpPeerState - '3', # bgpPeerAdminStatus - '5', # bgpPeerLocalAddr - '9', # bgpPeerRemoteAs - '10', # bgpPeerInUpdates - '11', # bgpPeerOutUpdates - '12', # bgpPeerInTotalMessages - '13', # bgpPeerOutTotalMessages - '15', # bgpPeerFsmEstablishedTransitions - '16', # bgpPeerFsmEstablishedTime - '24', # bgpPeerInUpdateElapsedTime - ]), - SNMPTree( - base='.1.3.6.1.2.1.15', # BGP4-MIB - oids=[ - '2', # bgpLocalAs - '4', # bgpIdentifier (local) - ]) - ], - detect=exists('.1.3.6.1.2.1.15.3.1.1.*') -) - -register.check_plugin( - name='bgp_peer', - service_name='BGP peer %s', - discovery_function=discovery_bgp_peer, - discovery_default_parameters={ - 'build_item': [ - # 'remote_address', - 'remote_as', - # 'address_family', - # 'routing_instance', - ] - }, - discovery_ruleset_name='discovery_bgp_peer', - check_function=check_bgp_peer, - check_default_parameters={ - 'minuptime': (7200, 3600), - 'peer_list': [], - 'peernotfound': 2, - 'admindown': 1, - 'noprefixlimit': 1, - 'neighborstate': { - '1': 2, # idle - '2': 1, # connect - '3': 1, # active - '4': 1, # opensent - '5': 1, # openconfirm - '6': 0, # established - }, - }, - check_ruleset_name='bgp_peer', -) +# dummy needed for CMK 2.30x to shadow the original file \ No newline at end of file diff --git a/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py new file mode 100644 index 0000000..e2f29fe --- /dev/null +++ b/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py @@ -0,0 +1,352 @@ +#!/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 : 2021-08-20 + +# based on the BGP peer plugin by Thomas Wollner (tw@wollner-net.de) +# +# 2021-08-20: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com +# 2021-08-21: added more perfdata, added metrics file +# 2021-08-22: added WATO options, streamlined with cisco_bgp_peer +# 2021-08-29: moved static (longoutput) to inventory plugin +# moved helper functions to utils/bgp_peer +# 2021-11-14: merged check function with cisco_bgp_peer +# moved parse function to utils/bgp_peer +# 2021-04-02: rewritten bgp neighbor state handling (made configurable) +# 2022-04-29: added upper/lower prefix limits from wato +# added info if device is admin prefix limit capable (device_admin_limit) +# 2022-05-09: made item name configurable (don't use address-family/routing-instance/VRF) +# 2022-05-11: changed bgp_get_peer_entry to get proper parameters instead of Nontransparent list +# added remote_as to BgpPeerItem +# 2022-09-05: added missing wato parameters to register.check_plugin check_default_parameters +# 2022-09-10: made more reliable on limited data (fsm_established_time and admin_state) +# (THX to martin[dot]pechstein[at]posteo[dot]de) +# 2022-09-11: optimized internal flow: > alias > not found > admin down > peer state > ... +# 2023-01-21: changed to always yield fsm_established_time (not only if beep connects, but also on all other states) +# 2023-01-22: fix output for admin_state +# 2023-02-16: changed for CMK 2.1 (moved gui files from local/share/.. to local/lib/..) +# fix type error in discovery (CMK2.1 GUI only) +# 2023-02-20: fix crash if metric data is None +# 2023-03-26: optimized output of metrics, GetRateError will not be set to 0 anymore +# 2023-08-17: fix removed internal_item form check_default_parameters (THX mail[at]bastian-kuhn[dot]de) +# added warning if internal_item is missing in params +# 2024-06-07: fixed crash on configured state mapping +# 2024-12-20: added local address/as/identifier for bgp_topology +# 2025-03-29: refactored for ruleset APIv1 + +# Example Agent Output: +# BGP4-MIB + +# .1.3.6.1.2.1.15.3.1.1.192.168.254.2 = IpAddress: 192.168.254.2 +# .1.3.6.1.2.1.15.3.1.2.192.168.254.2 = INTEGER: 3 +# .1.3.6.1.2.1.15.3.1.3.192.168.254.2 = INTEGER: 2 +# .1.3.6.1.2.1.15.3.1.4.192.168.254.2 = INTEGER: 4 +# .1.3.6.1.2.1.15.3.1.5.192.168.254.2 = IpAddress: 0.0.0.0 +# .1.3.6.1.2.1.15.3.1.7.192.168.254.2 = IpAddress: 192.168.254.2 +# .1.3.6.1.2.1.15.3.1.9.192.168.254.2 = INTEGER: 65301 +# .1.3.6.1.2.1.15.3.1.10.192.168.254.2 = Counter32: 0 +# .1.3.6.1.2.1.15.3.1.11.192.168.254.2 = Counter32: 5 +# .1.3.6.1.2.1.15.3.1.14.192.168.254.2 = Hex-STRING: 04 00 +# .1.3.6.1.2.1.15.3.1.16.192.168.254.2 = Gauge32: 6586822 +# + +import time +from typing import Dict, List, Generator + +from cmk.agent_based.v2 import ( + CheckPlugin, + CheckResult, + DiscoveryResult, + GetRateError, + Metric, + Result, + SNMPSection, + SNMPTree, + Service, + State, + StringTable, + check_levels, + exists, + get_rate, + get_value_store, + render, +) + +from cmk_addons.plugins.bgp_peer.lib.bgp_peer import ( + BgpPeer, + bgp_adminstate, + bgp_get_peer_entry, + bgp_peerstate, +) + + +def parse_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] | None: + peer_table = {} + try: + peer_info, local_info = string_table + except ValueError: + return None + + try: + local_as, local_id = local_info[0] + except (IndexError, ValueError): + local_as, local_id = None, None + + for entry in peer_info: + remote_addr, remote_id, peer_state, admin_state, local_addr, remote_as, in_updates, out_updates, \ + in_messages, out_messages, fsm_established_transitions, fsm_established_time, in_update_elapsed_time = entry + + bgp_peer = bgp_get_peer_entry( + admin_state=admin_state, + fsm_established_time=fsm_established_time, + fsm_established_transitions=fsm_established_transitions, + in_messages=in_messages, + in_update_elapsed_time=in_update_elapsed_time, + in_updates=in_updates, + local_addr=local_addr, + out_messages=out_messages, + out_updates=out_updates, + peer_state=peer_state, + remote_addr=remote_addr, + remote_as=remote_as, + remote_id=remote_id, + local_as=local_as, + local_id=local_id, + ) + if bgp_peer: + peer_table.update(bgp_peer) + return peer_table + + +def discovery_bgp_peer(params, section: Dict[str, BgpPeer]) -> DiscoveryResult: + _item_parts = [ + 'remote_address', + 'remote_as', + 'address_family', + 'routing_instance', + ] + for key in section.keys(): + parameters = {'internal_item': key} + item = '' + for item_part in _item_parts: + if item_part not in params['build_item']: + item += f'{section[key].item[item_part]} ' + item = item.strip(' ') + yield Service(item=item, parameters=parameters) + + +def bgp_peer_base_info(peer: BgpPeer) -> Generator: + for message, value in [ + ('Local AS:', peer.local_as), + ('Local address:', peer.local_addr), + ('Local identifier:', peer.local_id), + ('Remote AS:', peer.remote_as), + ('Remote address:', peer.remote_addr), + ('Remote identifier:', peer.remote_id), + ]: + if value is not None: + yield Result(state=State.OK, notice=f'{message} {value}') + + +def _rewrite_ruleset_needed(params: dict) -> bool: + for param, value in params.items(): + match param: + case 'accepted_prefixes_lower_levels' | 'accepted_prefixes_upper_levels' | 'minuptime': + if isinstance(value, tuple) and len(value) == 2 \ + and isinstance(value[0], int) and isinstance(value[1], int): + return True + case 'neighborstate': + for key in value.keys(): + if key in '123456': + return True + case 'peer_list': + if isinstance(value[0], tuple): + return True + return False + + +def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult: + if not params.get('internal_item'): + yield Result( + state=State.WARN, + summary='This service is using old parameters (see details)', + details='This service is using old parameters. To refresh the parameters you need to do a "Tabula rasa" ' + '(Discover services -> Remove all and find new)', + ) + else: + item = params['internal_item'] + + if _rewrite_ruleset_needed(params): + yield Result(state=State.WARN, + summary='BGP ruleset needs to be opened and saved to migrate the ruleset to ruleset APIv1!') + return + + neighbor_state = { + '1': 2, # idle + '2': 1, # connect + '3': 1, # active + '4': 1, # opensent + '5': 1, # openconfirm + '6': 0, # established + } + + neighbor_state.update(params['neighborstate']) + peer_not_found_state = params['peernotfound'] + + for entry in params.get('peer_list', []): + if item == entry['bgp_peer']: + if entry.get('bgp_peer_alias'): + yield Result(state=State.OK, summary=f'[{entry["bgp_peer_alias"]}]', details=' ') + peer_not_found_state = entry.get('state_not_found', params['peernotfound']) + + # get item data + try: + peer = section[item] + except KeyError: + yield Result(state=State(peer_not_found_state), summary='Item not found in SNMP data') + return + + if peer.description: + yield Result(state=State.OK, summary=f'[{peer.description}]', details=' ') + + if peer.admin_state == 1: # shutdown + yield Result(state=State(params['admindown']), summary=f'Admin state: {bgp_adminstate(peer.admin_state)}') + yield from bgp_peer_base_info(peer) + return + + yield Result( + state=State(neighbor_state.get(str(peer.peer_state))), + notice=f'Peer state: {bgp_peerstate(peer.peer_state)}' + ) + + if peer.fsm_established_time: # always yield fsm_established time + yield Metric( + name=f'bgp_peer_fsmestablishedtime', + value=peer.fsm_established_time, + boundaries=(0, None) + ) + + if not peer.peer_state == 6: # not established + yield from bgp_peer_base_info(peer) + return + + if peer.fsm_established_time: # if peer is established use fsm_established_time as uptime + yield from check_levels( + value=peer.fsm_established_time, + label='Uptime', + levels_upper=('no_levels', None), + levels_lower=params['minuptime'], + render_func=render.timespan, + metric_name='bgp_peer_fsmestablishedtime', + ) + + if peer.peer_unavail_reason != 0: # huawei peer unavailable state + yield Result(state=State.CRIT, notice=F'Peer unavailable reason: {peer.peer_unavail_reason_str}') + + if peer.device_admin_limit and peer.prefix_admin_limit is None: + yield Result( + state=State(params['noprefixlimit']), + notice='Prefix limit/warn threshold not configured on the device.', + ) + + if peer.accepted_prefixes is not None and peer.peer_state == 6: # peer established and prefixes accepted + if peer.prefix_admin_limit is None or peer.prefix_threshold is None: + levels_upper = None + else: + # use float (100.0) to get xx.xx in division + levels_upper = ( + 'fixed', (int(peer.prefix_admin_limit / 100.0 * peer.prefix_threshold), peer.prefix_admin_limit) + ) + + yield from check_levels( + value=peer.accepted_prefixes, + metric_name='bgp_peer_acceptedprefixes', + levels_upper=params.get('accepted_prefixes_upper_levels', levels_upper), + levels_lower=params.get('accepted_prefixes_lower_levels'), + label='Prefixes accepted', + render_func=lambda v: f'{v}' + ) + + now_time = time.time() + value_store = get_value_store() + + for key, value in peer.metric_rate: + try: + value = get_rate(value_store, f'{key}', now_time, value, raise_overflow=True) + except GetRateError: + continue + yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None)) + + for key, value in peer.metric_count: + if value is not None: + yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None)) + + yield from bgp_peer_base_info(peer) + + +snmp_section_bgp_peer = SNMPSection( + name='bgp_peer', + parse_function=parse_bgp_peer, + fetch=[ + SNMPTree( + base='.1.3.6.1.2.1.15.3.1', # BGP4-MIB::BgpPeerEntry + oids=[ + '7', # bgpPeerRemoteAddr + '1', # bgpPeerIdentifier + '2', # bgpPeerState + '3', # bgpPeerAdminStatus + '5', # bgpPeerLocalAddr + '9', # bgpPeerRemoteAs + '10', # bgpPeerInUpdates + '11', # bgpPeerOutUpdates + '12', # bgpPeerInTotalMessages + '13', # bgpPeerOutTotalMessages + '15', # bgpPeerFsmEstablishedTransitions + '16', # bgpPeerFsmEstablishedTime + '24', # bgpPeerInUpdateElapsedTime + ]), + SNMPTree( + base='.1.3.6.1.2.1.15', # BGP4-MIB + oids=[ + '2', # bgpLocalAs + '4', # bgpIdentifier (local) + ]) + ], + detect=exists('.1.3.6.1.2.1.15.3.1.1.*') +) + +check_plugin_bgp_peer = CheckPlugin( + name='bgp_peer', + service_name='BGP peer %s', + discovery_function=discovery_bgp_peer, + discovery_default_parameters={ + 'build_item': [ + # 'remote_address', + 'remote_as', + # 'address_family', + # 'routing_instance', + ] + }, + discovery_ruleset_name='discovery_bgp_peer', + check_function=check_bgp_peer, + check_default_parameters={ + 'minuptime': ('fixed', (7200, 3600)), + 'peer_list': [], + 'peernotfound': 2, + 'admindown': 1, + 'noprefixlimit': 1, + 'neighborstate': { + 'idle': 2, # ........1 + 'connect': 1, # .....2 + 'active': 1, # ......3 + 'open_sent': 1, # ...4 + 'open_confirm': 1, # 5 + 'established': 0, # .6 + }, + }, + check_ruleset_name='bgp_peer', +) diff --git a/source/agent_based/inv_bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py similarity index 94% rename from source/agent_based/inv_bgp_peer.py rename to source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py index 869b337..ce96d07 100644 --- a/source/agent_based/inv_bgp_peer.py +++ b/source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py @@ -18,18 +18,18 @@ import time from typing import List -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + InventoryPlugin, + InventoryResult, OIDBytes, + SNMPSection, SNMPTree, + StringByteTable, TableRow, exists, - register, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - InventoryResult, - StringByteTable, ) -from cmk.base.plugins.agent_based.utils.bgp_peer import ( + +from cmk_addons.plugins.bgp_peer.lib.bgp_peer import ( BgpWhois, InvBgpPeer, bgp_error_as_string, @@ -132,7 +132,7 @@ def inventory_bgp_peers(params, section) -> InventoryResult: del whois -register.snmp_section( +snmp_section_inv_bgp_peer = SNMPSection( name='inv_bgp_peer', parse_function=parse_inv_bgp_peer, fetch=[ @@ -160,7 +160,7 @@ register.snmp_section( detect=exists('.1.3.6.1.2.1.15.3.1.1.*') ) -register.inventory_plugin( +inventory_plugin_inv_bgp_peer = InventoryPlugin( name='inv_bgp_peer', inventory_function=inventory_bgp_peers, inventory_default_parameters={ diff --git a/source/checkman/bgp_peer b/source/cmk_addons_plugins/bgp_peer/checkman/bgp_peer similarity index 100% rename from source/checkman/bgp_peer rename to source/cmk_addons_plugins/bgp_peer/checkman/bgp_peer diff --git a/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py new file mode 100644 index 0000000..b40ffd9 --- /dev/null +++ b/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py @@ -0,0 +1,290 @@ +#!/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 : 2021-08-21 +# +# BGP Peer metrics plugin +# +# 2024-05-17: fixed typo in range mac -> max (crashed graphs in cmk 2.3.) +# 2025-03-29: rewritten for graphing API v1 + +from cmk.graphing.v1 import Title, graphs, metrics, perfometers + + +UNIT_COUNTER = metrics.Unit(metrics.DecimalNotation(''), metrics.StrictPrecision(2)) +UNIT_PER_SECOND = metrics.Unit(metrics.DecimalNotation('/s')) +UNIT_TIME = metrics.Unit(metrics.TimeNotation()) + +metric_bgp_peer_accepted_prefixes = metrics.Metric( + name='bgp_peer_acceptedprefixes', + title=Title("Prefixes accepted"), + unit=UNIT_COUNTER, + color=metrics.Color.DARK_PURPLE, +) + +metric_bgp_peer_advertised_prefixes = metrics.Metric( + name='bgp_peer_advertisedprefixes', + title=Title("Prefixes advertised"), + unit=UNIT_PER_SECOND, + color=metrics.Color.CYAN, +) + +metric_bgp_peer_denied_prefixes = metrics.Metric( + name='bgp_peer_deniedprefixes', + title=Title("Prefixes denied"), + unit=UNIT_PER_SECOND, + color=metrics.Color.YELLOW, +) + +metric_bgp_peer_fsm_established_time = metrics.Metric( + name='bgp_peer_fsmestablishedtime', + title=Title("FSM last change"), + unit=UNIT_TIME, + color=metrics.Color.GREEN, +) + +metric_bgp_peer_fsm_established_transitions = metrics.Metric( + name='bgp_peer_fsmestablishedtransitions', + title=Title("FSM transitions"), + unit=UNIT_COUNTER, + color=metrics.Color.YELLOW, +) + +metric_bgp_peer_in_total_messages = metrics.Metric( + name='bgp_peer_intotalmessages', + title=Title("Messages received"), + unit=UNIT_PER_SECOND, + color=metrics.Color.DARK_BLUE, +) + +metric_bgp_peer_in_update_elapsed_time = metrics.Metric( + name='bgp_peer_inupdateelapsedtime', + title=Title("Last update received"), + unit=UNIT_TIME, + color=metrics.Color.DARK_BLUE, +) + +metric_bgp_peer_in_updates = metrics.Metric( + name='bgp_peer_inupdates', + title=Title("Updates received"), + unit=UNIT_PER_SECOND, + color=metrics.Color.YELLOW, +) + +metric_bgp_peer_out_total_messages = metrics.Metric( + name='bgp_peer_outtotalmessages', + title=Title("Messages send"), + unit=UNIT_PER_SECOND, + color=metrics.Color.DARK_PINK, +) + +metric_bgp_peer_out_updates = metrics.Metric( + name='bgp_peer_outupdates', + title=Title("Updates send"), + unit=UNIT_PER_SECOND, + color=metrics.Color.CYAN, +) + +metric_bgp_peer_suppressed_prefixes = metrics.Metric( + name='bgp_peer_suppressedprefixes', + title=Title("Prefixes suppressed"), + unit=UNIT_PER_SECOND, + color=metrics.Color.DARK_PINK, +) + +metric_bgp_peer_withdrawn_prefixes = metrics.Metric( + name='bgp_peer_withdrawnprefixes', + title=Title("Prefixes withdrawn"), + unit=UNIT_PER_SECOND, + color=metrics.Color.BLUE, +) + +# Juniper specific metrics +metric_bgp_peer_in_prefixes = metrics.Metric( + name='bgp_peer_in_prefixes', + title=Title("Prefixes in"), + unit=UNIT_COUNTER, + color=metrics.Color.DARK_PURPLE, +) + +metric_bgp_peer_in_prefixes_rejected = metrics.Metric( + name='bgp_peer_in_prefixes_rejected', + title=Title("Prefixes in rejected"), + unit=UNIT_COUNTER, + color=metrics.Color.YELLOW, +) + +metric_bgp_peer_in_prefixes_active = metrics.Metric( + name='bgp_peer_in_prefixes_active', + title=Title("Prefixes in active"), + unit=UNIT_COUNTER, + color=metrics.Color.CYAN, +) + +metric_bgp_peer_out_prefixes = metrics.Metric( + name='bgp_peer_out_prefixes', + title=Title("Prefixes out"), + unit=UNIT_COUNTER, + color=metrics.Color.BLUE, +) + +# Huawei specific metrics +metric_bgp_peer_prefix_adv_counter = metrics.Metric( + name='bgp_peer_prefixadvcounter', + title=Title("Prefixes advertised"), + unit=UNIT_COUNTER, + color=metrics.Color.DARK_BLUE, +) + +metric_bgp_peer_prefix_active_counter = metrics.Metric( + name='bgp_peer_prefixactivecounter', + title=Title("Prefixes active"), + unit=UNIT_COUNTER, + color=metrics.Color.CYAN, +) + +metric_bgp_peer_prefix_rcv_counter = metrics.Metric( + name='bgp_peer_prefixrcvcounter', + title=Title("Prefixes received"), + unit=UNIT_COUNTER, + color=metrics.Color.DARK_PURPLE, +) + +graph_bgp_peer_fms_transitions_last_change = graphs.Graph( + name='bgp_peer.fms_transitions_last_change', + title=Title("FSM established last change"), + minimal_range=graphs.MinimalRange(0, metrics.MaximumOf('bgp_peer_fsmestablishedtime', metrics.Color.GRAY, ), ), + compound_lines=['bgp_peer_fsmestablishedtime'], +) + +graph_bgp_peer_prefixes_accepted = graphs.Graph( + name='bgp_peer.prefixes_accepted', + title=Title("Accepted Prefixes"), + minimal_range=graphs.MinimalRange(0, metrics.MaximumOf('bgp_peer_acceptedprefixes', metrics.Color.GRAY, ), ), + compound_lines=['bgp_peer_acceptedprefixes'], + simple_lines=[ + metrics.CriticalOf('bgp_peer_acceptedprefixes'), + metrics.WarningOf('bgp_peer_acceptedprefixes'), + ], +) + +graph_bgp_peer_prefixes_per_second = graphs.Graph( + name='bgp_peer.prefixes_per_second', + title=Title("Prefixes/s"), + simple_lines=[ + 'bgp_peer_withdrawnprefixes', + 'bgp_peer_suppressedprefixes', + 'bgp_peer_deniedprefixes', + 'bgp_peer_advertisedprefixes', + ], + optional=[ + 'bgp_peer_withdrawnprefixes', + 'bgp_peer_suppressedprefixes', + 'bgp_peer_deniedprefixes', + 'bgp_peer_advertisedprefixes', + ], +) + +graph_bgp_peer_updates_in_out = graphs.Bidirectional( + name='bgp_peer.updates_in_out', + title=Title("Updates"), + lower=graphs.Graph( + name='bgp_peer.updates_in_out_lower', + title=Title("Updates"), + compound_lines=['bgp_peer_outupdates'], + ), + upper=graphs.Graph( + name='bgp_peer.updates_in_out_upper', + title=Title("Updates"), + compound_lines=['bgp_peer_inupdates'], + ), +) + +graph_bgp_peer_messages_in_out = graphs.Bidirectional( + name='bgp_peer.messages_in_out', + title=Title("Total messages"), + lower=graphs.Graph( + name='bgp_peer.messages_in_out_lower', + title=Title("Total messages"), + compound_lines=['bgp_peer_outtotalmessages'], + ), + upper=graphs.Graph( + name='bgp_peer.messages_in_out_upper', + title=Title("Total messages"), + compound_lines=['bgp_peer_intotalmessages'], + ), +) + +graph_bgp_peer_fms_transitions_from_to = graphs.Graph( + name='bgp_peer.fms_transitions_from_to', + title=Title("FSM transitions from/to established"), + minimal_range=graphs.MinimalRange( + 0, metrics.MaximumOf('bgp_peer_fsmestablishedtransitions', metrics.Color.GRAY, ), + ), + compound_lines=['bgp_peer_fsmestablishedtransitions'], +) + +graph_bgp_peer_time_since_last_update = graphs.Graph( + name='bgp_peer.time_since_last_update', + title=Title("Time since last update received"), + minimal_range=graphs.MinimalRange( + 0, metrics.MaximumOf('bgp_peer_inupdateelapsedtime', metrics.Color.GRAY, ), + ), + compound_lines=['bgp_peer_inupdateelapsedtime'], +) + +# juniper prefixes +graph_bgp_peer_juniper_prefixes = graphs.Bidirectional( + name='bgp_peer.juniper_prefixes', + title=Title("Prefixes in/out"), + lower=graphs.Graph( + name='bgp_peer.juniper_prefixes_lower', + title=Title("Prefixes in/out"), + simple_lines=['bgp_peer_out_prefixes'], + ), + upper=graphs.Graph( + name='bgp_peer.juniper_prefixes_upper', + title=Title("Prefixes in/out"), + simple_lines=[ + 'bgp_peer_in_prefixes_rejected', + 'bgp_peer_in_prefixes_active', + 'bgp_peer_in_prefixes', + ], + ), +) + +# huawei prefixes +graph_huawei_bgp_peer_counter = graphs.Graph( + name='huawei_bgp_peer_counter', + title=Title("BGP prefix counter"), + simple_lines=[ + 'bgp_peer_prefixrcvcounter', + 'bgp_peer_prefixactivecounter', + 'bgp_peer_prefixadvcounter', + ], +) + + +perfometer_bgp_peer_accepted_prefixes = perfometers.Stacked( + name='bgp_peer_stacked', + upper=perfometers.Perfometer( + name='bgp_peer_fsmestablishedtime', + segments=['bgp_peer_fsmestablishedtime'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(2592000)) # ome month + ), + lower=perfometers.Perfometer( + name='bgp_peer_acceptedprefixes', + segments=['bgp_peer_acceptedprefixes'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(500000)) + ) +) + +perfometer_bgp_peer_fsm_established_time = perfometers.Perfometer( + name='bgp_peer_fsmestablishedtime', + segments=['bgp_peer_fsmestablishedtime'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(2592000)) # ome month +) \ No newline at end of file diff --git a/source/agent_based/utils/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/lib/bgp_peer.py similarity index 100% rename from source/agent_based/utils/bgp_peer.py rename to source/cmk_addons_plugins/bgp_peer/lib/bgp_peer.py diff --git a/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py new file mode 100644 index 0000000..fe1df4b --- /dev/null +++ b/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py @@ -0,0 +1,336 @@ +#!/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 : 2017-12-25 +# +# Check_MK bgp_peers WATO plugin +# +# 2021-03-27: rewrite for CMK 2.0 +# 2021-08-21: modified for bgp_peer plugin (from cisco_bgp_peer) +# 2021-08-29: removed htmloutput and infotext_values option +# 2022-04-02: added bgp neighbour states +# 2022-04-29: added upper/lower prefix limit +# 2022-05-09: added discovery rule set +# 2022-05-11: added remote_as to build_item +# 2022-09-05: added internal_item to avoid warnings on cmk updates (THX to Jay2k1 for reporting the issue) +# 2023-06-11: moved wato file from ~local/lib/check_mk/gui/plugins/wato +# to ~/local/lib/check_mk/gui/plugins/wato/check_parameters to override the build in wato plugin +# 2024-06-15: fixed typo (no not) +# +# Known issue: the override of the build in wato plugin will break the build in aritsa bgp peer plugin +from click import help_option +from cmk.rulesets.v1 import Help, Label, Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + InputHint, + Integer, + LevelDirection, + LevelsType, + List, + MultipleChoice, + MultipleChoiceElement, + ServiceState, + SimpleLevels, + String, + TimeMagnitude, + TimeSpan, + migrate_to_integer_simple_levels, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, DiscoveryParameters, Topic, HostAndItemCondition +from cmk.rulesets.v1.form_specs.validators import NumberInRange, Message, LengthInRange + +item_parts = [ + # MultipleChoiceElement(name='remote_address', title=Title('Peer remote address')), + MultipleChoiceElement(name='remote_as', title=Title('Remote AS')), + MultipleChoiceElement(name='address_family', title=Title('Address family')), + MultipleChoiceElement(name='routing_instance', title=Title('Routing instance/VRF')), +] + +def _migrate_to_float(value: object) -> float: + if isinstance(value, int | float): + return float(value) + + raise TypeError(value) + +def _migrate_bgp_peer_list(value: object) -> list: + if not isinstance(value, list): + raise TypeError(value) + + peer_list = [] + + for entry in value: + if isinstance(entry, tuple): + bgp_peer, bgp_peer_alias, state_not_found = entry + peer_list.append({ + 'bgp_peer': bgp_peer, + 'bgp_peer_alias': bgp_peer_alias, + 'state_not_found': state_not_found + }) + else: + peer_list.append(entry) + + return peer_list + + +def _migrate_neighbor_state(value: object) -> dict: + if not isinstance(value, dict): + raise TypeError(value) + + state_map = {} + + for state, value in value.items(): + match state: + case '1': + state_map['idle'] = value + case '2': + state_map['connect'] = value + case '3': + state_map['active'] = value + case '4': + state_map['open_sent'] = value + case '5': + state_map['open_confirm'] = value + case '6': + state_map['established'] = value + case _: + state_map[state] = value + + return state_map + +def _parameter_form_check_bgp_peer(): + return Dictionary( + elements={ + 'minuptime': DictElement( + parameter_form=SimpleLevels( + title=Title('Minimum uptime for peer'), + help_text=Help('Set the time in seconds, a peer must be up before the peer is considered sable.'), + prefill_levels_type=DefaultValue(LevelsType.FIXED), + prefill_fixed_levels=InputHint((7200, 3600)), + level_direction=LevelDirection.LOWER, + migrate=migrate_to_integer_simple_levels, + form_spec_template=TimeSpan( + displayed_magnitudes=[TimeMagnitude.SECOND, TimeMagnitude.MINUTE, TimeMagnitude.HOUR], + custom_validate=(NumberInRange(min_value=0),), + migrate=_migrate_to_float, + ))), + 'accepted_prefixes_upper_levels': DictElement( + parameter_form=SimpleLevels( + title=Title('Accepted prefixes upper levels'), + help_text=Help('The values from WATO are preferred to the values from the device.'), + prefill_levels_type=DefaultValue(LevelsType.FIXED), + prefill_fixed_levels=InputHint((0, 0)), + level_direction=LevelDirection.UPPER, + migrate=migrate_to_integer_simple_levels, + form_spec_template=Integer( + unit_symbol='prefixes', + custom_validate=(NumberInRange(min_value=0, ),) + ))), + 'accepted_prefixes_lower_levels': DictElement( + parameter_form=SimpleLevels( + title=Title('Accepted prefixes lower levels'), + help_text=Help('The values from WATO are preferred to the values from the device.'), + prefill_levels_type=DefaultValue(LevelsType.FIXED), + prefill_fixed_levels=InputHint((0, 0)), + level_direction=LevelDirection.LOWER, + migrate=migrate_to_integer_simple_levels, + form_spec_template=Integer( + unit_symbol='prefixes', + custom_validate=(NumberInRange(min_value=0, ),), + ))), + 'peernotfound': DictElement( + parameter_form=ServiceState( + title=Title('State if peer is not found.'), + help_text=Help('Default monitoring state if the peer is not found in the SNMP data'), + prefill=DefaultValue(2) + )), + 'admindown': DictElement( + parameter_form=ServiceState( + prefill=DefaultValue(1), + title=Title('State if peer is admin shutdown.'), + help_text=Help('Monitoring state if the peer is admin shutdown') + )), + + 'neighborstate': DictElement( + parameter_form=Dictionary( + title=Title('State to report for BGP neighbor state'), + help_text=Help('Map each BGP state to a CheckMK monitoring state'), + migrate=_migrate_neighbor_state, + elements={ + 'idle': DictElement( + parameter_form=ServiceState( + title=Title('1 - idle'), + help_text=Help( + 'This is the first stage of the BGP FSM. BGP detects a start event, tries to initiate a ' + 'TCP connection to the BGP peer, and also listens for a new connect from a peer router. ' + 'If an error causes BGP to go back to the Idle state for a second time, the ' + 'ConnectRetryTimer is set to 60 seconds and must decrement to zero before the connection ' + 'is initiated again. Further failures to leave the Idle state result in the ' + 'ConnectRetryTimer doubling in length from the previous time. ' + 'Default monitoring state is "CRIT"' + ), + prefill=DefaultValue(2) + )), + 'connect': DictElement( + parameter_form=ServiceState( + title=Title('2 - connect'), + help_text=Help( + 'In this state, BGP initiates the TCP connection. If the 3-way TCP handshake completes, ' + 'the established BGP Session BGP process resets the ConnectRetryTimer and sends the Open ' + 'message to the neighbor, and then changes to the OpenSent State.' + 'Default monitoring state is "WARN"'), + prefill=DefaultValue(1), + )), + 'active': DictElement( + parameter_form=ServiceState( + title=Title('3 - active'), + help_text=Help( + 'In this state, BGP starts a new 3-way TCP handshake. If a connection is established, ' + 'an Open message is sent, the Hold Timer is set to 4 minutes, and the state moves to ' + 'OpenSent. If this attempt for TCP connection fails, the state moves back to the Connect ' + 'state and resets the ConnectRetryTimer. ' + 'Default monitoring state is "WARN"'), + prefill=DefaultValue(1), + )), + 'open_sent': DictElement( + parameter_form=ServiceState( + title=Title('4 - opensent'), + help_text=Help( + 'In this state, an Open message has been sent from the originating router and is awaiting ' + 'an Open message from the other router. After the originating router receives the OPEN ' + 'message from the other router, both OPEN messages are checked for errors. If the Open ' + 'messages do not have any errors, the Hold Time is negotiated (using the lower value), ' + 'and a KEEPALIVE message is sent (assuming the value is not set to zero). The connection ' + 'state is then moved to OpenConfirm. If an error is found in the OPEN message, a ' + 'Notification message is sent, and the state is moved back to Idle.' + ' Default monitoring state is "WARN"'), + prefill=DefaultValue(1), + )), + 'open_confirm': DictElement( + parameter_form=ServiceState( + title=Title('5 - openconfirm'), + help_text=Help( + 'In this state, BGP waits for a Keepalive or Notification message. Upon receipt of a ' + 'neighbor’s Keepalive, the state is moved to Established. If the hold timer expires, a ' + 'stop event occurs, or a Notification message is received, and the state is moved to ' + 'Idle. ' + 'Default monitoring state is "WARN"'), + prefill=DefaultValue(1), + )), + 'established': DictElement( + parameter_form=ServiceState( + title=Title('6 - established'), + help_text=Help( + 'In this state, the BGP session is established. BGP neighbors exchange routes via Update ' + 'messages. As Update and Keepalive messages are received, the Hold Timer is reset. If the ' + 'Hold Timer expires, an error is detected and BGP moves the neighbor back to the Idle ' + 'state. ' + 'Default monitoring state is "OK"'), + prefill=DefaultValue(0), + )), + } + )), + + 'noprefixlimit': DictElement( + parameter_form=ServiceState( + title=Title('State if no admin prefix limit/warn threshold is configured.'), + help_text=Help( + 'The admin prefix limit and warn threshold needs to be configured on the device. ' + 'For example: "neighbor 172.17.10.10 maximum-prefix 10000 80". The threshold is in percentage ' + 'of the prefix limit.' + ), + prefill=DefaultValue(1), + )), + 'peer_list': DictElement( + parameter_form=List( + title=Title('BGP Peers'), + add_element_label=Label('Add BGP peer'), + remove_element_label=Label('remove BGP peer'), + no_element_label=Label('Add at least one BGP peer'), + migrate=_migrate_bgp_peer_list, + element_template=Dictionary( + elements={ + 'bgp_peer': DictElement( + required=True, + parameter_form=String( + title=Title('BGP peer'), + help_text=Help( + 'The configured value must match a BGP item reported by the monitored ' + 'device. For example: "10.194.115.98" or "2A10:1CD0:1020:135::20 IPv6 Unicast"' + ), + custom_validate=(LengthInRange(min_value=1, ),), + )), + 'bgp_peer_alias': DictElement( + parameter_form=String( + title=Title('BGP Peer Alias'), + help_text=Help( + 'You can configure an individual alias here for the BGP peer matching ' + 'the text configured in the "BGP Peer IP-address" field. The alias will ' + 'be shown in the check info' + ), + custom_validate=(LengthInRange(min_value=1, ),), + )), + 'state_not_found': DictElement( + required=True, + parameter_form=ServiceState( + title=Title('State if not found'), + help_text=Help( + 'You can configure an individual state if the BGP peer matching the text ' + 'configured in the "BGP Peer IP-address" field is not found' + ), + prefill=DefaultValue(2), + )), + } + ) + )), + 'internal_item': DictElement( + render_only=True, + parameter_form=MultipleChoice( + title=Title('Information not to use in the item name'), + elements=item_parts, + )), + } + ) + + +rule_spec_check_bgp_peer = CheckParameters( + name='bgp_peer', + parameter_form=_parameter_form_check_bgp_peer, + title=Title('BGP Peer'), + topic=Topic.NETWORKING, + condition=HostAndItemCondition(item_title=Title('BGP peer')), +) + + +def _parameter_form_discovery_bgp_peer(): + return Dictionary( + elements={ + 'build_item': DictElement( + parameter_form=MultipleChoice( + title=Title('Information not to use in the item name'), + help_text=Help( + 'The Peer remote address is always used as the item name. By default the check will add the ' + 'address-family and the routing instance/VRF if available. You can decide to not use these ' + 'additional information in the item name. Do so only if your peers have only one address-' + 'family configured and you don\'t have the same peer remote address in different routing ' + 'instances/VRFs configured.' + ), + elements=item_parts, + prefill=DefaultValue(['remote_as']) + ) + ) + } + ) + + +rule_spec_discovery_bgp_beer = DiscoveryParameters( + title=Title('BGP peer'), + topic=Topic.NETWORKING, + name='discovery_bgp_peer', + parameter_form=_parameter_form_discovery_bgp_peer, +) diff --git a/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py new file mode 100644 index 0000000..f1d30bc --- /dev/null +++ b/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2022-04-24 +# +# 2022-04-24: added option for BGP down time +# added option to remove some columns from inventory +# 2022-04-28: added Whois options +# 2023-06-12: moved wato file from ~local/lib/check_mk/gui/plugins/wato +# to ~/local/lib/check_mk/gui/plugins/wato/check_parameters +# 2025-03-29: rewritten for rulesets API v1 + +from cmk.rulesets.v1 import Title, Help +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + Integer, + MultipleChoice, + MultipleChoiceElement, + SingleChoice, + SingleChoiceElement, + TimeMagnitude, + TimeSpan, +) +from cmk.rulesets.v1.rule_specs import InventoryParameters, Topic +from cmk.rulesets.v1.form_specs.validators import NumberInRange, Message + +def _migrate_to_float(value: object) -> float: + if isinstance(value, int | float): + return float(value) + + raise TypeError(value) + + +def _parameter_form_inv_bgp_peer(): + remove_columns = [ + # MultipleChoiceElement(name='remote_as', title=Title('Remote AS')), + # MultipleChoiceElement(name='remote_id', title=Title('Remote ID')), + # MultipleChoiceElement(name='local_addr', title=Title('Local address')), + # MultipleChoiceElement(name='local_as', title=Title('Local AS')), + # MultipleChoiceElement(name='local_id', title=Title('Local ID')), + # MultipleChoiceElement(name='prev_state', title=Title('Previous state')), + MultipleChoiceElement(name='address_family', title=Title('Address family')), + MultipleChoiceElement(name='last_error', title=Title('Last error')), + MultipleChoiceElement(name='last_error_code', title=Title('Last error code')), + MultipleChoiceElement(name='as_name', title=Title('Remote AS name')), + MultipleChoiceElement(name='as_org_name', title=Title('Remote AS Org Name')), + MultipleChoiceElement(name='bgp_type', title=Title('Type')), + MultipleChoiceElement(name='version', title=Title('Version')), + ] + + rir_s = [ + SingleChoiceElement(name='afrinic', title=Title('AFRINIC (https://rdap.afrinic.net/rdap)')), + SingleChoiceElement(name='apnic', title=Title('APNIC (https://rdap.apnic.net)')), + SingleChoiceElement(name='arin', title=Title('ARIN (https://rdap.arin.net/registry)')), + SingleChoiceElement(name='ripe', title=Title('RIPE (https://rdap.db.ripe.net)')), + SingleChoiceElement(name='lacnic', title=Title('LACNIC (https://rdap.apnic.net)')), + ] + + return Dictionary( + elements={ + 'not_in_service_time': DictElement( + parameter_form=TimeSpan( + title=Title('Time peer is not up until considered not in service'), + displayed_magnitudes=[TimeMagnitude.MINUTE, TimeMagnitude.HOUR, TimeMagnitude.DAY], + prefill=DefaultValue(2592000.0), # 30 days in seconds + migrate=_migrate_to_float, + )), + 'remove_columns': DictElement( + parameter_form=MultipleChoice( + title=Title('List of columns to remove'), + help_text=Help('Information to remove from inventory'), + elements=remove_columns, + )), + 'whois_enable': DictElement( + parameter_form=Dictionary( + title=Title('Add whois data to the inventory'), + help_text=Help( + 'The whois data will be fetched via RDAP from the registries. For this the the plugin tries to' + 'find the best registry via the RDAP bootstrap data from https://data.iana.org/rdap/asn.json.' + 'The query it self will go to the found registry via http(s). Note: the request might be get ' + 'redirected if there a different authoritative registry for the ASn' + ), + elements={ + 'whois_rir': DictElement( + parameter_form=SingleChoice( + title=Title('Preferred RIR to fetch whois data'), + help_text=Help( + 'This registry will be used if the plugin can not determine the authoritative registry ' + 'based on the bootstrap data.' + ), + elements=rir_s, + prefill=DefaultValue('ripe'), + )), + 'whois_timeout': DictElement( + parameter_form=Integer( + custom_validate=( + NumberInRange( + min_value=1, + max_value=None, + error_msg=Message('The timeout must be greater than 0') + ), + ), + help_text=Help('The connection timeout for each whois request.'), + prefill=DefaultValue(5), + title=Title('Timeout for connections to RIRs'), + unit_symbol='seconds', + )), + } + ) + ) + } + ) + + +rule_spec_inv_bgp_peer = InventoryParameters( + name='inv_bgp_peer', + parameter_form=_parameter_form_inv_bgp_peer, + title=Title('BGP peer'), + topic=Topic.NETWORKING, +) diff --git a/source/gui/metrics/bgp_peer.py b/source/gui/metrics/bgp_peer.py deleted file mode 100644 index a6f8bcf..0000000 --- a/source/gui/metrics/bgp_peer.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/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 : 2021-08-21 -# -# BGP Peer metrics plugin -# -# 2024-05-17: fixed typo in range mac -> max (crashed graphs in cmk 2.3.) -# -from cmk.gui.i18n import _ - -from cmk.gui.plugins.metrics.utils import ( - metric_info, - graph_info, - perfometer_info -) - -##################################################################################################################### -# -# define metrics for bgp peer perfdata -# -##################################################################################################################### - -metric_info['bgp_peer_inupdates'] = { - 'title': _('Updates received'), - 'unit': '1/s', - 'color': '22/a', -} -metric_info['bgp_peer_outupdates'] = { - 'title': _('Updates send'), - 'unit': '1/s', - 'color': '32/a', -} -metric_info['bgp_peer_intotalmessages'] = { - 'title': _('Messages received'), - 'unit': '1/s', - 'color': '42/a', -} -metric_info['bgp_peer_outtotalmessages'] = { - 'title': _('Messages send'), - 'unit': '1/s', - 'color': '13/a', -} -metric_info['bgp_peer_fsmestablishedtransitions'] = { - 'title': _('FSM transitions'), - 'unit': 'count', - 'color': '23/a', -} -metric_info['bgp_peer_fsmestablishedtime'] = { - 'title': _('FSM last change'), - 'unit': 's', - 'color': '26/a', -} -metric_info['bgp_peer_inupdateelapsedtime'] = { - 'title': _('Last update received'), - 'unit': 's', - 'color': '43/a', -} - -metric_info['bgp_peer_acceptedprefixes'] = { - 'title': _('Prefixes accepted'), - 'help': _('number of accepted prefixes'), - 'unit': 'count', - 'color': '11/a', -} -metric_info['bgp_peer_deniedprefixes'] = { - 'title': _('Prefixes denied'), - 'unit': '1/s', - 'color': '21/a', -} -metric_info['bgp_peer_advertisedprefixes'] = { - 'title': _('Prefixes advertised'), - 'unit': '1/s', - 'color': '31/a', -} -metric_info['bgp_peer_withdrawnprefixes'] = { - 'title': _('Prefixes withdrawn'), - 'unit': '1/s', - 'color': '41/a', -} -metric_info['bgp_peer_suppressedprefixes'] = { - 'title': _('Prefixes suppressed'), - 'unit': '1/s', - 'color': '12/a', -} - -# Juniper specific metrics -metric_info['bgp_peer_in_prefixes'] = { - 'title': _('Prefixes in'), - 'unit': 'count', - 'color': '11/a', -} -metric_info['bgp_peer_in_prefixes_rejected'] = { - 'title': _('Prefixes in rejected'), - 'unit': 'count', - 'color': '21/a', -} -metric_info['bgp_peer_in_prefixes_active'] = { - 'title': _('Prefixes in active'), - 'unit': 'count', - 'color': '31/a', -} -metric_info['bgp_peer_out_prefixes'] = { - 'title': _('Prefixes out'), - 'unit': 'count', - 'color': '41/a', -} - -# Hawei spezific metrics -metric_info['bgp_peer_prefixrcvcounter'] = { - 'title': _('Prefixes received'), - 'unit': 'count', - 'color': '11/a', -} -metric_info['bgp_peer_prefixactivecounter'] = { - 'title': _('Prefixes active'), - 'unit': 'count', - 'color': '33/a', -} -metric_info['bgp_peer_prefixadvcounter'] = { - 'title': _('Prefixes advertised'), - 'unit': 'count', - 'color': '43/a', -} - -###################################################################################################################### -# -# how to graph perdata for bgp peer -# -###################################################################################################################### - -graph_info['bgp_peer.fms_transitions_last_change'] = { - 'title': _('FSM established last change'), - 'metrics': [ - ('bgp_peer_fsmestablishedtime', 'area'), - ], - 'range': (0, 'bgp_peer_fsmestablishedtime:max'), -} - -graph_info['bgp_peer.prefixes_accepted'] = { - 'title': _('Accepted Prefixes'), - 'metrics': [ - ('bgp_peer_acceptedprefixes', 'area'), - ], - 'scalars': [ - ('bgp_peer_acceptedprefixes:crit', _('crit')), - ('bgp_peer_acceptedprefixes:warn', _('warn')), - ], - 'range': (0, 'bgp_peer_acceptedprefixes:max'), -} - -graph_info['bgp_peer.prefixes_per_second'] = { - 'title': _('Prefixes/s'), - 'metrics': [ - ('bgp_peer_withdrawnprefixes', 'line'), - ('bgp_peer_suppressedprefixes', 'line'), - ('bgp_peer_deniedprefixes', 'line'), - ('bgp_peer_advertisedprefixes', 'line'), - ], - 'optional_metrics': [ - 'bgp_peer_withdrawnprefixes', - 'bgp_peer_suppressedprefixes', - 'bgp_peer_deniedprefixes', - 'bgp_peer_advertisedprefixes', - ], -} - -graph_info['bgp_peer.updates_in_out'] = { - 'title': _('Updates'), - 'metrics': [ - ('bgp_peer_outupdates', '-area'), - ('bgp_peer_inupdates', 'area'), - ] -} - -graph_info['bgp_peer.messages_in_out'] = { - 'title': _('Total messages'), - 'metrics': [ - ('bgp_peer_outtotalmessages', '-area'), - ('bgp_peer_intotalmessages', 'area'), - ] -} - -graph_info['bgp_peer.fms_transitions_from_to'] = { - 'title': _('FSM transitions from/to established'), - 'metrics': [ - ('bgp_peer_fsmestablishedtransitions', 'area'), - ], - 'range': (0, 'bgp_peer_fsmestablishedtransitions:max'), -} - - -graph_info['bgp_peer.time_since_last_update'] = { - 'title': _('Time since last update received'), - 'metrics': [ - ('bgp_peer_inupdateelapsedtime', 'area'), - ], - 'range': (0, 'bgp_peer_inupdateelapsedtime:max'), -} - -# juniper prefixes -graph_info['bgp_peer.juniper_prefixes'] = { - 'title': _('Prefixes in/out'), - 'metrics': [ - ('bgp_peer_out_prefixes', '-line'), - ('bgp_peer_in_prefixes_rejected', 'line'), - ('bgp_peer_in_prefixes_active', 'line'), - ('bgp_peer_in_prefixes', 'line'), - ], -} - -# huawei prefixes -graph_info['huawei_bgp_peer_counter'] = { - 'title': _('BGP prefix counter'), - 'metrics': [ - ('bgp_peer_prefixrcvcounter', 'line'), - ('bgp_peer_prefixactivecounter', 'line'), - ('bgp_peer_prefixadvcounter', 'line'), - ] -} - - -###################################################################################################################### -# -# define perf-o-meter for bgp peer uptime + prefixes accepted/advertised -# -###################################################################################################################### - -perfometer_info.append(('stacked', [ - { - 'type': 'logarithmic', - 'metric': 'bgp_peer_fsmestablishedtime', - 'half_value': 2592000.0, # ome month - 'exponent': 2, - }, - { - 'type': 'logarithmic', - 'metric': 'bgp_peer_acceptedprefixes', - 'half_value': 500000.0, - 'exponent': 2, - } -])) - -perfometer_info.append({ - 'type': 'logarithmic', - 'metric': 'bgp_peer_fsmestablishedtime', - 'half_value': 2592000.0, # ome month - 'exponent': 2, -}) diff --git a/source/gui/wato/check_parameters/bgp_peer.py b/source/gui/wato/check_parameters/bgp_peer.py index 32744b0..812659d 100644 --- a/source/gui/wato/check_parameters/bgp_peer.py +++ b/source/gui/wato/check_parameters/bgp_peer.py @@ -1,256 +1,4 @@ #!/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 : 2017-12-25 -# -# Check_MK bgp_peers WATO plugin -# -# 2021-03-27: rewrite for CMK 2.0 -# 2021-08-21: modified for bgp_peer plugin (from cisco_bgp_peer) -# 2021-08-29: removed htmloutput and infotext_values option -# 2022-04-02: added bgp neighbour states -# 2022-04-29: added upper/lower prefix limit -# 2022-05-09: added discovery rule set -# 2022-05-11: added remote_as to build_item -# 2022-09-05: added internal_item to avoid warnings on cmk updates (THX to Jay2k1 for reporting the issue) -# 2023-06-11: moved wato file from ~local/lib/check_mk/gui/plugins/wato -# to ~/local/lib/check_mk/gui/plugins/wato/check_parameters to override the build in wato plugin -# 2024-06-15: fixed typo (no not) -# -# Known issue: the override of the build in wato plugin will break the build in aritsa bgp peer plugin -from cmk.gui.i18n import _ -from cmk.gui.valuespec import ( - Dictionary, - Integer, - TextAscii, - ListOf, - Tuple, - TextUnicode, - MonitoringState, - ListChoice, - TextInput, -) - -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, - HostRulespec, - RulespecGroupCheckParametersDiscovery, -) - - -def _parameter_valuespec_bgp_peer(): - return Dictionary( - elements=[ - ('minuptime', - Tuple( - title=_('Minimum uptime for peer'), - orientation='horizontal', - help=_('Set the time in seconds, a peer must be up before the peer is considered sable.'), - elements=[ - Integer(title=_('Warning below'), unit='seconds', default_value=7200, minvalue=0), - Integer(title=_('Critical below'), unit='seconds', default_value=3600, minvalue=0) - ], - )), - ('accepted_prefixes_upper_levels', - Tuple( - title=_('Accepted prefixes upper levels'), - help=_('The values from WATO are preferred to the values from the device.'), - orientation='horizontal', - elements=[ - Integer(title=_('Warning at'), minvalue=0, unit=_('prefixes'), size=5), - Integer(title=_('Critical at'), minvalue=0, unit=_('prefixes'), size=5), - ], - )), - ('accepted_prefixes_lower_levels', - Tuple( - title=_('Accepted prefixes lower levels'), - orientation='horizontal', - elements=[ - Integer(title=_('Warning below'), minvalue=0, unit=_('prefixes'), size=5), - Integer(title=_('Critical below'), minvalue=0, unit=_('prefixes'), size=5), - ], - )), - ('peernotfound', - MonitoringState( - default_value=2, - title=_('State if peer is not found.'), - help=_('Default monitoring state if the peer is not found in the SNMP data') - )), - ('admindown', - MonitoringState( - default_value=1, - title=_('State if peer is admin shutdown.'), - help=_('Monitoring state if the peer is admin shutdown') - )), - ('neighborstate', - Dictionary( - title=_('State to report for BGP neighbor state'), - help=_('Map each BGP state to a CheckMK monitoring state'), - elements=[ - ('1', - MonitoringState( - title=_('1 - idle'), - help=_( - 'This is the first stage of the BGP FSM. BGP detects a start event, tries to initiate a ' - 'TCP connection to the BGP peer, and also listens for a new connect from a peer router. ' - 'If an error causes BGP to go back to the Idle state for a second time, the ' - 'ConnectRetryTimer is set to 60 seconds and must decrement to zero before the connection ' - 'is initiated again. Further failures to leave the Idle state result in the ' - 'ConnectRetryTimer doubling in length from the previous time. ' - 'Default monitoring state is "CRIT"'), - default_value=2, - )), - ('2', - MonitoringState( - title=_('2 - connect'), - help=_( - 'In this state, BGP initiates the TCP connection. If the 3-way TCP handshake completes, ' - 'the established BGP Session BGP process resets the ConnectRetryTimer and sends the Open ' - 'message to the neighbor, and then changes to the OpenSent State.' - 'Default monitoring state is "WARN"'), - default_value=1, - )), - ('3', - MonitoringState( - title=_('3 - active'), - help=_('In this state, BGP starts a new 3-way TCP handshake. If a connection is established, ' - 'an Open message is sent, the Hold Timer is set to 4 minutes, and the state moves to ' - 'OpenSent. If this attempt for TCP connection fails, the state moves back to the Connect ' - 'state and resets the ConnectRetryTimer. ' - 'Default monitoring state is "WARN"'), - default_value=1, - )), - ('4', - MonitoringState( - title=_('4 - opensent'), - help=_( - 'In this state, an Open message has been sent from the originating router and is awaiting ' - 'an Open message from the other router. After the originating router receives the OPEN ' - 'message from the other router, both OPEN messages are checked for errors. If the Open ' - 'messages do not have any errors, the Hold Time is negotiated (using the lower value), ' - 'and a KEEPALIVE message is sent (assuming the value is not set to zero). The connection ' - 'state is then moved to OpenConfirm. If an error is found in the OPEN message, a ' - 'Notification message is sent, and the state is moved back to Idle.' - ' Default monitoring state is "WARN"'), - default_value=1, - )), - ('5', - MonitoringState( - title=_('5 - openconfirm'), - help=_('In this state, BGP waits for a Keepalive or Notification message. Upon receipt of a ' - 'neighbor’s Keepalive, the state is moved to Established. If the hold timer expires, a ' - 'stop event occurs, or a Notification message is received, and the state is moved to ' - 'Idle. ' - 'Default monitoring state is "WARN"'), - default_value=1, - )), - ('6', - MonitoringState( - title=_('6 - established'), - help=_( - 'In this state, the BGP session is established. BGP neighbors exchange routes via Update ' - 'messages. As Update and Keepalive messages are received, the Hold Timer is reset. If the ' - 'Hold Timer expires, an error is detected and BGP moves the neighbor back to the Idle ' - 'state. ' - 'Default monitoring state is "OK"'), - default_value=0, - )), - ])), - ('noprefixlimit', - MonitoringState( - default_value=1, - title=_('State if no admin prefix limit/warn threshold is configured.'), - help=_('The admin prefix limit and warn threshold needs to be configured on the device. ' - 'For example: "neighbor 172.17.10.10 maximum-prefix 10000 80". The threshold is in percentage ' - 'of the prefix limit.') - )), - ('peer_list', - ListOf( - Tuple( - orientation='horizontal', - elements=[ - TextUnicode( - title=_('BGP Peer'), - help=_('The configured value must match a BGP item reported by the monitored ' - 'device. For example: "10.194.115.98" or "2A10:1CD0:1020:135::20 IPv6 Unicast"'), - allow_empty=False, - size=50, - ), - TextUnicode( - title=_('BGP Peer Alias'), - help=_('You can configure an individual alias here for the BGP peer matching ' - 'the text configured in the "BGP Peer IP-address" field. The alias will ' - 'be shown in the check info'), - size=50, - ), - MonitoringState( - default_value=2, - title=_('State if not found'), - help=_('You can configure an individual state if the BGP peer matching the text ' - 'configured in the "BGP Peer IP-address" field is not found') - ), - ]), - add_label=_('Add BGP peer'), - movable=False, - title=_('BGP Peers'), - )), - ('internal_item', # added by plugin discovery function - TextUnicode()), - ], - hidden_keys=['internal_item'], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name='bgp_peer', - group=RulespecGroupCheckParametersNetworking, - item_spec=lambda: TextInput(title=_('BGP peer'), ), - match_type='dict', - parameter_valuespec=_parameter_valuespec_bgp_peer, - title=lambda: _('BGP peer'), - )) - - -def _valuespec_discovery_bgp_peer(): - item_parts = [ - # ('remote_address', 'Peer remote address'), - ('remote_as', 'Remote AS'), - ('address_family', 'Address family'), - ('routing_instance', 'Routing instance/VRF'), - - ] - return Dictionary( - title=_('BGP peer'), - elements=[ - ('build_item', - ListChoice( - title=_('Information not to use in the item name'), - help=_( - 'The Peer remote address is always used as the item name. By default the check will add the ' - 'address-family and the routing instance/VRF if available. You can decide to not use these ' - 'additional information in the item name. Do so only if your peers have only one address-' - 'family configured and you don\'t have the same peer remote address in different routing ' - 'instances/VRFs configured.' - ), - choices=item_parts, - default_value=['remote_as'], - )), - ], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupCheckParametersDiscovery, - match_type='dict', - name='discovery_bgp_peer', - valuespec=_valuespec_discovery_bgp_peer, - )) +# dummy to shadow build in bgp peer rule set \ No newline at end of file diff --git a/source/gui/wato/check_parameters/inv_bgp_peer.py b/source/gui/wato/check_parameters/inv_bgp_peer.py deleted file mode 100644 index e728182..0000000 --- a/source/gui/wato/check_parameters/inv_bgp_peer.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2022-04-24 -# -# 2022-04-24: added option for BGP down time -# added option to remove some columns from inventory -# 2022-04-28: added Whois options -# 2023-06-12: moved wato file from ~local/lib/check_mk/gui/plugins/wato -# to ~/local/lib/check_mk/gui/plugins/wato/check_parameters - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - HostRulespec, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - ListChoice, - Age, - DropdownChoice, - Integer, -) - -from cmk.gui.plugins.wato.inventory import ( - RulespecGroupInventory, -) - - -def _valuespec_inv_bgp_peer(): - removecolumns = [ - # ('remote_as', 'Remote AS'), - # ('remote_id', 'Remote ID'), - # ('local_addr', 'Local address'), - # ('local_as', 'Local AS'), - # ('local_id', 'Local ID'), - ('address_family', 'Address family'), - ('last_error', 'Last error'), - ('last_error_code', 'Last error code'), - # ('prev_state', 'Previous state'), - ('as_name', 'Remote AS name'), - ('as_org_name', 'Remote AS Org Name'), - ('bgp_type', 'Type'), - ('version', 'Version'), - ] - - return Dictionary( - title=_('BGP peer'), - elements=[ - ('not_in_service_time', - Age( - title=_('Time peer is not up until considered not in service'), - default_value=2592000, # 30 days in seconds, - )), - ('remove_columns', - ListChoice( - title=_('List of columns to remove'), - help=_('Information to remove from inventory'), - choices=removecolumns, - default_value=[], - )), - ('whois_enable', - Dictionary( - title=_('Add whois data to the inventory'), - help=_( - 'The whois data will be fetched via RDAP from the registries. For this the the plugin tries to' - 'find the best registry via the RDAP bootstrap data from https://data.iana.org/rdap/asn.json.' - 'The query it self will go to the found registry via http(s). Note: the request might be get ' - 'redirected if there a different authoritative registry for the ASn' - ), - elements=[ - ('whois_rir', - DropdownChoice( - title='Preferred RIR to fetch whois data', - help=_( - 'This registry will be used if the plugin can not determine the authoritative registry ' - 'based on the bootstrap data.' - ), - choices=[ - ('afrinic', _('AFRINIC (https://rdap.afrinic.net/rdap)')), - ('apnic', _('APNIC (https://rdap.apnic.net)')), - ('arin', _('ARIN (https://rdap.arin.net/registry)')), - ('ripe', _('RIPE (https://rdap.db.ripe.net)')), - ('lacnic', _('LACNIC (https://rdap.apnic.net)')), - ] - )), - ('whois_timeout', - Integer( - title='Timeout for connections to RIRs', - help=_('The connection timeout for each whois request.'), - default_value=5, - minvalue=1, - unit=_('seconds'), - )), - ] - )), - ], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupInventory, - match_type='dict', - name='inv_parameters:inv_bgp_peer', - valuespec=_valuespec_inv_bgp_peer, - )) diff --git a/source/packages/bgp_peer b/source/packages/bgp_peer index 0bc5758..a897999 100644 --- a/source/packages/bgp_peer +++ b/source/packages/bgp_peer @@ -27,17 +27,19 @@ 'https://thl-cmk.hopto.org/gitlab/checkmk/juniper-networks/juniper_bgp_peer\n' '\n', 'download_url': 'https://thl-cmk.hopto.org/vendor-independent/bgp_peer', - 'files': {'agent_based': ['bgp_peer.py', - 'inv_bgp_peer.py', - 'utils/bgp_peer.py'], - 'checkman': ['bgp_peer'], - 'gui': ['metrics/bgp_peer.py', - 'wato/check_parameters/bgp_peer.py', - 'wato/check_parameters/inv_bgp_peer.py'], + 'files': {'agent_based': ['bgp_peer.py'], + 'cmk_addons_plugins': ['bgp_peer/agent_based/bgp_peer.py', + 'bgp_peer/agent_based/inv_bgp_peer.py', + 'bgp_peer/graphing/bgp_peer.py', + 'bgp_peer/lib/bgp_peer.py', + 'bgp_peer/rulesets/bgp_peer.py', + 'bgp_peer/rulesets/inv_bgp_peer.py', + 'bgp_peer/checkman/bgp_peer'], + 'gui': ['wato/check_parameters/bgp_peer.py'], 'web': ['plugins/views/inv_bgp_peer.py']}, 'name': 'bgp_peer', 'title': 'BGP Peer', - 'version': '2.2.8-20250117', + 'version': '2.3.0-20250329', 'version.min_required': '2.3.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', - 'version.usable_until': '2.4.0b1'} + 'version.usable_until': '2.5.0b1'} -- GitLab