From 8b7a224360bc83f8b28eca33cb8d5b020fd9e461 Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Mon, 22 Jul 2024 21:39:11 +0200 Subject: [PATCH] update project --- README.md | 2 +- mkp/bgp_topology-0.0.1-20240722.mkp | Bin 0 -> 9704 bytes source/checkman/.gitkeep | 0 source/checkman/bgp_topology | 45 --- .../bgp_topology/constants.py | 62 ++++ .../bgp_topology/graphing/bgp_topology.py | 35 ++ .../bgp_topology/lib/bgp_topology.py | 344 ++++++++++++++++++ .../bgp_topology/lib/utils.py | 336 +++++++++++++++++ .../bgp_topology/libexec/check_bgp_topology | 18 + .../bgp_topology/rulesets/bgp_topology.py | 176 +++++++++ .../server_side_calls/bgp_topology.py | 178 +++++++++ source/packages/bgp_topology | 16 + 12 files changed, 1166 insertions(+), 46 deletions(-) create mode 100644 mkp/bgp_topology-0.0.1-20240722.mkp delete mode 100644 source/checkman/.gitkeep delete mode 100644 source/checkman/bgp_topology create mode 100644 source/cmk_addons_plugins/bgp_topology/constants.py create mode 100644 source/cmk_addons_plugins/bgp_topology/graphing/bgp_topology.py create mode 100644 source/cmk_addons_plugins/bgp_topology/lib/bgp_topology.py create mode 100644 source/cmk_addons_plugins/bgp_topology/lib/utils.py create mode 100755 source/cmk_addons_plugins/bgp_topology/libexec/check_bgp_topology create mode 100644 source/cmk_addons_plugins/bgp_topology/rulesets/bgp_topology.py create mode 100644 source/cmk_addons_plugins/bgp_topology/server_side_calls/bgp_topology.py create mode 100644 source/packages/bgp_topology diff --git a/README.md b/README.md index 9209cf4..9e11d58 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/packagee-0.1.2-20230706.mkp "package-0.1.2-20230706.mkp" +[PACKAGE]: ../../raw/master/mkp/bgp_topology-0.0.1-20240722.mkp "bgp_topology-0.0.1-20240722.mkp" # Title A short description about the plugin diff --git a/mkp/bgp_topology-0.0.1-20240722.mkp b/mkp/bgp_topology-0.0.1-20240722.mkp new file mode 100644 index 0000000000000000000000000000000000000000..c2016d68bdc1e69682bf793b7e24f92f311828cb GIT binary patch literal 9704 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 K2(TM4u>S#Qj1f@) literal 0 HcmV?d00001 diff --git a/source/checkman/.gitkeep b/source/checkman/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/source/checkman/bgp_topology b/source/checkman/bgp_topology deleted file mode 100644 index 08ef898..0000000 --- a/source/checkman/bgp_topology +++ /dev/null @@ -1,45 +0,0 @@ -title: Dummy check man page - used as template for new check manuals -agents: linux, windows, aix, solaris, hpux, vms, freebsd, snmp -catalog: see modules/catalog.py for possible values -license: GPL -distribution: check_mk -description: - Describe here: (1) what the check actually does, (2) under which - circumstances it goes warning/critical, (3) which devices are supported - by the check, (4) if the check requires a separated plugin or - tool or separate configuration on the target host. - -item: - Describe the syntax and meaning of the check's item here. Provide all - information one needs if coding a manual check with {checks +=} in {main.mk}. - Give an example. If the check uses {None} as sole item, - then leave out this section. - -examples: - # Give examples for configuration in {main.mk} here. If the check has - # configuration variable, then give example for them here. - - # set default levels to 40 and 60 percent: - foo_default_values = (40, 60) - - # another configuration variable here: - inventory_foo_filter = [ "superfoo", "superfoo2" ] - -perfdata: - Describe precisely the number and meaning of performance variables - the check sends. If it outputs no performance data, then leave out this - section. - -inventory: - Describe how the inventory for the check works. Which items - will it find? Describe the influence of check specific - configuration parameters to the inventory. - -[parameters] -foofirst(int): describe the first parameter here (if parameters are grouped - as tuple) -fooother(string): describe another parameter here. - -[configuration] -foo_default_levels(int, int): Describe global configuration variable of - foo here. Important: also tell the user how they are preset. diff --git a/source/cmk_addons_plugins/bgp_topology/constants.py b/source/cmk_addons_plugins/bgp_topology/constants.py new file mode 100644 index 0000000..1213084 --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/constants.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-16 +# File : bgp_topology/constants.py + +# 2024-07-16: copied from bgp_topology/lib/ruleset_names.py + + +from typing import Final + +ARG_PARSER_ANCHOR: Final[str] = '--bgp-anchor' +ARG_PARSER_ANCHOR_AS: Final[str] = 'bgp-as' +ARG_PARSER_ANCHOR_BOTH: Final[str] = 'both' +ARG_PARSER_ANCHOR_ID: Final[str] = 'bgp-id' +ARG_PARSER_EMBLEM_AS: Final[str] = '--bgp-emblem-as' +ARG_PARSER_EMBLEM_ID: Final[str] = '--bgp-emblem-id' +ARG_PARSER_HOST: Final[str] = '--host' +ARG_PARSER_MAKE_DEFAULT: Final[str] = '--make-default' +ARG_PARSER_NONE: Final[str] = 'none' +ARG_PARSER_SITES_EXCLUDE: Final[str] = '--exclude-sites' +ARG_PARSER_SITES_INCLUDE: Final[str] = '--include-sites' + +BGP_PEER_LOCAL_ADDR: Final[str] = 'local_addr' +BGP_PEER_LOCAL_AS: Final[str] = 'local_as' +BGP_PEER_LOCAL_ID: Final[str] = 'local_id' +BGP_PEER_REMOTE_ADDR: Final[str] = 'remote_addr' +BGP_PEER_REMOTE_AS: Final[str] = 'remote_as' +BGP_PEER_REMOTE_ID: Final[str] = 'remote_id' +BGP_PEER_STATE: Final[str] = 'state' +BGP_PEER_UPTIME: Final[str] = 'uptime' + +EMBLEM_BGP_ID: Final[str] = 'icon_topic_system' # icon_plugins_hw +EMBLEM_BGP_AS: Final[str] = 'icon_cloud' + +METRIC_TIME_TAKEN: Final[str] = 'topology_time_taken' + +PARAM_BGP_AS: Final[str] = 'bgp_as' +PARAM_BGP_EXT_ANCHOR: Final[str] = 'bgp_ext_anchor' +PARAM_BGP_EXT_ANCHOR_AS: Final[str] = 'bgp_ext_anchor_as' +PARAM_BGP_EXT_ANCHOR_BOTH: Final[str] = 'bgp_ext_anchor_both' +PARAM_BGP_EXT_ANCHOR_ID: Final[str] = 'bgp_ext_anchor_id' +PARAM_BGP_EXT_ANCHOR_NONE: Final[str] = 'bgp_ext_anchor_none' +PARAM_BGP_ID: Final[str] = 'bgp_id' +PARAM_BGP_SITES_EXCLUDE: Final[str] = 'exclude_sites' +PARAM_BGP_SITES_FILTER: Final[str] = 'bgp_filter_sites' +PARAM_BGP_SITES_INCLUDE: Final[str] = 'include_sites' +PARAM_EMBLEM_CUSTOM: Final[str] = 'custom_emblem' +PARAM_EMBLEM_DEFAULT: Final[str] = 'default_emblem' +PARAM_EMBLEM_NO_EMBLEM: Final[str] = 'no_emblem' +PARAM_MAKE_DEFAULT: Final[str] = 'make_default' + +PICTURE_TYPE_EMBLEM: Final[str] = 'emblem' +PICTURE_TYPE_ICON: Final[str] = 'icon' + +RULE_SET_NAME_BGP_TOPOLOGY: Final[str] = 'bgp_topology' + +TOPOLOGY_NAME: Final[str] = 'BGP' diff --git a/source/cmk_addons_plugins/bgp_topology/graphing/bgp_topology.py b/source/cmk_addons_plugins/bgp_topology/graphing/bgp_topology.py new file mode 100644 index 0000000..69af60e --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/graphing/bgp_topology.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-16 +# File : bgp_topology/graphing/bgp_topology.py + +from cmk.graphing.v1 import Title +from cmk.graphing.v1.graphs import Graph +from cmk.graphing.v1.metrics import Color, Metric, Unit, TimeNotation, AutoPrecision +from cmk.graphing.v1.perfometers import Closed, FocusRange, Open, Perfometer + +from cmk_addons.plugins.bgp_topology.constants import METRIC_TIME_TAKEN + +metric_topology_time_taken = Metric( + name=METRIC_TIME_TAKEN, + title=Title('Time taken'), + unit=Unit(TimeNotation(), AutoPrecision(4)), + color=Color.BLUE, +) + +graph_topology_time_taken = Graph( + name=METRIC_TIME_TAKEN, + title=Title('Time taken'), + compound_lines=[METRIC_TIME_TAKEN], +) + +perfometer_topology_time_taken = Perfometer( + name=METRIC_TIME_TAKEN, + focus_range=FocusRange(Closed(0), Open(1)), + segments=[METRIC_TIME_TAKEN] +) diff --git a/source/cmk_addons_plugins/bgp_topology/lib/bgp_topology.py b/source/cmk_addons_plugins/bgp_topology/lib/bgp_topology.py new file mode 100644 index 0000000..76e4d0a --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/lib/bgp_topology.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-16 +# File : bgp_topology/lib/bgp_topology.py + +# 2024-07-20: moved to lib -> is now an active check + +__AUTHOR__ = 'thl-cmk[at]outlook[dot]com' +__URL__ = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/bgp_topology' +__USAGE__ = '/local/lib/python3/cmk_addons/plugins/bgp_topology/libexec/check_bgp_topology --make-default' +__VERSION__ = '0.0.1-20240721' + +from argparse import ArgumentParser, Namespace, RawTextHelpFormatter +from collections.abc import MutableMapping, MutableSequence, Sequence +from dataclasses import dataclass +from time import time_ns + +from cmk.agent_based.v2 import render +from cmk_addons.plugins.bgp_topology.constants import ( + ARG_PARSER_ANCHOR, + ARG_PARSER_ANCHOR_AS, + ARG_PARSER_ANCHOR_BOTH, + ARG_PARSER_ANCHOR_ID, + ARG_PARSER_EMBLEM_AS, + ARG_PARSER_EMBLEM_ID, + ARG_PARSER_HOST, + ARG_PARSER_MAKE_DEFAULT, + ARG_PARSER_NONE, + ARG_PARSER_SITES_EXCLUDE, + ARG_PARSER_SITES_INCLUDE, + BGP_PEER_LOCAL_ADDR, + BGP_PEER_LOCAL_AS, + BGP_PEER_LOCAL_ID, + BGP_PEER_REMOTE_ADDR, + BGP_PEER_REMOTE_AS, + BGP_PEER_REMOTE_ID, + BGP_PEER_STATE, + BGP_PEER_UPTIME, + EMBLEM_BGP_AS, + EMBLEM_BGP_ID, + METRIC_TIME_TAKEN, + PARAM_BGP_EXT_ANCHOR_AS, + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_ID, + TOPOLOGY_NAME, +) +from cmk_addons.plugins.bgp_topology.lib.utils import ( + BASE_TOPO_PATH, + LiveStatusConnection, + Metric, + OMD_ROOT, + TopoConnections, + TopoObjects, + add_dummy_topologies, + get_anchor, + get_bgp_peer_clean_key, + get_bgp_peer_clean_value, + get_emblem, + get_service, + make_topo_default, + save_topology, +) + + +class Params(Namespace): + bgp_anchor: str + make_default: bool = False + bgp_emblem_as: str = EMBLEM_BGP_AS + bgp_emblem_id: str = EMBLEM_BGP_ID + include_sites: Sequence[str] | None = None + exclude_sites: Sequence[str] | None = None + host: str = '' + + +@dataclass(frozen=True) +class BgpPeer: + host: str + service: str + state: int + state_str: str + remote_address: str + uptime: str | None = None + local_as: str | None = None + local_address: str | None = None + local_id: str | None = None + remote_as: str | None = None + remote_id: str | None = None + + @classmethod + def parse(cls, host: str, state: int, servie: str, raw_peer_data: str): + raw_peer_attributes: Sequence[str] = raw_peer_data.split('\\n') + + bgp_peer: MutableMapping[str, str] = {} + for entry in raw_peer_attributes: + try: + key, value = entry.split(':', 1) + except ValueError: + key = None + value = None + key: str | None = get_bgp_peer_clean_key(key) + value: str | None = get_bgp_peer_clean_value(key, value) + if key is not None and value is not None: + bgp_peer[key] = value + + return cls( + host=host, + local_address=bgp_peer.get(BGP_PEER_LOCAL_ADDR), + local_as=bgp_peer.get(BGP_PEER_LOCAL_AS), + local_id=bgp_peer.get(BGP_PEER_LOCAL_ID), + remote_address=bgp_peer.get(BGP_PEER_REMOTE_ADDR), + remote_as=bgp_peer.get(BGP_PEER_REMOTE_AS), + remote_id=bgp_peer.get(BGP_PEER_REMOTE_ID), + service=servie, + state=state, + state_str=bgp_peer.get(BGP_PEER_STATE), + uptime=bgp_peer.get(BGP_PEER_UPTIME), + ) + + +def create_bgp_topology(params: Params | None) -> int: + start_time = time_ns() + + summary: list[str] = [] + details: list[str] = [] + + objects = TopoObjects() + connections = TopoConnections() + sub_directory = TOPOLOGY_NAME + + ls_connection = LiveStatusConnection() + if params.include_sites is not None: + ls_connection.filter_sites(include=True, sites=params.include_sites) + sites_str: str = ', '.join(params.include_sites) + details.append(f'Site(s) included: {sites_str}') + sub_directory = f'{TOPOLOGY_NAME}_{params.host}' + elif params.exclude_sites is not None: + ls_connection.filter_sites(include=False, sites=params.exclude_sites) + sites_str: str = ', '.join(params.exclude_sites) + details.append(f'Site(s) excluded: {sites_str}') + sub_directory = f'{TOPOLOGY_NAME}_{params.host}' + + bgp_anchor = get_anchor(params.bgp_anchor) + emblem_as = get_emblem(params.bgp_emblem_as) + emblem_id = get_emblem(params.bgp_emblem_id) + + query: str = ( + 'GET services\n' + 'Columns: host_name state description long_plugin_output\n' + 'Filter: description ~ BGP peer\n' + 'OutputFormat: python3\n' + ) + if (raw_bgp_peers := ls_connection.query(query=query)) is not None: + bgp_peers: MutableSequence[BgpPeer] = [ + BgpPeer.parse(host, state, service, raw_peer_data) for host, state, service, raw_peer_data in raw_bgp_peers + ] + + # create index by local address, add host and service objects + bgp_peers_by_local_addr: MutableMapping[str, MutableSequence[BgpPeer]] = {'0.0.0.0': []} + for bgp_peer in bgp_peers: + objects.add_host(bgp_peer.host) + objects.add_service(bgp_peer.host, bgp_peer.service) + connections.add_connection( + right=bgp_peer.host, + left=get_service(bgp_peer.host, bgp_peer.service), + left_state=bgp_peer.state + ) + + if bgp_peer.local_address is not None: + if bgp_peers_by_local_addr.get(bgp_peer.local_address) is None: + bgp_peers_by_local_addr[bgp_peer.local_address]: MutableSequence[BgpPeer] = [] + bgp_peers_by_local_addr[bgp_peer.local_address].append(bgp_peer) + else: + bgp_peers_by_local_addr['0.0.0.0'].append(bgp_peer) + + # find connections + for bgp_peer in bgp_peers: + if (peer_list := bgp_peers_by_local_addr.get(bgp_peer.remote_address)) is not None: + for peer in peer_list: + if bgp_peer.local_as == peer.remote_as and bgp_peer.local_as is not None: + if bgp_peer.local_id == peer.remote_id and bgp_peer.local_id is not None: + if bgp_peer.local_address == peer.remote_address and bgp_peer.local_address is not None: + connections.add_connection( + left=get_service(bgp_peer.host, bgp_peer.service), + right=get_service(peer.host, peer.service), + right_state=bgp_peer.state, + left_state=peer.state, + ) + + # if there is no peer_list the peer is either external (to checkmk) or the connection is not + # established, so we have no local address or remote id + else: + ext_bgp_host_as: str | None = None + ext_bgp_host_id: str | None = None + if bgp_anchor in [ + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_AS, + ] and bgp_peer.remote_as is not None: + ext_bgp_host_as: str = f'BGP-AS: {bgp_peer.remote_as}' + objects.add_host( + host=ext_bgp_host_as, + link2core=False, + emblem=emblem_as, + ) + if bgp_anchor in [ + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_ID + ] and bgp_peer.remote_id is not None: + ext_bgp_host_id: str = f'BGP-ID: {bgp_peer.remote_id}' + objects.add_host( + host=ext_bgp_host_id, + link2core=False, + emblem=emblem_id + ) + connections.add_connection( + right=ext_bgp_host_id, + left=get_service(bgp_peer.host, bgp_peer.service), + left_state=bgp_peer.state + ) + if ext_bgp_host_as is not None: + connections.add_connection(ext_bgp_host_id, ext_bgp_host_as) + + if ext_bgp_host_as is not None and ext_bgp_host_id is None: + connections.add_connection( + right=get_service(bgp_peer.host, bgp_peer.service), + left=ext_bgp_host_as, + right_state=bgp_peer.state, + ) + + connections.topo_connections.sort() + data_set = { + 'version': 1, + 'name': TOPOLOGY_NAME, + 'objects': dict(sorted(objects.topo_objects.items())), + 'connections': connections.topo_connections, + } + + save_topology(data=data_set, sub_directory=sub_directory) + # workaround for backend is only picking up topologies from default folder + add_dummy_topologies(sub_directory=sub_directory) + # end workaround + make_topo_default(sub_directory=sub_directory, make_default=params.make_default) + + summary.append(f'Objects: {len(objects.topo_objects)}') + details.append(f'Objects: {len(objects.topo_objects)}') + + summary.append(f'Connections: {len(connections.topo_connections)}') + details.append(f'Connections: {len(connections.topo_connections)}') + + details.append(f'Written to: {BASE_TOPO_PATH}/{sub_directory}/data_{TOPOLOGY_NAME.lower()}.json') + + value = (time_ns() - start_time) / 1e9 + summary.append(f'Time taken: {render.timespan(value)}') + details.append(f'Time taken: {render.timespan(value)}') + + perf_data = Metric( + name=METRIC_TIME_TAKEN, + value=value, + # levels=None, + # boundaries=None, + ) + + all_summary: str = ', '.join(summary) + all_details: str = '\n'.join(details) + print(f'{all_summary}\n{all_details}|{perf_data}') + return 0 + + +def parse_arguments(argv: Sequence[str]) -> Params: + parser = ArgumentParser( + prog='bgp_topology', + formatter_class=RawTextHelpFormatter, + description=f"""Create BGP peer network topology for Checkmk""", + epilog=f""" +Example usage: +{OMD_ROOT}/{__USAGE__} + +Version: {__VERSION__} | Written by {__AUTHOR__} +for more information see: {__URL__} + """ + ) + parser.add_argument( + ARG_PARSER_ANCHOR, + choices=[ + ARG_PARSER_ANCHOR_BOTH, + ARG_PARSER_ANCHOR_AS, + ARG_PARSER_ANCHOR_ID, + ARG_PARSER_NONE, + ], + default=ARG_PARSER_ANCHOR_BOTH, + help='Anchor for external BGP objects (default: %(default)s).', + ) + parser.add_argument( + ARG_PARSER_EMBLEM_AS, + type=str, + default=EMBLEM_BGP_AS, + help='Emblem to use for BGP-AS objects (default: %(default)s).', + ) + parser.add_argument( + ARG_PARSER_EMBLEM_ID, + type=str, + default=EMBLEM_BGP_ID, + help='Emblem to use for BGP-ID objects (default: %(default)s).', + ) + parser.add_argument( + ARG_PARSER_MAKE_DEFAULT, + action='store_const', const=True, + default=False, + help='Make this topology the default (default: %(default)s).', + ) + parser.add_argument( + ARG_PARSER_HOST, + type=str, + help="""The name of the Checkmk host to which the plugin is attached. This is set +automatically by Checkmk. If a site filter is active, the host name is +appended to the subdirectory where the topology is stored (“BGP†becomes +“BGP_host_nameâ€). This way we can have more than one BGP topology without +overwriting each other.""", + ) + site_filter = parser.add_mutually_exclusive_group() + site_filter.add_argument( + ARG_PARSER_SITES_INCLUDE, + type=str, + nargs='+', + help=f"""List of Checkmk site names to include in the topology creation. +Can not used together with {ARG_PARSER_SITES_EXCLUDE}""", + ) + site_filter.add_argument( + ARG_PARSER_SITES_EXCLUDE, + type=str, + nargs='+', + help=f"""List of Checkmk site names to exclude from the topology creation. +Can not used together with {ARG_PARSER_SITES_INCLUDE}""", + ) + + return parser.parse_args(argv) + + +def main(argv: Sequence[str] | None = None) -> int: + return create_bgp_topology(parse_arguments(argv=argv)) diff --git a/source/cmk_addons_plugins/bgp_topology/lib/utils.py b/source/cmk_addons_plugins/bgp_topology/lib/utils.py new file mode 100644 index 0000000..9c93067 --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/lib/utils.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 : 2024-07-16 +# File : bgp_topology/lib/utils.py + +# 2024-07-16: copied from vsphere_topology/lib/utils.py + +from collections.abc import Mapping, MutableMapping, MutableSequence, Sequence +from dataclasses import dataclass +from json import dumps as json_dunps, loads as json_loads +from os import environ +from pathlib import Path +from typing import Final, Tuple + +from livestatus import MultiSiteConnection, SiteConfigurations, SiteId +from cmk_addons.plugins.bgp_topology.constants import ( + ARG_PARSER_NONE, + BGP_PEER_LOCAL_ADDR, + BGP_PEER_LOCAL_AS, + BGP_PEER_LOCAL_ID, + BGP_PEER_REMOTE_ADDR, + BGP_PEER_REMOTE_AS, + BGP_PEER_REMOTE_ID, + BGP_PEER_STATE, + BGP_PEER_UPTIME, + PARAM_BGP_EXT_ANCHOR_AS, + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_ID, + TOPOLOGY_NAME, +) + +OMD_ROOT = environ['OMD_ROOT'] +BASE_TOPO_PATH: Final[str] = f'{OMD_ROOT}/var/check_mk/topology/data' + + +def get_bgp_peer_clean_key(raw_key: str) -> str | None: + key_map: Mapping = { + 'Local AS': BGP_PEER_LOCAL_AS, + 'Local address': BGP_PEER_LOCAL_ADDR, + 'Local identifier': BGP_PEER_LOCAL_ID, + 'Peer state': BGP_PEER_STATE, + 'Remote AS': BGP_PEER_REMOTE_AS, + 'Remote address': BGP_PEER_REMOTE_ADDR, + 'Remote identifier': BGP_PEER_REMOTE_ID, + 'Uptime': BGP_PEER_UPTIME, + } + + return key_map.get(raw_key) + + +def get_bgp_peer_clean_value(key: str, value: str) -> str | None: + class MatchValues: + local_addr = BGP_PEER_LOCAL_ADDR + remote_addr = BGP_PEER_REMOTE_ADDR + remote_id = BGP_PEER_REMOTE_ID + + if value is not None: + value = value.strip() + match key: + case MatchValues.local_addr | MatchValues.remote_addr | MatchValues.remote_id: + if value.strip() in ['0.0.0.0', 'N/A']: + return + if value: + return value + + +def get_service(host: str, servie: str) -> str: + return f'{servie}@{host}' + + +def get_anchor(raw_anchor: str) -> str | None: + anchor_map = { + 'both': PARAM_BGP_EXT_ANCHOR_BOTH, + 'as': PARAM_BGP_EXT_ANCHOR_AS, + 'id': PARAM_BGP_EXT_ANCHOR_ID, + 'none': ARG_PARSER_NONE + } + return anchor_map.get(raw_anchor) + + +def get_emblem(emblem: str) -> str | None: + if emblem != ARG_PARSER_NONE: + return emblem + + +def save_topology(data: Mapping, sub_directory: str) -> None: + """ + Save the topology as json file under $OMD_ROOT/var/check_mk/topology/data/{sub_directory}.data_{data['name']}.json + the filename will be changed to lower case. + Args: + data: the topology data + sub_directory: the subdirectory were to save the data under ~/var/check_mk/topology/data/ + + Returns: + None + """ + file_name = f'data_{data["name"]}.json'.lower() + save_file = Path(f'{BASE_TOPO_PATH}/{sub_directory}/{file_name}') + save_file.parent.mkdir(exist_ok=True, parents=True) + save_file.write_text(json_dunps(data)) + + +def make_topo_default(sub_directory: str, make_default: bool) -> None: + """ + Create the symlink "default" to $OMD_ROOT/var/check_mk/topology/data/{sub_directory} in + $OMD_ROOT/var/check_mk/topology/data/ if it don't exist or mage_default is True + Args: + sub_directory: the subdirectory under ~/var/check_mk/topology/data/ thaht become default + make_default: if True, create the symlink "default" with path as target + + Returns: + None + """ + + target_path = f'{BASE_TOPO_PATH}/{sub_directory}' + + if not Path(f'{BASE_TOPO_PATH}/default').exists(): + make_default = True + if make_default: + Path(f'{BASE_TOPO_PATH}/default').unlink(missing_ok=True) + Path(f'{BASE_TOPO_PATH}/default').symlink_to(target=Path(target_path), target_is_directory=True) + + +def get_topologies() -> Sequence[str | None]: + """ + Returns a list of topology names form the default typology directory. + + Returns: + List of str ie: ['CDP', 'LLDP'] + """ + path: str = f'{BASE_TOPO_PATH}/default' + if not Path(path).exists(): + return [] + + files = [f for f in Path(path).glob('*.json') if f.is_file()] + return [ + json_loads(Path(file).read_text())['name'] for file in files if + json_loads(Path(file).read_text()).get('name') is not None + ] + + +def add_dummy_topologies(sub_directory: str): + path: str = f'{BASE_TOPO_PATH}/default' + + # don't overwrite existing topology + if Path(path).exists() and not Path(f'{path}/data_{TOPOLOGY_NAME.lower()}.json').exists(): + dummy_topology = {'version': 1, 'name': TOPOLOGY_NAME, 'objects': {}, 'connections': []} + save_topology( + data=dummy_topology, + sub_directory='default', + ) + + for topology in get_topologies(): + if not Path(f'{BASE_TOPO_PATH}/{sub_directory}/data_{topology.lower()}.json').exists(): + save_topology( + data={'version': 1, 'name': topology, 'objects': {}, 'connections': []}, + sub_directory=sub_directory + ) + + +# +# live status +# +class LiveStatusConnection: + def __init__(self): + self.sites: SiteConfigurations = SiteConfigurations({}) + self.sites_mk = Path(f'{OMD_ROOT}/etc/check_mk/multisite.d/sites.mk') + self.socket_path = f'unix:{OMD_ROOT}/tmp/run/live' + if self.sites_mk.exists(): + # make eval() "secure" + # https://realpython.com/python-eval-function/#minimizing-the-security-issues-of-eval + _code = compile(self.sites_mk.read_text(), '<string>', 'eval') + allowed_names = ['sites', 'update'] + for name in _code.co_names: + if name not in allowed_names: + raise NameError(f'Use of {name} in {self.sites_mk.name} not allowed.') + + sites_raw: MutableMapping = {} + eval(self.sites_mk.read_text(), {'__builtins__': {}}, {'sites': sites_raw}) + + for site, data in sites_raw.items(): + self.sites.update({site: { + 'alias': data['alias'], + 'timeout': data['timeout'], + }}) + if data['socket'] == ('local', None): + self.sites[site]['socket'] = self.socket_path + else: + protocol, socket = data['socket'] + address, port = socket['address'] + self.sites[site]['socket'] = f'{protocol}:{address}:{port}' + self.sites[site]['tls'] = socket['tls'] + else: + self.sites.update({SiteId('local'): { + 'alias': 'Local site', + 'timeout': 5, + 'socket': self.socket_path + }}) + + self.c = MultiSiteConnection(self.sites) + dead_sites = [site['site']['alias'] for site in self.c.dead_sites().values()] + if dead_sites: + self.c.set_only_sites(self.c.alive_sites()) + + def query(self, query: str): + data: MutableSequence[Tuple[str, str]] = self.c.query(query=query) + + if data: + return data + else: + return None + + def filter_sites(self, include: bool, sites: Sequence[str]): + if include is True: + site_list = [site for site in self.c.sites if site in sites] + else: + site_list = [site for site in self.c.sites if site not in sites] + + self.c.set_only_sites(site_list) + + +class TopoObjects: + def __init__(self) -> None: + self.topo_objects: MutableMapping[str, object] = {} + + def add_host( + self, + host: str, + emblem: str | None = None, + icon: str | None = None, + link2core: bool = True, + obj_id_prefix: str = '', + ): + if self.topo_objects.get(f'{obj_id_prefix}{host}') is not None: + return + + metadata = {} + if emblem or icon: + metadata = {'images': {}} + + if emblem is not None: + metadata['images'].update({'emblem': emblem}) # node image + + if icon is not None: + metadata['images'].update({'icon': icon}) # node icon + + self.topo_objects[f'{obj_id_prefix}{host}'] = { + 'name': host, + 'link': {'core': host} if link2core else {}, + 'metadata': metadata, + } + + def add_service( + self, + host: str, + service: str, + emblem: str | None = None, + link2core: bool = True, + ): + obj_id = f'{service}@{host}' + if self.topo_objects.get(obj_id) is not None: + return + + metadata = {} + if emblem is not None: + metadata = { + 'images': { + 'emblem': emblem, # node image + }, + } + + self.topo_objects[obj_id] = { + 'name': service, + 'link': {'core': [host, service]} if link2core else {}, + 'metadata': metadata, + } + + +def get_connection_color(left_state: int, right_state:int) -> str: + state_to_color = { + 0: 'white', + 1: 'yellow', + 2: 'red', + 3: 'orange', + } + + return state_to_color.get(max(left_state, right_state), 'orange') + + +class TopoConnections: + def __init__(self) -> None: + self.topo_connections: MutableSequence = [] + self.clean_connections: MutableSequence[Sequence[str]] = [] + + def add_connection( + self, + left: str, + right: str, + left_state: int = 0, + right_state: int = 0, + ): + connection = [left, right] + connection.sort() + if connection not in self.clean_connections: + self.clean_connections.append(connection) + self.topo_connections.append([ + connection, { + 'line_config': { + 'css_styles': { + 'stroke-dasharray': 'unset' + }, + 'color': get_connection_color(left_state, right_state), + } + } + ]) + + +# taken from https://github.com/Checkmk/checkmk/blob/master/cmk/plugins/smb/lib/check_disk_smb.py +# Change-Id: I7426f6553a906c5ac50a8306157931a10640a526 +@dataclass +class Metric: + name: str + value: float + levels: tuple[float, float] | None = None + boundaries: tuple[float, float] | None = None + + def __str__(self) -> str: + l = f"{self.levels[0]};{self.levels[1]};" if self.levels else ";;" + b = f"{self.boundaries[0]};{self.boundaries[1]}" if self.boundaries else ";" + # I'm not too sure about the single quotes here, but keeping it for now + return f"'{self.name}'={self.value}B;{l}{b}" \ No newline at end of file diff --git a/source/cmk_addons_plugins/bgp_topology/libexec/check_bgp_topology b/source/cmk_addons_plugins/bgp_topology/libexec/check_bgp_topology new file mode 100755 index 0000000..d1c8d43 --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/libexec/check_bgp_topology @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-20 +# File : bgp_topology/lib_exec/bgp_topology + +import sys + +from cmk_addons.plugins.bgp_topology.lib.bgp_topology import main + +if __name__ == "__main__": + sys.exit(main()) + + diff --git a/source/cmk_addons_plugins/bgp_topology/rulesets/bgp_topology.py b/source/cmk_addons_plugins/bgp_topology/rulesets/bgp_topology.py new file mode 100644 index 0000000..7038083 --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/rulesets/bgp_topology.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-06 +# File : bgp_topology/rulesets/bgp_topology.py + +from collections.abc import Sequence + +from cmk.rulesets.v1 import Help, Label, Message, Title +from cmk.rulesets.v1.form_specs import ( + CascadingSingleChoice, + CascadingSingleChoiceElement, + DefaultValue, + DictElement, + Dictionary, + FixedValue, + SingleChoice, + SingleChoiceElement, + String, + List +) + +from cmk.rulesets.v1.form_specs.validators import LengthInRange, ValidationError +from cmk.rulesets.v1.rule_specs import ActiveCheck, Topic +from cmk_addons.plugins.bgp_topology.constants import ( + EMBLEM_BGP_AS, + EMBLEM_BGP_ID, + PARAM_BGP_AS, + PARAM_BGP_EXT_ANCHOR, + PARAM_BGP_EXT_ANCHOR_AS, + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_ID, + PARAM_BGP_EXT_ANCHOR_NONE, + PARAM_BGP_ID, + PARAM_BGP_SITES_EXCLUDE, + PARAM_BGP_SITES_FILTER, + PARAM_BGP_SITES_INCLUDE, + PARAM_EMBLEM_CUSTOM, + PARAM_EMBLEM_DEFAULT, + PARAM_EMBLEM_NO_EMBLEM, + PARAM_MAKE_DEFAULT, + PICTURE_TYPE_EMBLEM, + RULE_SET_NAME_BGP_TOPOLOGY, +) + + +class DuplicateInList: # pylint: disable=too-few-public-methods + """ Custom validator that ensures the validated list has no duplicate entries. """ + + def __init__( + self, + ) -> None: + pass + + @staticmethod + def _get_default_errmsg(_duplicates: Sequence) -> Message: + return Message(f"Duplicate element in list. Duplicate elements: {', '.join(_duplicates)}") + + def __call__(self, value: List[str] | None) -> None: + if not isinstance(value, list): + return + _duplicates = [value[i] for i, x in enumerate(value) if value.count(x) > 1] + _duplicates = list(set(_duplicates)) + if _duplicates: + raise ValidationError(message=self._get_default_errmsg(_duplicates)) + + +def get_emblem_element(default_emblem: str, picture_type: str) -> Sequence[CascadingSingleChoiceElement]: + return [ + CascadingSingleChoiceElement( + name=PARAM_EMBLEM_NO_EMBLEM, + title=Title(f'No custom {picture_type}'), + parameter_form=FixedValue( + value=True, + label=Label(f'No custom {picture_type} will be used') + )), + CascadingSingleChoiceElement( + name=PARAM_EMBLEM_DEFAULT, + title=Title(f'Use default {picture_type}'), + parameter_form=FixedValue( + value=True, + label=Label(f'"{default_emblem}" will be used as {picture_type}') + )), + CascadingSingleChoiceElement( + name=PARAM_EMBLEM_CUSTOM, + title=Title(f'Use custom {picture_type}'), + parameter_form=String( + custom_validate=(LengthInRange(min_value=1),), + prefill=DefaultValue(default_emblem), + )) + ] + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + PARAM_BGP_AS: DictElement( + parameter_form=CascadingSingleChoice( + title=Title('BGP AS emblem'), + elements=get_emblem_element(EMBLEM_BGP_AS, PICTURE_TYPE_EMBLEM), + prefill=DefaultValue(PARAM_EMBLEM_DEFAULT), + help_text=Help( + 'Here you can change the picture for the BGP-AS object. ' + 'If you use the built-in icons prefix the name with "icon_"' + ), + )), + PARAM_BGP_ID: DictElement( + parameter_form=CascadingSingleChoice( + title=Title('BGP ID emblem'), + elements=get_emblem_element(EMBLEM_BGP_ID, PICTURE_TYPE_EMBLEM), + prefill=DefaultValue(PARAM_EMBLEM_DEFAULT), + help_text=Help( + 'Here you can change the picture for the BGP-ID object. ' + 'If you use the built-in icons prefix the name with "icon_"' + ), + )), + PARAM_BGP_EXT_ANCHOR: DictElement( + parameter_form=SingleChoice( + title=Title('Anchor for external BGP objects'), + elements=[ + SingleChoiceElement(name=PARAM_BGP_EXT_ANCHOR_BOTH, title=Title('Use BGP AS and ID')), + SingleChoiceElement(name=PARAM_BGP_EXT_ANCHOR_AS, title=Title('Use BGP AS only')), + SingleChoiceElement(name=PARAM_BGP_EXT_ANCHOR_ID, title=Title('Use BGP ID only')), + SingleChoiceElement(name=PARAM_BGP_EXT_ANCHOR_NONE, title=Title(f'No anchor object')) + ], + prefill=DefaultValue(PARAM_BGP_EXT_ANCHOR_BOTH), + help_text=Help('Select how BGP elements external to Checkmk will be created'), + )), + PARAM_MAKE_DEFAULT: DictElement( + parameter_form=FixedValue( + title=Title('Make default'), + label=Label('This will be the default topology'), + help_text=Help( + 'Makes the topology the default topology. If there no default topology, this ' + 'topology becomes always the default.' + ), + value=True + )), + PARAM_BGP_SITES_FILTER: DictElement( + parameter_form=CascadingSingleChoice( + title=Title('Filter Checkmk sites'), + elements=[ + CascadingSingleChoiceElement( + name=PARAM_BGP_SITES_INCLUDE, + title=Title('Include checkmk sites'), + parameter_form=List(element_template=String( + title=Title('checkmk site name'), + custom_validate=(DuplicateInList(),), + )) + ), + CascadingSingleChoiceElement( + name=PARAM_BGP_SITES_EXCLUDE, + title=Title('Exclude checkmk sites'), + parameter_form=List(element_template=String( + title=Title('Checkmk site name'), + custom_validate=(DuplicateInList(),), + )) + ) + ], + prefill=DefaultValue(PARAM_BGP_SITES_INCLUDE), + help_text=Help(''), + )), + } + ) + + +rule_spec_bgp_topo = ActiveCheck( + name=RULE_SET_NAME_BGP_TOPOLOGY, + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title('BGP Topology'), +) diff --git a/source/cmk_addons_plugins/bgp_topology/server_side_calls/bgp_topology.py b/source/cmk_addons_plugins/bgp_topology/server_side_calls/bgp_topology.py new file mode 100644 index 0000000..37baa8e --- /dev/null +++ b/source/cmk_addons_plugins/bgp_topology/server_side_calls/bgp_topology.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-07-20 +# File : bgp_topology/server_side_calls/bgp_topology.py + + +from collections.abc import Iterator, Mapping, Sequence +from pydantic import BaseModel +from typing import Literal + +from cmk.utils import debug +from cmk.server_side_calls.v1 import ( + HostConfig, + Secret, + ActiveCheckCommand, + ActiveCheckConfig, +) + +from cmk_addons.plugins.bgp_topology.constants import ( + ARG_PARSER_ANCHOR, + ARG_PARSER_ANCHOR_AS, + ARG_PARSER_ANCHOR_ID, + ARG_PARSER_EMBLEM_AS, + ARG_PARSER_EMBLEM_ID, + ARG_PARSER_HOST, + ARG_PARSER_MAKE_DEFAULT, + ARG_PARSER_NONE, + ARG_PARSER_SITES_EXCLUDE, + ARG_PARSER_SITES_INCLUDE, + PARAM_BGP_EXT_ANCHOR_AS, + PARAM_BGP_EXT_ANCHOR_BOTH, + PARAM_BGP_EXT_ANCHOR_ID, + PARAM_BGP_EXT_ANCHOR_NONE, + PARAM_BGP_SITES_EXCLUDE, + PARAM_BGP_SITES_INCLUDE, + PARAM_EMBLEM_CUSTOM, + PARAM_EMBLEM_DEFAULT, + PARAM_EMBLEM_NO_EMBLEM, + RULE_SET_NAME_BGP_TOPOLOGY, +) + +EMBLEM = ( + tuple[Literal["default_emblem"], bool] | + tuple[Literal['custom_emblem'], str] | + tuple[Literal['no_emblem'], bool] | + None +) + +FILTER_SITES = ( + tuple[Literal['exclude_sites'], Sequence[str]] | + tuple[Literal['include_sites'], Sequence[str]] +) + +class BgpAnchorType: + anchor_both: str = PARAM_BGP_EXT_ANCHOR_BOTH + anchor_as: str = PARAM_BGP_EXT_ANCHOR_AS + anchor_id: str = PARAM_BGP_EXT_ANCHOR_ID + anchor_none: str = PARAM_BGP_EXT_ANCHOR_NONE + + +class Emblem: + default: str = PARAM_EMBLEM_DEFAULT + custom: str = PARAM_EMBLEM_CUSTOM + no_emblem: str = PARAM_EMBLEM_NO_EMBLEM + + +class Params(BaseModel): + bgp_as: EMBLEM = None + bgp_id: EMBLEM = None + bgp_ext_anchor: str | None = None + make_default: bool | None = None + bgp_filter_sites: FILTER_SITES | None = None + + +__params = { + 'bgp_as': ('custom_emblem', 'icon_cloud'), + 'bgp_id': ('no_emblem', True), + 'bgp_ext_anchor': 'bgp_ext_anchor_both', + 'make_default': True +} +__parsed = Params( + bgp_as=('custom_emblem', 'icon_cloud'), + bgp_id=('no_emblem', True), + bgp_ext_anchor='bgp_ext_anchor_both', + make_default=True +) + + +def _commands_bgp_topology_parser(params: Mapping[str, object]) -> Params: + if debug.enabled(): + print(params) + return Params.model_validate(params) + + +def commands_bgp_topology_arguments( + params: Params, host_config: HostConfig +) -> Iterator[ActiveCheckCommand]: + if debug.enabled(): + pass + # print(host_config) + + args: list[str | Secret] = [] + args += [ARG_PARSER_HOST, host_config.name.replace(' ', '_')] + if params.make_default is True: + args.append(ARG_PARSER_MAKE_DEFAULT) + + if params.bgp_ext_anchor is not None: + match params.bgp_ext_anchor: + case BgpAnchorType.anchor_both: + pass # this is the default + case BgpAnchorType.anchor_as: + args += [ARG_PARSER_ANCHOR, ARG_PARSER_ANCHOR_AS] + case BgpAnchorType.anchor_id: + args += [ARG_PARSER_ANCHOR, ARG_PARSER_ANCHOR_ID] + case BgpAnchorType.anchor_none: + args += [ARG_PARSER_ANCHOR, ARG_PARSER_NONE] + case _: + pass + + if params.bgp_id: + key, value = params.bgp_id + match key: + case Emblem.default: + pass # this is the default + case Emblem.custom: + args += [ARG_PARSER_EMBLEM_ID, value] + case Emblem.no_emblem: + args += [ARG_PARSER_EMBLEM_ID, ARG_PARSER_NONE] + case _: + pass + + if params.bgp_as: + key, value = params.bgp_as + match key: + case Emblem.default: + pass # this is the default + case Emblem.custom: + args += [ARG_PARSER_EMBLEM_AS, value] + case Emblem.no_emblem: + args += [ARG_PARSER_EMBLEM_AS, ARG_PARSER_NONE] + case _: + pass + + if params.bgp_filter_sites: + class FilterMode: + exclude_sites = PARAM_BGP_SITES_EXCLUDE + include_sites = PARAM_BGP_SITES_INCLUDE + + mode, site_list = params.bgp_filter_sites + match mode: + case FilterMode.exclude_sites: + args.append(ARG_PARSER_SITES_EXCLUDE) + args += site_list + case FilterMode.include_sites: + args.append(ARG_PARSER_SITES_INCLUDE) + args += site_list + case _: + pass + + if debug.enabled(): + print(args) + + yield ActiveCheckCommand( + service_description="BGP Topology", + command_arguments=args + ) + + +active_check_bgp_topology = ActiveCheckConfig( + name=RULE_SET_NAME_BGP_TOPOLOGY, + parameter_parser=_commands_bgp_topology_parser, + commands_function=commands_bgp_topology_arguments, +) diff --git a/source/packages/bgp_topology b/source/packages/bgp_topology new file mode 100644 index 0000000..7ebfba5 --- /dev/null +++ b/source/packages/bgp_topology @@ -0,0 +1,16 @@ +{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)', + 'description': 'Active check to create the BGP peer topology\n', + 'download_url': 'https://thl-cmk.hopto.org', + 'files': {'cmk_addons_plugins': ['bgp_topology/constants.py', + 'bgp_topology/lib/bgp_topology.py', + 'bgp_topology/lib/utils.py', + 'bgp_topology/libexec/check_bgp_topology', + 'bgp_topology/rulesets/bgp_topology.py', + 'bgp_topology/server_side_calls/bgp_topology.py', + 'bgp_topology/graphing/bgp_topology.py']}, + 'name': 'bgp_topology', + 'title': 'BGP peer topology', + 'version': '0.0.1-20240722', + 'version.min_required': '2.3.0b1', + 'version.packaged': 'cmk-mkp-tool 0.2.0', + 'version.usable_until': '2.4.0b1'} -- GitLab