diff --git a/README.md b/README.md index 63d5cc58c04146d92088e9dfe8c6a4633bf926fc..76338dba90754a28bb382a51c78e0192b17e172d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/vsphere_topo-0.0.5-20240714.mkp "vsphere_topo-0.0.5-20240714.mkp" +[PACKAGE]: ../../raw/master/mkp/vsphere_topo-0.0.5-20240717.mkp "vsphere_topo-0.0.5-20240717.mkp" # vSphere Topology Visualization This plugin uses the data from the _VMware ESX via vSphere_ special agent to create a topology of the vSphere environment.\ diff --git a/mkp/vsphere_topo-0.0.5-20240717.mkp b/mkp/vsphere_topo-0.0.5-20240717.mkp new file mode 100644 index 0000000000000000000000000000000000000000..736c43e65fad350c19a369c5a616d20d990612d9 Binary files /dev/null and b/mkp/vsphere_topo-0.0.5-20240717.mkp differ diff --git a/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py b/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py index dfb19c1f8fca6665e41ad0b283e80ffd2c577b94..f03291ece65591596d2a031be484a4ce31d0c034 100644 --- a/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py +++ b/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py @@ -16,10 +16,11 @@ # -> obsoletes 'Don\'t add vCenter as VM' # 2024-07-12: added support for Queried host is a host system # 2024-07-13: refactoring save topology/make default and add dummy's logic +# 2024-07-16: moved add_host/add_service/add_connection to lib/utils/TopoObjects/TopoConnections +# moved ruleset name constants to utils/ruleset_names.py from collections.abc import Mapping, Sequence from time import time_ns -from typing import Any from cmk.agent_based.v2 import ( CheckPlugin, @@ -32,13 +33,11 @@ from cmk.agent_based.v2 import ( render, ) from cmk.base.check_api import host_name -from cmk_addons.plugins.vsphere_topo.lib.utils import ( - BASE_TOPO_PATH, +from cmk_addons.plugins.vsphere_topo.constants import ( EMBLEM_CLUSTER, EMBLEM_DATA_CENTER, EMBLEM_DATA_STORE, ICON_VCENTER, - LiveStatusConnection, PARAM_CLUSTER, PARAM_DATA_CENTER, PARAM_DATA_STORE, @@ -48,6 +47,13 @@ from cmk_addons.plugins.vsphere_topo.lib.utils import ( PARAM_VCENTER, PARAM_VM_NAMES, RULE_SET_NAME_VSPHERE_TOPO, + TOPOLOGY_NAME, +) +from cmk_addons.plugins.vsphere_topo.lib.utils import ( + BASE_TOPO_PATH, + LiveStatusConnection, + TopoConnections, + TopoObjects, add_dummy_topologies, adjust_name, get_emblem, @@ -67,64 +73,14 @@ def discover_vsphere_topo( def check_vsphere_topo( - params: Mapping[str, Any], + params: Mapping[str, object], section_esx_vsphere_clusters: Section | None, section_esx_vsphere_virtual_machines: Sequence[Section] | None, section_esx_vsphere_datastores: Section | None, ) -> CheckResult: - def add_host( - host: str, - emblem: str | None = None, - icon: str | None = None, - link2core: bool = True, - obj_id_prefix: str = '', - ): - if 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 - - objects[f'{obj_id_prefix}{host}'] = { - "name": host, - "link": {'core': host} if link2core else {}, - 'metadata': metadata, - } - - def add_service(host: str, service: str, emblem: str | None = None, link2core: bool = True): - obj_id = f'{service}@{host}' - if objects.get(obj_id) is not None: - return - - metadata = {} - if emblem is not None: - metadata = { - 'images': { - 'emblem': emblem, # node image - }, - } - - objects[obj_id] = { - "name": service, - "link": {'core': [host, service]} if link2core else {}, - 'metadata': metadata, - } - - def add_connection(left: str, right: str): - connection = [left, right] - connection.sort() - connections.append([connection, {'line_config': {'css_styles': {'stroke-dasharray': 'unset'}}}]) - start_time = time_ns() - objects = {} - connections = [] + objects = TopoObjects() + connections = TopoConnections() if section_esx_vsphere_clusters is not None: __clusters = { @@ -136,7 +92,7 @@ def check_vsphere_topo( } raw_vsphere_host: str = host_name().strip() - add_host( + objects.add_host( host=raw_vsphere_host, obj_id_prefix='vc', icon=get_emblem(ICON_VCENTER, params.get(PARAM_VCENTER)), @@ -145,32 +101,32 @@ def check_vsphere_topo( for cluster in section_esx_vsphere_clusters: data_center: str = section_esx_vsphere_clusters[cluster]['datacenter'].strip() - if objects.get(data_center) is None: - add_host( + if objects.topo_objects.get(data_center) is None: + objects.add_host( host=data_center, emblem=get_emblem(EMBLEM_DATA_CENTER, params.get(PARAM_DATA_CENTER)), link2core=False, ) - add_connection(data_center, vsphere_host) - add_host( + connections.add_connection(data_center, vsphere_host) + objects.add_host( host=cluster, emblem=get_emblem(EMBLEM_CLUSTER, params.get(PARAM_CLUSTER)), link2core=False ) - add_connection(cluster, data_center) + connections.add_connection(cluster, data_center) host_systems: Sequence[str] = section_esx_vsphere_clusters[cluster].get('hostsystems', '').split(',') for host_system in host_systems: esx_name: str = adjust_name(host_system, params.get(PARAM_HOST_SYSTEMS, {})) - add_host(host=esx_name) - add_connection(cluster, esx_name) + objects.add_host(host=esx_name) + connections.add_connection(cluster, esx_name) yield Result( state=State.OK, notice='VMware ESX via vSphere agent type of query: Queried host is the vCenter' ) else: # assuming the query type is ESXi host raw_vsphere_host: str = host_name().strip() - add_host(host=raw_vsphere_host) + objects.add_host(host=raw_vsphere_host) vsphere_host = raw_vsphere_host yield Result( state=State.OK, @@ -207,8 +163,8 @@ def check_vsphere_topo( for vm in section_esx_vsphere_virtual_machines: vm_name: str = adjust_name(vm['vm_name'], params.get(PARAM_VM_NAMES, {})) host_system: str = adjust_name(vm['hostsystem'], params.get(PARAM_HOST_SYSTEMS, {})) - add_host(host=vm_name) - add_connection(vm_name, host_system) + objects.add_host(host=vm_name) + connections.add_connection(vm_name, host_system) service_prefix: str = '' ds_services = None @@ -216,18 +172,18 @@ def check_vsphere_topo( if section_esx_vsphere_datastores is not None: data_stores_host: str = 'data_stores' # Anchor for data stores - add_host( + objects.add_host( host=data_stores_host, link2core=False, emblem=get_emblem(EMBLEM_DATA_STORE, params.get(PARAM_DATA_STORE)), ) for ds_name in section_esx_vsphere_datastores: - add_host( + objects.add_host( host=ds_name, link2core=False, emblem=get_emblem(EMBLEM_DATA_STORE, params.get(PARAM_DATA_STORE)), ) - add_connection(ds_name, data_stores_host) + connections.add_connection(ds_name, data_stores_host) if params.get(PARAM_DATA_STORE_AS_SERVICE) is True: query: str = ( @@ -242,12 +198,12 @@ def check_vsphere_topo( service_name: str = ds_service[0] service_id: str = f'{service_name}@{vsphere_host}' ds_name: str = service_name.split('fs_')[-1].split('Filesystem ')[-1] - add_service( + objects.add_service( host=vsphere_host, service=service_name, ) - add_connection(service_id, ds_name) - service_prefix = service_name.split(ds_name)[0] + connections.add_connection(service_id, ds_name) + service_prefix: str = service_name.split(ds_name)[0] query: str = ( 'GET services\n' @@ -258,46 +214,49 @@ def check_vsphere_topo( if (vm_to_data_stores := ls_connection.query(query=query)) is not None: for vm, data_stores in vm_to_data_stores: vm_name: str = adjust_name(vm, params.get(PARAM_VM_NAMES, {})) - if vm_name in objects: + if vm_name in objects.topo_objects: data_stores = data_stores.split(',') for data_store in data_stores: ds_name = data_store.strip().split(' ')[2] if ds_services is not None: service_name = f'{service_prefix}{ds_name}' service_id = f'{service_name}@{vsphere_host}' - add_connection(service_id, vm_name) + connections.add_connection(service_id, vm_name) else: - add_connection(ds_name, vm_name) + connections.add_connection(ds_name, vm_name) - connections.sort() - data_set_vsphere = { + connections.topo_connections.sort() + data_set = { 'version': 1, - 'name': 'vSphere', - 'objects': dict(sorted(objects.items())), - 'connections': connections, + 'name': TOPOLOGY_NAME, + 'objects': dict(sorted(objects.topo_objects.items())), + 'connections': connections.topo_connections, } - save_topology(data=data_set_vsphere, sub_directory=raw_vsphere_host) + save_topology(data=data_set, sub_directory=raw_vsphere_host) # workaround for backend is only picking up topologies from default folder add_dummy_topologies(sub_directory=raw_vsphere_host) # end workaround make_topo_default(sub_directory=raw_vsphere_host, make_default=params.get(PARAM_MAKE_DEFAULT, False)) - yield Result(state=State.OK, summary=f'Objects: {len(objects)}') - yield Result(state=State.OK, summary=f'Connections: {len(connections)}') - yield Result(state=State.OK, notice=f'Written to: {BASE_TOPO_PATH}/{raw_vsphere_host}/data_vsphere.json') + yield Result(state=State.OK, summary=f'Objects: {len(objects.topo_objects)}') + yield Result(state=State.OK, summary=f'Connections: {len(connections.topo_connections)}') + yield Result( + state=State.OK, + notice=f'Written to: {BASE_TOPO_PATH}/{raw_vsphere_host}/data_{TOPOLOGY_NAME.lower()}.json', + ) yield from check_levels( value=(time_ns() - start_time) / 1e9, label='Time taken', - metric_name='vsphere_topo_time_taken', + metric_name=f'{TOPOLOGY_NAME.lower()}_topo_time_taken', boundaries=(0, None), render_func=render.timespan, ) check_plugin_vsphere_topo = CheckPlugin( - name="vsphere_topo", - service_name="vSphere Topology", + name='vsphere_topo', + service_name=f'{TOPOLOGY_NAME} Topology', sections=[ 'esx_vsphere_clusters', 'esx_vsphere_virtual_machines', diff --git a/source/cmk_addons_plugins/vsphere_topo/constants.py b/source/cmk_addons_plugins/vsphere_topo/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..e58585f259bce4965acac724785e3c0a5a9d6d33 --- /dev/null +++ b/source/cmk_addons_plugins/vsphere_topo/constants.py @@ -0,0 +1,43 @@ +#!/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 : vsphere_topo/lib/ruelset_names.py + +from typing import Final + +EMBLEM_CLUSTER: Final[str] = 'icon_plugins_hw' +EMBLEM_DATA_CENTER: Final[str] = 'icon_cloud' +EMBLEM_DATA_STORE: Final[str] = 'icon_services_green' + +ICON_VCENTER: Final[str] = 'icon_topic_hosts' + +PARAM_ADD_DUMMY_TOPOLOGIES: Final[str] = 'add_dummy_topologies' +PARAM_CHANGE_CASE: Final[str] = 'change_case' +PARAM_CLUSTER: Final[str] = 'cluster' +PARAM_CUSTOM_EMBLEM: Final[str] = 'custom_emblem' +PARAM_DATA_CENTER: Final[str] = 'data_center' +PARAM_DATA_STORE: Final[str] = 'data_store' +PARAM_DATA_STORE_AS_SERVICE: Final[str] = 'data_sore_as_service' +PARAM_DEFAULT_EMBLEM: Final[str] = 'default_emblem' +PARAM_DONT_ADD_VC_AS_VM: Final[str] = 'dont_add_vc_as_vm' +PARAM_HOST_SYSTEMS: Final[str] = 'host_systems' +PARAM_KEEP_DOMAIN: Final[str] = 'keep_domain' +PARAM_LOWER: Final[str] = 'lower' +PARAM_MAKE_DEFAULT: Final[str] = 'make_default' +PARAM_NO_CHANGE: Final[str] = 'no_change' +PARAM_NO_EMBLEM: Final[str] = 'no_emblem' +PARAM_PREFIX: Final[str] = 'prefix' +PARAM_UPPER: Final[str] = 'upper' +PARAM_VCENTER: Final[str] = 'vcenter_icon' +PARAM_VM_NAMES: Final[str] = 'vm_names' + +PICTURE_TYPE_EMBLEM: Final[str] = 'emblem' +PICTURE_TYPE_ICON: Final[str] = 'icon' + +RULE_SET_NAME_VSPHERE_TOPO: Final[str] = 'vsphere_topo' +TOPOLOGY_NAME: Final[str] ='vSphere' diff --git a/source/cmk_addons_plugins/vsphere_topo/lib/utils.py b/source/cmk_addons_plugins/vsphere_topo/lib/utils.py index 606ffcd0873a45d94549fdfaccb935ad7c88a7bb..e969d5970c1d75bc35659ab5bfa940f0d4974b9b 100644 --- a/source/cmk_addons_plugins/vsphere_topo/lib/utils.py +++ b/source/cmk_addons_plugins/vsphere_topo/lib/utils.py @@ -12,46 +12,26 @@ from collections.abc import Mapping, MutableMapping, MutableSequence, Sequence from json import dumps as json_dunps, loads as json_loads from os import environ from pathlib import Path -from typing import Any, Final, Tuple +from typing import Final, Tuple from livestatus import MultiSiteConnection, SiteConfigurations, SiteId - -EMBLEM_CLUSTER: Final[str] = 'icon_plugins_hw' -EMBLEM_DATA_CENTER: Final[str] = 'icon_cloud' -EMBLEM_DATA_STORE: Final[str] = 'icon_services_green' - -ICON_VCENTER: Final[str] = 'icon_topic_hosts' - -PARAM_ADD_DUMMY_TOPOLOGIES: Final[str] = 'add_dummy_topologies' -PARAM_CHANGE_CASE: Final[str] = 'change_case' -PARAM_CLUSTER: Final[str] = 'cluster' -PARAM_CUSTOM_EMBLEM: Final[str] = 'custom_emblem' -PARAM_DATA_CENTER: Final[str] = 'data_center' -PARAM_DATA_STORE: Final[str] = 'data_store' -PARAM_DATA_STORE_AS_SERVICE: Final[str] = 'data_sore_as_service' -PARAM_DEFAULT_EMBLEM: Final[str] = 'default_emblem' -PARAM_DONT_ADD_VC_AS_VM: Final[str] = 'dont_add_vc_as_vm' -PARAM_HOST_SYSTEMS: Final[str] = 'host_systems' -PARAM_KEEP_DOMAIN: Final[str] = 'keep_domain' -PARAM_LOWER: Final[str] = 'lower' -PARAM_MAKE_DEFAULT: Final[str] = 'make_default' -PARAM_NO_CHANGE: Final[str] = 'no_change' -PARAM_NO_EMBLEM: Final[str] = 'no_emblem' -PARAM_PREFIX: Final[str] = 'prefix' -PARAM_UPPER: Final[str] = 'upper' -PARAM_VCENTER: Final[str] = 'vcenter_icon' -PARAM_VM_NAMES: Final[str] = 'vm_names' - -PICTURE_TYPE_EMBLEM: Final[str] = 'emblem' -PICTURE_TYPE_ICON: Final[str] = 'icon' - -RULE_SET_NAME_VSPHERE_TOPO: Final[str] = 'vsphere_topo' - -OMD_ROOT = environ["OMD_ROOT"] +from cmk_addons.plugins.vsphere_topo.constants import ( + PARAM_CHANGE_CASE, + PARAM_CUSTOM_EMBLEM, + PARAM_DEFAULT_EMBLEM, + PARAM_KEEP_DOMAIN, + PARAM_LOWER, + PARAM_NO_CHANGE, + PARAM_NO_EMBLEM, + PARAM_PREFIX, + PARAM_UPPER, + TOPOLOGY_NAME, +) +OMD_ROOT = environ['OMD_ROOT'] BASE_TOPO_PATH: Final[str] = f'{OMD_ROOT}/var/check_mk/topology/data' -def adjust_name(name: str, params: Mapping[str, Any]) -> str: +def adjust_name(name: str, params: Mapping[str, object]) -> str: class Case: upper: str = PARAM_UPPER lower: str = PARAM_LOWER @@ -149,8 +129,8 @@ def get_topologies() -> Sequence[str | None]: def add_dummy_topologies(sub_directory: str): path: str = f'{BASE_TOPO_PATH}/default' - if Path(path).exists() and not Path(f'{path}/data_vsphere.json').exists(): # don't overwrite existing topology - dummy_topology = {'version': 1, 'name': 'vSphere', 'objects': {}, 'connections': []} + if Path(path).exists() and not Path(f'{path}/data_{TOPOLOGY_NAME.lower()}.json').exists(): # don't overwrite existing topology + dummy_topology = {'version': 1, 'name': TOPOLOGY_NAME, 'objects': {}, 'connections': []} save_topology( data=dummy_topology, sub_directory='default', @@ -175,14 +155,14 @@ class LiveStatusConnection(object): 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") + _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}) + eval(self.sites_mk.read_text(), {'__builtins__': {}}, {'sites': sites_raw}) for site, data in sites_raw.items(): self.sites.update({site: { @@ -215,3 +195,74 @@ class LiveStatusConnection(object): return data else: return None + + +class TopoObjects(object): + 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, + } + + +class TopoConnections(object): + def __init__(self) -> None: + self.topo_connections: MutableSequence = [] + + def add_connection( + self, + left: str, + right: str, + ): + connection = [left, right] + connection.sort() + self.topo_connections.append([connection, {'line_config': {'css_styles': {'stroke-dasharray': 'unset'}}}]) diff --git a/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py b/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py index 974e6e0b358e14435b77da4bd29dd6c64f2d29d5..e541adba74fcf20b36266d4a25806e3df88ea197 100644 --- a/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py +++ b/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py @@ -24,7 +24,7 @@ from cmk.rulesets.v1.form_specs import ( ) from cmk.rulesets.v1.form_specs.validators import LengthInRange from cmk.rulesets.v1.rule_specs import CheckParameters, HostCondition, Topic -from cmk_addons.plugins.vsphere_topo.lib.utils import ( +from cmk_addons.plugins.vsphere_topo.constants import ( EMBLEM_CLUSTER, EMBLEM_DATA_CENTER, EMBLEM_DATA_STORE, diff --git a/source/packages/vsphere_topo b/source/packages/vsphere_topo index 203c6983fae0bea438a9caaffbee88281d6270dd..2215409ee4e4ec46a2ba7b29d63a08e126166653 100644 --- a/source/packages/vsphere_topo +++ b/source/packages/vsphere_topo @@ -5,14 +5,15 @@ '\n' 'Topolgy is created by: vCenter -> data center(s) ' '->Cluster(s) -> ESX host(s) -> VM(s)\n', - 'download_url': 'https://thl-cmk.hopto.org', + 'download_url': 'https://thl-cmk.hopto.org/gitlab/checkmk/various/vsphere_topo', 'files': {'cmk_addons_plugins': ['vsphere_topo/agent_based/packages.py', 'vsphere_topo/rulesets/packages.py', 'vsphere_topo/lib/utils.py', - 'vsphere_topo/graphing/packages.py']}, + 'vsphere_topo/graphing/packages.py', + 'vsphere_topo/constants.py']}, 'name': 'vsphere_topo', 'title': 'vSphere Topologie', - 'version': '0.0.5-20240714', + 'version': '0.0.5-20240717', 'version.min_required': '2.3.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', 'version.usable_until': '2.4.0b1'}