diff --git a/README.md b/README.md index d12b93ec320423f5fbded656e90cd76380242af1..53f352ca139a2cc30399691968fc328fcc5e2f10 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/vsphere_topo-0.0.2-20240709.mkp "vsphere_topo-0.0.2-20240709.mkp" +[PACKAGE]: ../../raw/master/mkp/vsphere_topo-0.0.3-20240711.mkp "vsphere_topo-0.0.3-20240711.mkp" # vSphere Topology Visualization This plugin uses the data from the _VMware ESX via vSphere_ spezial agent to create a topolofy of the vSphere environment. diff --git a/mkp/vsphere_topo-0.0.3-20240711.mkp b/mkp/vsphere_topo-0.0.3-20240711.mkp new file mode 100644 index 0000000000000000000000000000000000000000..be24721712f3b21055ae229868bc21e987abbc9d Binary files /dev/null and b/mkp/vsphere_topo-0.0.3-20240711.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 401aa8f5a7f38f02389ab5d64214979b331092ea..8c70ca95e9df85996d3c7817cc14d19cdd7adffc 100644 --- a/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py +++ b/source/cmk_addons_plugins/vsphere_topo/agent_based/packages.py @@ -9,8 +9,14 @@ # File : vsphere_topo/agent_based/packages.py # 2024-07-10: added time taken as metric +# 2024-07-11: added clearer error message for missing section_esx_vsphere_clusters section +# added automatic workaround for backend is only picking up topologies from default folder +# -> obsoletes 'Add dummy topologies' +# added vCenter twice (once as vCenter once as vM) with different object IDs +# -> obsoletes 'Don\'t add vCenter as VM' from collections.abc import Mapping, Sequence +from pathlib import Path from time import time_ns from typing import Any @@ -29,20 +35,21 @@ from cmk_addons.plugins.vsphere_topo.lib.utils import ( EMBLEM_CLUSTER, EMBLEM_DATA_CENTER, EMBLEM_DATA_STORE, + ICON_VCENTER, LiveStatusConnection, OMD_ROOT, - PARAM_ADD_DUMMY_TOPOLOGIES, PARAM_CLUSTER, PARAM_DATA_CENTER, PARAM_DATA_STORE, PARAM_DATA_STORE_AS_SERVICE, - PARAM_DONT_ADD_VC_AS_VM, PARAM_HOST_SYSTEMS, PARAM_MAKE_DEFAULT, + PARAM_VCENTER, PARAM_VM_NAMES, RULE_SET_NAME_VSPHERE_TOPO, adjust_name, get_emblem, + get_topologies, save_data_to_file, ) @@ -63,19 +70,27 @@ def check_vsphere_topo( section_esx_vsphere_virtual_machines: Sequence[Section] | None, section_esx_vsphere_datastores: Section | None, ) -> CheckResult: - def add_host(host: str, emblem: str | None = None, link2core: bool = True): - if objects.get(host) is not None: + 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': { - 'emblem': emblem, # node image - }, - } + metadata['images'].update({'emblem': emblem}) # node image + + if icon is not None: + metadata['images'].update({'icon': icon}) # node icon - objects[host] = { + objects[f'{obj_id_prefix}{host}'] = { "name": host, "link": {'core': host} if link2core else {}, 'metadata': metadata, @@ -109,8 +124,14 @@ def check_vsphere_topo( objects = {} connections = [] - vsphere_host: str = host_name().strip() - add_host(host=vsphere_host) + raw_vsphere_host: str = host_name().strip() + add_host( + host=raw_vsphere_host, + obj_id_prefix='vc', + # icon='icon_topic_hosts', + icon=get_emblem(ICON_VCENTER, params.get(PARAM_VCENTER)), + ) + vsphere_host = f'vc{raw_vsphere_host}' __clusters = { 'HA': { @@ -121,7 +142,11 @@ def check_vsphere_topo( } if section_esx_vsphere_clusters is None: - yield Result(state=State.UNKNOWN, summary='Found no vSphere data centers/clusters') + yield Result( + state=State.UNKNOWN, + summary='Found no vSphere data centers/clusters. This service needs the "VMware ESX via vSphere" ' + 'option "Type of query" set to "Queried host is vCenter"' + ) return for cluster in section_esx_vsphere_clusters: @@ -133,7 +158,6 @@ def check_vsphere_topo( link2core=False, ) add_connection(data_center, vsphere_host) - add_host( host=cluster, emblem=get_emblem(EMBLEM_CLUSTER, params.get(PARAM_CLUSTER)), @@ -178,8 +202,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, {})) - if params.get(PARAM_DONT_ADD_VC_AS_VM) is True and vm_name.lower() == vsphere_host.lower(): - continue + # if params.get(PARAM_DONT_ADD_VC_AS_VM) is True and vm_name.lower() == vsphere_host.lower(): + # continue host_system: str = adjust_name(vm['hostsystem'], params.get(PARAM_HOST_SYSTEMS, {})) add_host(host=vm_name) add_connection(vm_name, host_system) @@ -232,8 +256,8 @@ 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 params.get(PARAM_DONT_ADD_VC_AS_VM) is True and vm_name.lower() == vsphere_host.lower(): - continue + # if params.get(PARAM_DONT_ADD_VC_AS_VM) is True and vm_name.lower() == vsphere_host.lower(): + # continue if vm_name in objects: data_stores = data_stores.split(',') for data_store in data_stores: @@ -250,21 +274,30 @@ def check_vsphere_topo( 'version': 1, 'name': 'vSphere', 'objects': dict(sorted(objects.items())), + # 'objects': objects, 'connections': connections, } - # workaround for missing topology - path: str = f'{OMD_ROOT}/var/check_mk/topology/data/{vsphere_host}' - if params.get(PARAM_ADD_DUMMY_TOPOLOGIES) is True: - data_sets = [ - {'version': 1, 'name': 'cdp', 'objects': {}, 'connections': []}, - {'version': 1, 'name': 'lldp', 'objects': {}, 'connections': []}, - {'version': 1, 'name': 'static', 'objects': {}, 'connections': []}, - {'version': 1, 'name': 'l3v4', 'objects': {}, 'connections': []}, - data_set_vsphere - ] - else: - data_sets = [data_set_vsphere] + data_sets = [] + + # workaround for backend is only picking up topologies from default folder + path: str = f'{OMD_ROOT}/var/check_mk/topology/data/default' + if Path(path).exists(): + dummy_topology = {'version': 1, 'name': 'vSphere', 'objects': {}, 'connections': []} + save_data_to_file( + data=dummy_topology, + file='data_vsphere.json', + make_default=params.get(PARAM_MAKE_DEFAULT, False), + path=path, + ) + + for topology in get_topologies(): + data_sets.append({'version': 1, 'name': topology, 'objects': {}, 'connections': []}) + # end workaround + data_sets.append(data_set_vsphere) + + path: str = f'{OMD_ROOT}/var/check_mk/topology/data/{raw_vsphere_host}' + for data_set in data_sets: file: str = f'data_{data_set["name"].lower()}.json' save_data_to_file( diff --git a/source/cmk_addons_plugins/vsphere_topo/lib/utils.py b/source/cmk_addons_plugins/vsphere_topo/lib/utils.py index f32e7177b73dd24b4c7bd247e29097f48ee9f517..2e8ca1da73f64ab834e8b2a12cd6750a3d96e66d 100644 --- a/source/cmk_addons_plugins/vsphere_topo/lib/utils.py +++ b/source/cmk_addons_plugins/vsphere_topo/lib/utils.py @@ -8,8 +8,8 @@ # Date : 2024-07-06 # File : vsphere_topo/lib/utils.py -from collections.abc import Mapping, MutableMapping, MutableSequence -from json import dumps as json_dunps +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 @@ -20,6 +20,8 @@ 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' @@ -38,6 +40,10 @@ PARAM_NO_EMBLEM: Final[str] = 'no_emblem' PARAM_PREFIX: Final[str] = 'prefix' PARAM_UPPER: Final[str] = 'upper' PARAM_VM_NAMES: Final[str] = 'vm_names' +PARAM_VCENTER: Final[str] = 'vcenter_icon' + +PICTURE_TYPE_EMBLEM: Final[str] = 'emblem' +PICTURE_TYPE_ICON: Final[str] = 'icon' RULE_SET_NAME_VSPHERE_TOPO: Final[str] = 'vsphere_topo' @@ -110,6 +116,17 @@ def save_data_to_file(data: Mapping, path: str, file: str, make_default: bool) - Path(f'{parent_path}/default').symlink_to(target=Path(path), target_is_directory=True) +def get_topologies() -> Sequence[str | None]: + path: str = f'{OMD_ROOT}/var/check_mk/topology/data/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()).get('name') for file in files if + json_loads(Path(file).read_text()).get('name') is not None + ] + # # live status # diff --git a/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py b/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py index 800fdae5f755bceee606d3f42c2db259e833becc..bcbb4e5a54e51bc3dcb552d56de30c46eb8c16e6 100644 --- a/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py +++ b/source/cmk_addons_plugins/vsphere_topo/rulesets/packages.py @@ -48,6 +48,10 @@ from cmk_addons.plugins.vsphere_topo.lib.utils import ( PARAM_UPPER, PARAM_VM_NAMES, RULE_SET_NAME_VSPHERE_TOPO, + PARAM_VCENTER, + ICON_VCENTER, + PICTURE_TYPE_EMBLEM, + PICTURE_TYPE_ICON, ) adjust_names_elements: Mapping[str, DictElement] = { @@ -75,25 +79,25 @@ adjust_names_elements: Mapping[str, DictElement] = { } -def get_emblem_element(default_emblem) -> Sequence[CascadingSingleChoiceElement]: +def get_emblem_element(default_emblem: str, picture_type: str) -> Sequence[CascadingSingleChoiceElement]: return [ CascadingSingleChoiceElement( name=PARAM_NO_EMBLEM, - title=Title('No custom emblem'), + title=Title(f'No custom {picture_type}'), parameter_form=FixedValue( value=True, - label=Label('No custom emblem will be used') + label=Label(f'No custom {picture_type} will be used') )), CascadingSingleChoiceElement( name=PARAM_DEFAULT_EMBLEM, - title=Title('Use default emblem'), + title=Title(f'Use default {picture_type}'), parameter_form=FixedValue( value=True, - label=Label(f'Emblem "{default_emblem}" will be used') + label=Label(f'"{default_emblem}" will be used as {picture_type}') )), CascadingSingleChoiceElement( name=PARAM_CUSTOM_EMBLEM, - title=Title('Use custom emblem'), + title=Title(f'Use custom {picture_type}'), parameter_form=String( custom_validate=(LengthInRange(min_value=1),), prefill=DefaultValue(default_emblem), @@ -114,10 +118,20 @@ def _parameter_form() -> Dictionary: title=Title('Adjust VM names'), elements=adjust_names_elements )), + PARAM_VCENTER: DictElement( + parameter_form=CascadingSingleChoice( + title=Title('vCenter Icon'), + elements=get_emblem_element(ICON_VCENTER, PICTURE_TYPE_ICON), + prefill=DefaultValue(PARAM_DEFAULT_EMBLEM), + help_text=Help( + 'Here you can change the icon for the vCenter object. If you use the built-in icons prefix ' + 'the name with "icon_"' + ), + )), PARAM_CLUSTER: DictElement( parameter_form=CascadingSingleChoice( title=Title('Cluster emblem'), - elements=get_emblem_element(EMBLEM_CLUSTER), + elements=get_emblem_element(EMBLEM_CLUSTER, PICTURE_TYPE_EMBLEM), prefill=DefaultValue(PARAM_DEFAULT_EMBLEM), help_text=Help( 'Here you can change the picture for the cluster. If you use the built-in icons prefix ' @@ -127,7 +141,7 @@ def _parameter_form() -> Dictionary: PARAM_DATA_CENTER: DictElement( parameter_form=CascadingSingleChoice( title=Title('Datacenter emblem'), - elements=get_emblem_element(EMBLEM_DATA_CENTER), + elements=get_emblem_element(EMBLEM_DATA_CENTER, PICTURE_TYPE_EMBLEM), prefill=DefaultValue(PARAM_DEFAULT_EMBLEM), help_text=Help( 'Here you can change the picture for the datacenter. If you use the built-in icons prefix ' @@ -137,7 +151,7 @@ def _parameter_form() -> Dictionary: PARAM_DATA_STORE: DictElement( parameter_form=CascadingSingleChoice( title=Title('Datastore emblem'), - elements=get_emblem_element(EMBLEM_DATA_STORE), + elements=get_emblem_element(EMBLEM_DATA_STORE, PICTURE_TYPE_EMBLEM), prefill=DefaultValue(PARAM_DEFAULT_EMBLEM), help_text=Help( 'Here you can change the picture for the datastore. If you use the built-in icons prefix ' @@ -165,6 +179,7 @@ def _parameter_form() -> Dictionary: value=True )), PARAM_DONT_ADD_VC_AS_VM: DictElement( + render_only=True, parameter_form=FixedValue( title=Title('Don\'t add vCenter as VM'), label=Label('The vCenter will not be added as VM'), @@ -175,6 +190,7 @@ def _parameter_form() -> Dictionary: value=True )), PARAM_ADD_DUMMY_TOPOLOGIES: DictElement( + render_only=True, parameter_form=FixedValue( title=Title('Add dummy topologies'), label=Label('Adds empty CDP, LLDP, L3v4 and STATIC topology'), diff --git a/source/packages/vsphere_topo b/source/packages/vsphere_topo index 142ae810548317ff10beaef62ee407091554bdb1..d74824776f4ca9fcf4b12a46c20c29d2246d7655 100644 --- a/source/packages/vsphere_topo +++ b/source/packages/vsphere_topo @@ -12,7 +12,7 @@ 'vsphere_topo/graphing/packages.py']}, 'name': 'vsphere_topo', 'title': 'vSphere Topologie', - 'version': '0.0.2-20240709', + 'version': '0.0.3-20240711', 'version.min_required': '2.3.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', 'version.usable_until': '2.4.0b1'}