diff --git a/agent_based/bgp_peer.py b/agent_based/bgp_peer.py index a4bf779fd7bdf0650af54b4c759dda738f12e301..0f10cc652b0927e938409bc660e55e1afd367683 100644 --- a/agent_based/bgp_peer.py +++ b/agent_based/bgp_peer.py @@ -28,8 +28,10 @@ # 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) # -# ToDo: make check/discovery function the base for huawei_bgp_peer # Example Agent Output: # BGP4-MIB @@ -85,9 +87,20 @@ def parse_bgp_peer(string_table: StringTable) -> Optional[Dict[str, BgpPeer]]: return peer_table -def discovery_bgp_peer(section: Dict[str, BgpPeer]) -> DiscoveryResult: +def discovery_bgp_peer(params, section: Dict[str, BgpPeer]) -> DiscoveryResult: + _item_parts = [ + 'remote_address', + 'address_family', + 'routing_instance', + ] for key in section.keys(): - yield Service(item=key) + 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 check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult: @@ -101,6 +114,8 @@ def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult: '6': 0, # established } + item = params.get('internal_item', item) + neighborstate.update(params.get('neighborstate', neighborstate)) # update neighbor status with params peer_not_found_state = params['peernotfound'] @@ -135,26 +150,27 @@ def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult: 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}') - 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 - elif acceptedprefixes is not None: + 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.', + notice='Prefix limit/warn threshold not configured on the device.', ) - warnthreshold = None if peer.admin_state == 2: # no perfdata if admin shutdown - if acceptedprefixes is not None: + 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=(warnthreshold, prefixadminlimit), + 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}' ) @@ -200,6 +216,14 @@ register.check_plugin( name='bgp_peer', service_name='BGP peer %s', discovery_function=discovery_bgp_peer, + discovery_default_parameters={ + 'build_item': [ + # 'remote_address', + # 'address_family', + # 'routing_instance', + ] + }, + discovery_ruleset_name='discovery_bgp_peer', check_function=check_bgp_peer, check_default_parameters={ 'minuptime': (7200, 3600), diff --git a/agent_based/inv_bgp_peer.py b/agent_based/inv_bgp_peer.py index 52b70867687d59804e63076d5bf7c8d23cd70da9..d273b4bc6738eb6eebb6c69c7b36d029858894d5 100644 --- a/agent_based/inv_bgp_peer.py +++ b/agent_based/inv_bgp_peer.py @@ -9,27 +9,33 @@ # # inventory of bgp peers # +# 2022-04-30: code cleanup/streamlining # +import time from typing import List + from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, SNMPTree, TableRow, exists, + OIDBytes ) - from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - StringTable, + StringByteTable, InventoryResult, ) from cmk.base.plugins.agent_based.utils.bgp_peer import ( - ByteToHex, - bgp_errors, + bgp_error_code_as_hex, + bgp_error_as_string, + InvBgpPeer, + get_bgp_type, + BgpWhois, ) -def parse_inv_bgp_peer(string_table: List[StringTable]): +def parse_inv_bgp_peer(string_table: List[StringByteTable]): peers, base = string_table try: local_as, local_id = base[0] @@ -41,11 +47,11 @@ def parse_inv_bgp_peer(string_table: List[StringTable]): for entry in peers: try: - remote_id, version, local_addr, remote_addr, remote_as, last_error = entry + remote_id, state, version, local_addr, remote_addr, remote_as, last_error, fsm_established_time = entry except ValueError: return - bgp_peers.append({ + bgp_peer: InvBgpPeer = { 'remote_addr': remote_addr, 'remote_id': remote_id, 'version': version, @@ -53,20 +59,30 @@ def parse_inv_bgp_peer(string_table: List[StringTable]): 'remote_as': remote_as, 'local_as': local_as, 'local_id': local_id, - 'bgp_type': 'iBGP' if local_as == remote_as else 'eBGP', - 'status_columns': { - 'last_error_code': ByteToHex(last_error), - 'last_error': bgp_errors(last_error), - }, - }) + 'bgp_type': get_bgp_type(local_as, remote_as), + 'fsm_established_time': int(fsm_established_time), + 'peer_state': 1 if state == '6' else 2, # adjust to match if_oper_status for inventory painter + 'last_error_code': bgp_error_code_as_hex(last_error), + 'last_error': bgp_error_as_string(last_error), + 'status_columns': {}, + 'address_family': 'N/A' + } + + bgp_peers.append(bgp_peer) + return bgp_peers def inventory_bgp_peers(params, section) -> InventoryResult: path = ['networking', 'bgp_peers'] + whois = None + if params.get('whois_enable'): + whois = BgpWhois( + default_rir=params['whois_enable'].get('whois_rir', 'https://rdap.db.ripe.net'), + timeout=params['whois_enable'].get('whois_timeout', 5) + ) for bgp_peer in section: - key_columns = {'remote_addr': bgp_peer['remote_addr']} for key in key_columns.keys(): @@ -75,6 +91,29 @@ def inventory_bgp_peers(params, section) -> InventoryResult: status_columns = bgp_peer['status_columns'] bgp_peer.pop('status_columns') + if whois: + as_info = whois.get_whois_data_by_asn(int(bgp_peer.get('remote_as'))) + bgp_peer.update(as_info) + + for column in params.get('remove_columns', []): + try: + bgp_peer.pop(column) + except KeyError: + pass + + fsm_established_time = bgp_peer.get('fsm_established_time') + if fsm_established_time: + bgp_peer.pop('fsm_established_time') + peer_state = bgp_peer.get('peer_state') + in_service = True + not_in_service_time = params.get('not_in_service_time', 2592000) + if peer_state == 2: # not established + if fsm_established_time >= not_in_service_time: + in_service = False + + bgp_peer.update({'in_service': in_service}) + bgp_peer.update({'last_change': time.time() - fsm_established_time}) + yield TableRow( path=path, key_columns=key_columns, @@ -82,6 +121,9 @@ def inventory_bgp_peers(params, section) -> InventoryResult: status_columns=status_columns ) + if whois: + del whois + register.snmp_section( name='inv_bgp_peer', @@ -91,11 +133,13 @@ register.snmp_section( base='.1.3.6.1.2.1.15.3.1', # BGP4-MIB::BgpPeerEntry oids=[ '1', # bgpPeerIdentifier + '2', # bgpPeerState '4', # bgpPeerNegotiatedVersion '5', # bgpPeerLocalAddr '7', # bgpPeerRemoteAddr '9', # bgpPeerRemoteAs - '14', # bgpPeerLastError + OIDBytes('14'), # bgpPeerLastError + '16', # bgpPeerFsmEstablishedTime ] ), SNMPTree( @@ -113,6 +157,7 @@ register.inventory_plugin( name='inv_bgp_peer', inventory_function=inventory_bgp_peers, inventory_default_parameters={ + 'not_in_service_time': 2592000, # 30 days in seconds }, inventory_ruleset_name='inv_bgp_peer', ) diff --git a/agent_based/utils/bgp_peer.py b/agent_based/utils/bgp_peer.py index f48d76448f996fd123a517aa727d72555e91c340..0284eb0075c839096823df495b2b6cb9a32b16e4 100644 --- a/agent_based/utils/bgp_peer.py +++ b/agent_based/utils/bgp_peer.py @@ -7,16 +7,27 @@ # URL : https://thl-cmk.hopto.org # Date : 2021-08-29 # -# include file, will be used with bgp_peer, inv_bgp_peer, cisco_bgp_peer, inv_cisco_bgp_peer +# include file, will be used with (inv_)bgp_peer, (inv_)cisco_bgp_peer, (inv_)juniper_bgp_peer, huawei_bgp_peer # # 2022-04-17: added peer_unavail_reason/peer_unavail_reason_str for huawei bgp peers +# 2022-04-29: added device_admin_limit +# 2022-04-30: code cleanup/streamlining +# 2022-05-09: added item to BgpPeer class, this is used in the discovery function # +import requests +import json import re -from typing import List, Tuple, Optional, Dict +import ipaddress +from typing import List, Tuple, Optional, Dict, TypedDict from dataclasses import dataclass +class BgpPeerItem(TypedDict): + remote_address: str + address_family: str + routing_instance: str + @dataclass class BgpPeer: peer_state: int @@ -26,6 +37,8 @@ class BgpPeer: fsm_established_time: int metric_rate: List[Tuple[str, int]] metric_count: List[Tuple[str, int]] + item: BgpPeerItem + device_admin_limit: Optional[bool] prefix_admin_limit: Optional[int] prefix_threshold: Optional[int] prefix_clear_threshold: Optional[int] @@ -34,6 +47,23 @@ class BgpPeer: peer_unavail_reason_str: Optional[str] +class InvBgpPeer(TypedDict): + remote_addr: str + remote_id: str + version: str + local_addr:str + remote_as: str + local_as: str + local_id: str + bgp_type: str + fsm_established_time: int + peer_state: int + last_error_code: str + last_error: str + status_columns: Dict[str, str] + address_family: Optional[str] + + def sec2hr(seconds): m, s = divmod(seconds, 60) h, m = divmod(m, 60) @@ -65,13 +95,24 @@ def bgp_adminstate(st): return names.get(st, st) -def ByteToHex(byteStr): - return ''.join(['%02X ' % ord(x) for x in byteStr]).strip() +def get_bgp_type(local_as: str, remote_as: str) -> str: + bgp_type = 'N/A' + if local_as.isdigit() and remote_as.isdigit(): + if local_as == remote_as: + bgp_type = 'iBGP' + else: + bgp_type = 'eBGP' + return bgp_type -def bgp_errors(bytestring): - byte1, byte2 = ByteToHex(bytestring).split() +def bgp_error_code_as_hex(error_code: List[int]): + return ''.join([f'{m:02x}' for m in error_code]) + + +def bgp_error_as_string(error_code: List[int]): + # byte1, byte2 = ByteToHex(bytestring).split() + main_code, sub_code = error_code names = {} names[0] = {0: 'NO ERROR'} names[1] = { @@ -115,14 +156,10 @@ def bgp_errors(bytestring): 4: 'Connection Rejected', 5: 'Other Configuration Change', } - return names[int(byte1, 16)].get(int(byte2, 16)) - + return names[main_code].get(sub_code) -def bgp_render_ipv4_address(bytestring): - return '.'.join([f'{ord(m)}' for m in bytestring]) - -def bgp_shorten_ipv6_adress(address): +def bgp_shorten_ipv6_address(address): address = address.split(':') span = 2 address = [''.join(address[i:i + span]) for i in range(0, len(address), span)] @@ -139,35 +176,38 @@ def bgp_shorten_ipv6_adress(address): return address -def bgp_render_ipv6_address(bytestring): - address = ":".join(["%02s" % hex(ord(m))[2:] for m in bytestring]).replace(' ', '0').upper() - address = bgp_shorten_ipv6_adress(address) - - return address - - -def bgp_render_ip_address(bytestring): - if len(bytestring) == 4: - return bgp_render_ipv4_address(bytestring) - elif len(bytestring) == 16: - return bgp_render_ipv6_address(bytestring) +def bgp_render_ip_address(addr_type: str, addr: List[int]) -> str: + if addr_type == '1': + return '.'.join([str(m) for m in addr]) + elif addr_type == '2': + # IPv6 address from snmp oid in decimal + # [10, 1, 7, 40, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 5, 25] + # change to hex with leading zero + # ['0a', '01', '07', '28', '00', '00', '32', '00', '00', '00', '00', '00', '00', '00', '05', '19'] + remote_address = ':'.join([f'{m:02x}' for m in addr]).split(':') + # convert to long ipv6 address 0a01:0728:0000:3200:0000:0000:0000:0519 + remote_address = ':'.join([''.join(remote_address[i:i + 2]) for i in range(0, len(remote_address), 2)]) + # convert to short ipv6 address a01:728:0:3200::519 + remote_address = str(ipaddress.ip_address(remote_address)) + # replace bytes in entry with ip address + return remote_address else: - return '' + return 'N/A' -def bgp_get_peer(OID_END): +def bgp_get_ip_address_from_oid(oid_end): # returns peer address string from OID_END # u'1.4.217.119.208.34.1.1' --> 217.119.208.34 - # u'2.20.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17.2.1' --> 42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17 - peer_ip = '' - OID_END = OID_END.split('.') - if int(OID_END[1]) == 4: # length of ip address - peer_ip = '.'.join(OID_END[2:6]) # ipv4 address - elif int(OID_END[1]) == 16: # ipv6 address - peer_ip = ':'.join('%02s' % hex(int(m))[2:] for m in OID_END[2:18]).replace(' ', '0').upper() - peer_ip = bgp_shorten_ipv6_adress(peer_ip) + # u'2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17.2.1' --> 42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17 + ip_address = '' + oid_end = oid_end.split('.') + if int(oid_end[1]) == 4: # length of ip address + ip_address = '.'.join(oid_end[2:6]) # ipv4 address + elif int(oid_end[1]) == 16: # ipv6 address + ip_address = ':'.join('%02s' % hex(int(m))[2:] for m in oid_end[2:18]).replace(' ', '0').upper() + ip_address = bgp_shorten_ipv6_address(ip_address) - return peer_ip + return ip_address def bgp_get_peer_entry(peer: List) -> Optional[Dict[str, BgpPeer]]: @@ -185,6 +225,12 @@ def bgp_get_peer_entry(peer: List) -> Optional[Dict[str, BgpPeer]]: fsm_established_time=int(fsm_established_time), metric_count=[], metric_rate=[], + item={ + 'remote_address': remote_addr, + 'address_family': '', + 'routing_instance': '', + }, + device_admin_limit=None, prefix_admin_limit=None, prefix_threshold=None, prefix_clear_threshold=None, @@ -214,3 +260,246 @@ def bgp_get_peer_entry(peer: List) -> Optional[Dict[str, BgpPeer]]: pass return {remote_addr: bgp_peer} + + +def _fetch_data(url: str, timeout: int) -> dict: + response = requests.get( + url=url, + timeout=timeout, + ) + # ToDo: improve/Implement error handling + if response.status_code == 200: + return json.loads(response.text) + else: + return {} + + +class BgpWhois: + __rdap_boot_strap = { + 'source': 'https://data.iana.org/rdap/asn.json', + 'description': 'RDAP bootstrap file for Autonomous System Number allocations', + 'publication': '2021-12-07T20:00:01Z', + 'services': [ + [ + [ + '36864-37887', + '327680-328703', + '328704-329727' + ], + [ + 'https://rdap.afrinic.net/rdap/', + 'http://rdap.afrinic.net/rdap/' + ] + ], + [ + [ + '4608-4865', + '7467-7722', + '9216-10239', + '17408-18431', + '23552-24575', + '37888-38911', + '45056-46079', + '55296-56319', + '58368-59391', + '63488-63999', + '64000-64098', + '64297-64395', + '131072-132095', + '132096-133119', + '133120-133631', + '133632-134556', + '134557-135580', + '135581-136505', + '136506-137529', + '137530-138553', + '138554-139577', + '139578-140601', + '140602-141625', + '141626-142649', + '142650-143673', + '143674-144697', + '144698-145721', + '145722-146745', + '146746-147769', + '147770-148793', + '148794-149817', + '149818-150841', + '150842-151865' + ], + [ + 'https://rdap.apnic.net/' + ] + ], + [ + [ + '1-1876', + '1902-2042', + '2044-2046', + '2048-2106', + '2137-2584', + '2615-2772', + '2823-2829', + '2880-3153', + '3354-4607', + '4866-5376', + '5632-6655', + '6912-7466', + '7723-8191', + '10240-12287', + '13312-15359', + '16384-17407', + '18432-20479', + '21504-23455', + '23457-23551', + '25600-26623', + '26624-27647', + '29696-30719', + '31744-32767', + '32768-33791', + '35840-36863', + '39936-40959', + '46080-47103', + '53248-54271', + '54272-55295', + '62464-63487', + '64198-64296', + '393216-394239', + '394240-395164', + '395165-396188', + '396189-397212', + '397213-398236', + '398237-399260', + '399261-400284', + '400285-401308' + ], + [ + 'https://rdap.arin.net/registry/', + 'http://rdap.arin.net/registry/' + ] + ], + [ + [ + '1877-1901', + '2043', + '2047', + '2107-2136', + '2585-2614', + '2773-2822', + '2830-2879', + '3154-3353', + '5377-5631', + '6656-6911', + '8192-9215', + '12288-13311', + '15360-16383', + '20480-21503', + '24576-25599', + '28672-29695', + '30720-31743', + '33792-34815', + '34816-35839', + '38912-39935', + '40960-41983', + '41984-43007', + '43008-44031', + '44032-45055', + '47104-48127', + '48128-49151', + '49152-50175', + '50176-51199', + '51200-52223', + '56320-57343', + '57344-58367', + '59392-60415', + '60416-61439', + '61952-62463', + '64396-64495', + '196608-197631', + '197632-198655', + '198656-199679', + '199680-200191', + '200192-201215', + '201216-202239', + '202240-203263', + '203264-204287', + '204288-205211', + '205212-206235', + '206236-207259', + '207260-208283', + '208284-209307', + '209308-210331', + '210332-211355', + '211356-212379', + '212380-213403' + ], + [ + 'https://rdap.db.ripe.net/' + ] + ], + [ + [ + '27648-28671', + '52224-53247', + '61440-61951', + '64099-64197', + '262144-263167', + '263168-263679', + '263680-264604', + '264605-265628', + '265629-266652', + '266653-267676', + '267677-268700', + '268701-269724', + '269725-270748', + '270749-271772', + '271773-272796', + '272797-273820' + ], + [ + 'https://rdap.lacnic.net/rdap/' + ] + ] + ], + 'version': '1.0' + } + __rirs = { + 'ripe': 'https://rdap.db.ripe.net', + 'arin': 'https://rdap.arin.net/registry', + 'afrinic': 'https://rdap.afrinic.net/rdap', + 'lacnic': 'https://rdap.lacnic.net/rdap', + 'apnic': 'https://rdap.apnic.net', + } + + def __find_rir_by_asn(self, asn: int) -> str: + for rir in self.__rdap_boot_strap['services']: + url = rir[1][0] # https rdap url + for asns in rir[0]: + asns = asns.split('-') + if len(asns) == 1: + asns = asns + asns + if int(asns[0]) <= asn <= int(asns[1]): + return url + return self.__rir + + def __init__(self, default_rir: str, timeout: int): + self.__timeout = timeout + self.__rir = self.__rirs.get(default_rir, 'https://rdap.db.ripe.net') + self.__known_asns = {} + + def get_whois_data_by_asn(self, asn: int) -> Dict[str, str]: + asn_info = {} + # don't fetch for private ASNs, two byte or four byte ASN number + if asn < 64512 or (65536 < asn < 4200000000): + rir = self.__find_rir_by_asn(asn) + query = 'autnum' + data = _fetch_data(f'{rir}{query}/{asn}', self.__timeout) + if data: + asn_info['as_name'] = data.get('name') + vcard_array = data.get('entities')[0].get('vcardArray') + if vcard_array: + for line in vcard_array[1]: + if line[0] == 'fn': + asn_info['as_org_name'] = line[3] + self.__known_asns.update(asn_info) + return asn_info diff --git a/bgp_peer.mkp b/bgp_peer.mkp index fd34868a5bb1d7cad94daad457f702246101afc8..d710de6e528646e7a828bb7a8182917ab739b04a 100644 Binary files a/bgp_peer.mkp and b/bgp_peer.mkp differ diff --git a/packages/bgp_peer b/packages/bgp_peer index acd4ce4e62de8425841ef65416635f164192ec8c..fd6a9142c85e6d903ac74aeda8353f337e422f9c 100644 --- a/packages/bgp_peer +++ b/packages/bgp_peer @@ -15,11 +15,12 @@ 'checkman': ['bgp_peer'], 'web': ['plugins/metrics/bgp_peer.py', 'plugins/views/inv_bgp_peer.py', - 'plugins/wato/bgp_peer.py']}, + 'plugins/wato/bgp_peer.py', + 'plugins/wato/inv_bgp_peer.py']}, 'name': 'bgp_peer', - 'num_files': 7, + 'num_files': 8, 'title': 'BGP Peer State Check', - 'version': '20220418.v1.7', + 'version': '20220509.v1.8', 'version.min_required': '2.0.0', 'version.packaged': '2021.09.20', 'version.usable_until': None} \ No newline at end of file diff --git a/web/plugins/metrics/bgp_peer.py b/web/plugins/metrics/bgp_peer.py index d6cee6e915c4185a8275e51796040ddc4d879ecd..21c2c7f18a66cafdfbb5385b986965d5a15d1553 100644 --- a/web/plugins/metrics/bgp_peer.py +++ b/web/plugins/metrics/bgp_peer.py @@ -87,7 +87,27 @@ metric_info['bgp_peer_suppressedprefixes'] = { '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', +} ###################################################################################################################### @@ -165,6 +185,17 @@ graph_info['bgp_peer.time_since_last_update'] = { 'range': (0, 'bgp_peer_inupdateelapsedtime:mac'), } +# 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'), + ], +} + ###################################################################################################################### # # define perf-o-meter for bgp peer uptime + prefixes accepted/advertised diff --git a/web/plugins/views/inv_bgp_peer.py b/web/plugins/views/inv_bgp_peer.py index 3d828a1c6d8d54c3a58d7e921dedf05eeae00c80..af7261dd1710949a655d8b8a0551e71ca8fc4ae0 100644 --- a/web/plugins/views/inv_bgp_peer.py +++ b/web/plugins/views/inv_bgp_peer.py @@ -1,16 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from cmk.gui.i18n import _ from cmk.gui.plugins.views import ( inventory_displayhints, ) -from cmk.gui.i18n import _ +from cmk.gui.plugins.visuals.inventory import ( + FilterInvtableAdminStatus, + FilterInvtableTimestampAsAge, + FilterInvBool, +) +from cmk.gui.plugins.views.inventory import declare_invtable_view inventory_displayhints.update({ '.networking.bgp_peers:': { 'title': _('BGP Peers'), 'keyorder': [ - 'remote_addr', 'local_addr', 'remote_id', 'local_id', 'remote_as', 'local_as', 'bgp_type', 'version', + 'remote_addr', + 'peer_state', 'last_change', 'in_service', + 'local_addr', 'remote_id', 'local_id', 'remote_as', 'local_as', ], 'view': 'invbgppeer_of_host', }, @@ -25,8 +33,26 @@ inventory_displayhints.update({ '.networking.bgp_peers:*.last_error': {'title': _('Last error'), }, '.networking.bgp_peers:*.last_error_code': {'title': _('Last error code'), }, '.networking.bgp_peers:*.address_family': {'title': _('Address family'), }, + '.networking.bgp_peers:*.as_name': {'title': _('Remote AS Name'), }, + '.networking.bgp_peers:*.as_org_name': {'title': _('Remote AS Org Name'), }, + '.networking.bgp_peers:*.peer_state': { + 'title': _('Peer state'), + 'short': _('State'), + 'paint': 'if_admin_status', + 'filter': FilterInvtableAdminStatus, + }, + '.networking.bgp_peers:*.in_service': { + 'title': _('In service'), + 'short': _('In service'), + 'paint': 'bool', + # 'filter': FilterInvBool, + }, + '.networking.bgp_peers:*.last_change': { + 'title': _('Last change'), + 'short': _('Last change'), + 'paint': 'timestamp_as_age_days', + 'filter': FilterInvtableTimestampAsAge, + }, }) -from cmk.gui.plugins.views.inventory import declare_invtable_view - declare_invtable_view('invbgppeer', '.networking.bgp_peers:', _('BGP peers'), _('BGP peers')) diff --git a/web/plugins/wato/bgp_peer.py b/web/plugins/wato/bgp_peer.py index 3756c91874973b5b896b20839623a80b85d36f04..5e37eb5c8f0d3c87b198aec880155372f6609ffe 100644 --- a/web/plugins/wato/bgp_peer.py +++ b/web/plugins/wato/bgp_peer.py @@ -13,7 +13,10 @@ # 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 # + from cmk.gui.i18n import _ from cmk.gui.valuespec import ( Dictionary, @@ -23,12 +26,15 @@ from cmk.gui.valuespec import ( Tuple, TextUnicode, MonitoringState, + ListChoice, ) from cmk.gui.plugins.wato import ( CheckParameterRulespecWithItem, rulespec_registry, RulespecGroupCheckParametersNetworking, + HostRulespec, + RulespecGroupCheckParametersDiscovery, ) @@ -37,10 +43,30 @@ def _parameter_valuespec_bgp_peer(): ('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 if below'), unit='seconds', default_value=7200, minvalue=0), - Integer(title=_('Critical if below'), unit='seconsa', default_value=3600, minvalue=0) + 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', @@ -136,18 +162,21 @@ def _parameter_valuespec_bgp_peer(): ('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, @@ -172,3 +201,39 @@ rulespec_registry.register( parameter_valuespec=_parameter_valuespec_bgp_peer, title=lambda: _('BGP peer'), )) + + +def _valuespec_discovery_bgp_peer(): + item_parts = [ + # ('remote_address', 'Peer remote address'), + ('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=[], + )), + ], + ) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupCheckParametersDiscovery, + match_type='dict', + name='discovery_bgp_peer', + valuespec=_valuespec_discovery_bgp_peer, + )) diff --git a/web/plugins/wato/inv_bgp_peer.py b/web/plugins/wato/inv_bgp_peer.py new file mode 100644 index 0000000000000000000000000000000000000000..a62b0048c9d35ffbdce14e1f0e806efeef38420e --- /dev/null +++ b/web/plugins/wato/inv_bgp_peer.py @@ -0,0 +1,107 @@ +#!/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 + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato 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, + ))