From d50a4b0d219ed425027755a8171b4c5e519234fe Mon Sep 17 00:00:00 2001 From: Andreas Doehler Date: Sun, 6 Jul 2025 10:41:18 +0200 Subject: [PATCH] Hyper-V integration for CMK --- agents/windows/plugins/hyperv_cluster.ps1 | 150 ++++++++++++++++++ agents/windows/plugins/hyperv_host_csv_io.ps1 | 91 +++++++++++ .../hyperv/agent_based/hyperv_cluster_csv.py | 72 +++++++++ .../agent_based/hyperv_cluster_disks.py | 51 ++++++ .../agent_based/hyperv_cluster_general.py | 49 ++++++ .../agent_based/hyperv_cluster_network.py | 51 ++++++ .../agent_based/hyperv_cluster_nodes.py | 51 ++++++ .../agent_based/hyperv_cluster_roles.py | 105 ++++++++++++ .../agent_based/hyperv_host_io_local.py | 70 ++++++++ .../agent_based/hyperv_host_io_remote.py | 70 ++++++++ .../agent_based/hyperv_vm_checkpoints.py | 106 +++++++++++++ .../hyperv/agent_based/hyperv_vm_general.py | 66 ++++++++ .../agent_based/hyperv_vm_integration.py | 70 ++++++++ .../hyperv/agent_based/hyperv_vm_nic.py | 58 +++++++ .../hyperv/agent_based/hyperv_vm_ram.py | 58 +++++++ .../hyperv/agent_based/hyperv_vm_vhd.py | 77 +++++++++ cmk/plugins/hyperv/graphing/hyperv.py | 38 +++++ cmk/plugins/hyperv/lib.py | 80 ++++++++++ .../hyperv/rulesets/hyperv_cluster_cee.py | 39 +++++ .../rulesets/hyperv_cluster_roles_wato.py | 113 +++++++++++++ .../rulesets/hyperv_vm_integration_wato.py | 102 ++++++++++++ .../rulesets/hyperv_vms_guestinfos_cee.py | 39 +++++ 22 files changed, 1606 insertions(+) create mode 100644 agents/windows/plugins/hyperv_cluster.ps1 create mode 100644 agents/windows/plugins/hyperv_host_csv_io.ps1 create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_csv.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_disks.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_general.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_network.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_nodes.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_cluster_roles.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_host_io_local.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_host_io_remote.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_checkpoints.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_general.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_integration.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_nic.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_ram.py create mode 100644 cmk/plugins/hyperv/agent_based/hyperv_vm_vhd.py create mode 100644 cmk/plugins/hyperv/graphing/hyperv.py create mode 100644 cmk/plugins/hyperv/lib.py create mode 100644 cmk/plugins/hyperv/rulesets/hyperv_cluster_cee.py create mode 100644 cmk/plugins/hyperv/rulesets/hyperv_cluster_roles_wato.py create mode 100644 cmk/plugins/hyperv/rulesets/hyperv_vm_integration_wato.py create mode 100644 cmk/plugins/hyperv/rulesets/hyperv_vms_guestinfos_cee.py diff --git a/agents/windows/plugins/hyperv_cluster.ps1 b/agents/windows/plugins/hyperv_cluster.ps1 new file mode 100644 index 00000000000..22ef70bf608 --- /dev/null +++ b/agents/windows/plugins/hyperv_cluster.ps1 @@ -0,0 +1,150 @@ +# +# all the output should be changed to a tables format with keys as column names +# or better you should get directly json output +# + +function Get-ClusterInfo() +{ +'<<>>' +# get cluster core data +$cluster = Get-Cluster +Write-Host 'cluster.name' $cluster.Name + +# get cluster IP address +$clusterGroup = Get-ClusterGroup | Where-Object { $_.GroupType -eq 'Cluster' } +$clusterIP = Get-ClusterResource | Where-Object { $_.ResourceType -eq 'IP Address' -and $_.OwnerGroup -eq $clusterGroup } | Get-ClusterParameter Address +$clusterSubnet = Get-ClusterResource | Where-Object { $_.ResourceType -eq 'IP Address' -and $_.OwnerGroup -eq $clusterGroup } | Get-ClusterParameter SubnetMask +Write-Host 'cluster.ip' $clusterIP.Value +Write-Host 'cluster.subnet' $clusterSubnet.Value + +# get quorum config and resource +$quorum = Get-ClusterQuorum +Write-Host 'quorum.resourcename' ($quorum.QuorumResource.Name) +Write-Host 'quorum.type' ($quorum.QuorumType) + + +# check for S2D presence +'<<>>' +if ($null -ne (Get-Command Get-ClusterStorageSpacesDirect -ErrorAction SilentlyContinue)) { + $S2D = (Get-ClusterStorageSpacesDirect -ErrorAction SilentlyContinue) + if ($null -ne $S2D) { + Write-Host 'S2D' ($S2D.State) + Write-Host 'S2D.name' ($S2D.Name) + Write-Host 'S2D.cachemode.HDD' ($S2D.CacheModeHDD) + Write-Host 'S2D.cachemode.SSD' ($S2D.CacheModeSSD) + Write-Host 'S2D.cache.device_model' ($S2D.CacheDeviceModel) + Write-Host 'S2D.cache.metadata_reserve_bytes' ($S2D.CacheMetadataReserveBytes) + Write-Host 'S2D.cache.page_size_kbytes' ($S2D.CachePageSizeKBytes) + + } +} + +# get cluster nodes +'<<>>' +$clusterNodes = (Get-ClusterNode | Sort-Object ID) +Write-Host 'cluster.number_of_nodes' ($clusterNodes.Count) + +foreach ($clusterNode in $clusterNodes) +{ + Write-Host 'cluster.node.name' ($clusterNode.Name) + Write-Host 'cluster.node.id ' ($clusterNode.ID) + Write-Host 'cluster.node.state' ($clusterNode.State) + Write-Host 'cluster.node.info' ($clusterNoder.StatusInformation) + Write-Host 'cluster.node.weight' ($clusterNode.NodeWeight) + Write-Host 'cluster.node_vendor.manufacturer' ($clusterNode.Manufacturer) + Write-Host 'cluster.node_vendor.model' ($clusterNode.Model) + Write-Host 'cluster.node_vendor.serial' ($clusterNode.SerialNumber) +} + +# get cluster networks +'<<>>' +$clusterNetworks = (Get-ClusterNetwork | Sort-Object Name) +Write-Host 'cluster.number_of_networks' ($clusterNetworks.Count) +foreach($clusterNetwork in $clusterNetworks) +{ + Write-Host 'cluster.network.name' ($clusterNetwork.Name) + Write-Host 'cluster.network.role' ([string]$clusterNetwork.Role) + Write-Host 'cluster.network.state' ($clusterNetwork.State) + Write-Host 'cluster.network.ip' ($clusterNetwork.Address) + Write-Host 'cluster.network.netmask' ($clusterNetwork.Addressmask) + Write-Host 'cluster.network.ipv4_address' ($clusterNetwork.Ipv4Addresses) + Write-Host 'cluster.network.ipv6_address' ($clusterNetwork.Ipv6Addresses) +} + +# get cluster disks (non-CSV first, then CSV) +'<<>>' +$ClusterDisks = (Get-ClusterResource | Where-Object {$_.ResourceType -eq 'Physical Disk'} | Sort-Object OwnerGroup,Name) +Write-Host 'cluster.number_of_disks' ($ClusterDisks.Count) + +foreach ($Disk in $ClusterDisks) +{ + Write-Host 'cluster.disk.name' ($Disk.Name) + Write-Host 'cluster.disk.owner_group' ($Disk.OwnerGroup) + Write-Host 'cluster.disk.owner_node' ($Disk.OwnerNode) + Write-Host 'cluster.disk.state' ($Disk.State) + +} + +# get CSVs +'<<>>' +$clusterSharedVolume = (Get-ClusterSharedVolume -Cluster $cluster | Sort-Object Name) +Write-Host 'cluster.number_of_csv' ($clusterSharedVolume.Count) + +foreach ($CSV in $clusterSharedVolume) +{ + Write-Host 'cluster.csv.name' ($CSV.Name) + Write-Host 'cluster.csv.owner' ($CSV.OwnerNode) + foreach ($CSVInfo in $CSV.SharedVolumeInfo) + { + Write-Host 'cluster.csv.volume' ($CSVInfo.FriendlyVolumeName) + $CSVVolume = Get-ClusterSharedVolume -Name $CSV.Name | select -Expand SharedVolumeInfo | select -Expand Partition + Write-Host 'cluster.csv.size' ($CSVVolume.Size) + Write-Host 'cluster.csv.free_space' ($CSVVolume.FreeSpace) + Write-Host 'cluster.csv.used_space' ($CSVVolume.UsedSpace) + } +} + +# get clustered VMs +'<<>>' +$arrClusterVMs = (Get-ClusterGroup -Cluster $cluster | Where-Object {$_.GroupType -eq 'VirtualMachine'} | Sort-Object Name) +if ($arrClusterVMs.length -ne 0) +{ + Write-Host 'cluster.number_of_vms' $arrClusterVMs.length + foreach ($clusterVM in $arrClusterVMs) + { + Write-Host 'cluster.vm.name' ($clusterVM.Name) + Write-Host 'cluster.vm.state' ($clusterVM.State) + Write-Host 'cluster.vm.owner' ($clusterVM.OwnerNode) + } +} + +# get additional cluster roles +'<<>>' +$arrRolesToIgnore = 'VirtualMachine', 'Cluster', 'AvailableStorage' +$arrClusterGroups = (Get-ClusterGroup -Cluster $cluster | Where-Object {$arrRolesToIgnore -notcontains $_.GroupType} | Sort-Object Name) +if ($arrClusterGroups.length -ne 0) +{ + Write-Host 'cluster.number_of_roles' ($arrClusterGroups.length) + + foreach ($clusterRole in $arrClusterGroups) + { + Write-Host 'cluster.role.name' ($clusterRole.Name) + Write-Host 'cluster.role.state' ($clusterRole.State) + Write-Host 'cluster.role.type' ($clusterRole.GroupType) + Write-Host 'cluster.role.owner' ($clusterRole.OwnerNode) + } +} + +# determine CAU availability +'<<>>' +$CAU = (Get-ClusterResource -Cluster $cluster | Where-Object { $_.ResourceType -eq 'ClusterAwareUpdatingResource' }) +if ($null -ne $CAU) { + Write-Host 'cluster.cau.state' ($CAU.State) + Write-Host 'cluster.cau.name' ($CAU.OwnerGroup) +} else { + Write-Host 'cluster.cau.state not installed' + Write-Host 'cluster.cau.name -' +} +} + +Get-ClusterInfo diff --git a/agents/windows/plugins/hyperv_host_csv_io.ps1 b/agents/windows/plugins/hyperv_host_csv_io.ps1 new file mode 100644 index 00000000000..5762d962e86 --- /dev/null +++ b/agents/windows/plugins/hyperv_host_csv_io.ps1 @@ -0,0 +1,91 @@ +# +# all the output should be changed to a tables format with keys as column names +# or better you should get directly json output +# + + +"<<>>" + +$MaxSamples = 1 +$Interval = 1 + +$Counters = @('\PhysicalDisk(*)\Current Disk Queue Length','\PhysicalDisk(*)\% Disk Time','\PhysicalDisk(*)\Avg. Disk Queue Length','\PhysicalDisk(*)\% Disk Read Time','\PhysicalDisk(*)\Avg. Disk Read Queue Length','\PhysicalDisk(*)\% Disk Write Time','\PhysicalDisk(*)\Avg. Disk Write Queue Length','\PhysicalDisk(*)\Avg. Disk sec/Transfer','\PhysicalDisk(*)\Avg. Disk sec/Read','\PhysicalDisk(*)\Avg. Disk sec/Write','\PhysicalDisk(*)\Disk Transfers/sec','\PhysicalDisk(*)\Disk Reads/sec','\PhysicalDisk(*)\Disk Writes/sec','\PhysicalDisk(*)\Disk Bytes/sec','\PhysicalDisk(*)\Disk Read Bytes/sec','\PhysicalDisk(*)\Disk Write Bytes/sec','\PhysicalDisk(*)\Avg. Disk Bytes/Transfer','\PhysicalDisk(*)\Avg. Disk Bytes/Read','\PhysicalDisk(*)\Avg. Disk Bytes/Write','\PhysicalDisk(*)\% Idle Time','\PhysicalDisk(*)\Split IO/Sec') + +$Splat = @{ + Counter = $Counters + MaxSamples = $MaxSamples + SampleInterval = $Interval +} + +$customobjects = @() + +Get-Counter @Splat | ForEach { + $_.CounterSamples | ForEach { + $customobjects += [pscustomobject]@{ + Path = $_.Path + Value = $_.CookedValue + } + } +} + +$counts = @() +$remotecounts = @() +$idtolun = @{} + +$csvs = Get-ClusterSharedVolume +foreach ( $csv in $csvs ) + { + if ($csv | Where-Object {$_.OwnerNode -match $env:COMPUTERNAME}) + { + $diskguid = ($csv | Get-ClusterParameter DiskIdGuid).Value + $lunid = $csv.Name + $disk = get-disk | Where-Object {$_.guid -match $diskguid} + $diskid = $disk.DiskNumber + $idtolun[$diskid] = $lunid + $counts += $disk + } + else { + $diskguid = ($csv | Get-ClusterParameter DiskIdGuid).Value + $lunid = $csv.Name + $disk = get-disk | Where-Object {$_.guid -match $diskguid} + $diskid = $disk.DiskNumber + $idtolun[$diskid] = $lunid + $remotecounts += $disk + } + } + +$resultlist = @() + +foreach ( $volume in $counts) { + foreach ( $element in $customobjects) { + $checkString = $volume.Number + if ($element.Path -match "\($checkString\)") { + $element.Path = [regex]::Replace($element.Path, '\\\\.*\\physicaldisk\(.*\)\\',$idtolun[$checkString]+'\') + $resultlist += $element + } + } +} + +$hostname = $env:COMPUTERNAME.ToLower() + +$resultlist | select Path, Value | % { + $_.Path = [regex]::Replace($_.Path, "\\\\$hostname\\physicaldisk\([0-9]+\)",""); + $_.Value = $_.Value; + return $_; +} | ft -HideTableHeaders + +'<<>>' + +$resultlist = @() + +foreach ( $volume in $remotecounts) { + foreach ( $element in $customobjects) { + $checkString = $volume.Number + if ($element.Path -match "\($checkString\)") { + $element.Path = [regex]::Replace($element.Path, '\\\\.*\\physicaldisk\(.*\)\\',$idtolun[$checkString]+'\') + $resultlist += $element + } + } +} + +$resultlist | ft -HideTableHeaders diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_csv.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_csv.py new file mode 100644 index 00000000000..b2403f856f7 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_csv.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, + get_value_store, + render, +) +from cmk.plugins.lib.df import FILESYSTEM_DEFAULT_PARAMS, df_check_filesystem_single +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_cluster_csv(section: Section) -> DiscoveryResult: + for csv in section.keys(): + yield Service(item=csv) + + +def check_hyperv_cluster_csv( + item: str, params: Mapping[str, Any], section: Section +) -> CheckResult: + value_store = get_value_store() + csv = section.get(item, "") + + if not csv: + yield Result(state=State(3), summary="CSV not found in agent output") + return + + mega = 1024.0 * 1024.0 + size_total = int(csv.get("cluster.csv.size")) / mega + size_avail = int(csv.get("cluster.csv.free_space")) / mega + + if section.get("ignore_levels"): + message = f"Total size: {render.bytes(size_total)}, Used space is ignored" + yield Result(state=State(0), summary=message) + else: + yield from df_check_filesystem_single( + value_store, + item, + size_total, + size_avail, + 0, + None, + None, + params=params, + ) + + +agent_section_hyperv_cluster_csv = AgentSection( + name="hyperv_cluster_csv", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_cluster_csv = CheckPlugin( + name="hyperv_cluster_csv", + service_name="HyperV CSV %s", + sections=["hyperv_cluster_csv"], + discovery_function=discovery_hyperv_cluster_csv, + check_function=check_hyperv_cluster_csv, + check_ruleset_name="filesystem", + check_default_parameters=FILESYSTEM_DEFAULT_PARAMS, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_disks.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_disks.py new file mode 100644 index 00000000000..a4a0196a8ab --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_disks.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import parse_hyperv + + +def discovery_hyperv_cluster_disks(section) -> DiscoveryResult: + for disk in section.keys(): + yield Service(item=disk) + + +def check_hyperv_cluster_disks(item: str, section) -> CheckResult: + + disk = section.get(item, "") + + if not disk: + yield Result(state=State(3), summary="Disk not found in agent output") + return + + state = 0 + if disk["cluster.disk.state"] != "Online": + state = 3 + message = "is %s, with owner %s and group %s." % ( + disk["cluster.disk.state"], + disk["cluster.disk.owner_node"], + disk["cluster.disk.owner_group"], + ) + yield Result(state=State(state), summary=message) + + +agent_section_hyperv_cluster_disks = AgentSection( + name="hyperv_cluster_disks", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_cluster_disks = CheckPlugin( + name="hyperv_cluster_disks", + service_name="HyperV Disk %s", + sections=["hyperv_cluster_disks"], + discovery_function=discovery_hyperv_cluster_disks, + check_function=check_hyperv_cluster_disks, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_general.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_general.py new file mode 100644 index 00000000000..2324654e53e --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_general.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import hyperv_vm_convert + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_cluster_general(section) -> DiscoveryResult: + if section: + yield Service() + + +def check_hyperv_cluster_general(section: Section) -> CheckResult: + if section: + name = section.get("cluster.name", "") + quorum = section.get("quorum.resourcename", "") + ip = section.get("cluster.ip", "") + quorum_typ = section.get("quorum.type", "") + + message = f"Hyper-V Cluster {name} with IP {ip} and quorum {quorum} as {quorum_typ} quorum." + + yield Result(state=State(0), summary=message) + + +agent_section_hyperv_cluster_general = AgentSection( + name="hyperv_cluster_general", + parse_function=hyperv_vm_convert, +) + +check_plugin_hyperv_cluster_general = CheckPlugin( + name="hyperv_cluster_general", + service_name="HyperV Cluster Status", + sections=["hyperv_cluster_general"], + discovery_function=discovery_hyperv_cluster_general, + check_function=check_hyperv_cluster_general, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_network.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_network.py new file mode 100644 index 00000000000..e81b73ba156 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_network.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_cluster_network(section) -> DiscoveryResult: + for network in section.keys(): + yield Service(item=network) + + +def check_hyperv_cluster_network(item: str, section: Section) -> CheckResult: + + network = section.get(item, "") + + if not network: + yield Result(state=State(3), summary="Network not found in agent output") + + state = 0 + if network["cluster.network.state"] != "Up": + state = 3 + message = f"is {network['cluster.network.state']}, has address {network['cluster.network.ip']} and role {network['cluster.network.role']}." + yield Result(state=State(state), summary=message) + + +agent_section_hyperv_cluster_network = AgentSection( + name="hyperv_cluster_network", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_cluster_network = CheckPlugin( + name="hyperv_cluster_network", + service_name="HyperV Network %s", + sections=["hyperv_cluster_network"], + discovery_function=discovery_hyperv_cluster_network, + check_function=check_hyperv_cluster_network, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_nodes.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_nodes.py new file mode 100644 index 00000000000..4a1b5d3495a --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_nodes.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_cluster_nodes(section) -> DiscoveryResult: + for node in section.keys(): + yield Service(item=node) + + +def check_hyperv_cluster_nodes(item: str, section: Section) -> CheckResult: + + node = section.get(item, "") + + if not node: + yield Result(state=State(3), summary="Node not found in agent output") + + state = 0 + if node["cluster.node.state"] != "Up": + state = 3 + message = f"is {node['cluster.node.state']}, has ID {node['cluster.node.id']} and weight {node['cluster.node.weight']}." + yield Result(state=State(state), summary=message) + + +agent_section_hyperv_cluster_nodes = AgentSection( + name="hyperv_cluster_nodes", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_cluster_nodes = CheckPlugin( + name="hyperv_cluster_nodes", + service_name="HyperV Node %s", + sections=["hyperv_cluster_nodes"], + discovery_function=discovery_hyperv_cluster_nodes, + check_function=check_hyperv_cluster_nodes, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_cluster_roles.py b/cmk/plugins/hyperv/agent_based/hyperv_cluster_roles.py new file mode 100644 index 00000000000..d0ba614d9d8 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_cluster_roles.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +hyperv_cluster_roles_default_levels = { + "states": { + "active": 0, + "inactive": 1, + "Online": 0, + "Offline": 1, + } +} + + +def discovery_hyperv_cluster_roles(section) -> DiscoveryResult: + for vm in section.keys(): + yield Service(item=vm) + + +def check_hyperv_cluster_roles( + item: str, params: Mapping[str, Any], section: Section +) -> CheckResult: + vm = section.get(item, "") + + translate_state = { + "active": "Online", + "inactive": "Offline", + } + + if not vm: + yield Result(state=State(0), summary="VM not found in agent output") + return + + state = 0 + wanted_result = None + wanted_states = params.get("match_services") + + if wanted_states: + for element in wanted_states: + if element.get("service_name") == item: + wanted_state = element.get("state") + wanted_result = translate_state.get(wanted_state) + break + + vm_state = vm.get("cluster.vm.state") + if wanted_result: + if wanted_result == vm_state: + message = "power state: %s" % vm.get("cluster.vm.state") + yield Result(state=State(state), summary=message) + else: + state = 1 + message = "power state: %s - wanted state: %s" % ( + vm.get("cluster.vm.state"), + wanted_state, + ) + yield Result(state=State(state), summary=message) + else: + if params.get("states") == "ignore": + state = 0 + else: + state = hyperv_cluster_roles_default_levels.get("states", {}).get( + vm.get("cluster.vm.state"), 3 + ) + message = "power state: %s" % vm.get("cluster.vm.state") + yield Result(state=State(state), summary=message) + + if vm.get("cluster.vm.owner"): + if vm.get("cluster.vm.state") == "Online": + message = "running on %s" % vm.get("cluster.vm.owner") + yield Result(state=State(0), summary=message) + else: + message = "defined on %s" % vm.get("cluster.vm.owner") + yield Result(state=State(0), summary=message) + + +agent_section_hyperv_cluster_roles = AgentSection( + name="hyperv_cluster_roles", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_cluster_roles = CheckPlugin( + name="hyperv_cluster_roles", + service_name="HyperV VM %s", + sections=["hyperv_cluster_roles"], + discovery_function=discovery_hyperv_cluster_roles, + check_function=check_hyperv_cluster_roles, + check_default_parameters=hyperv_cluster_roles_default_levels, + check_ruleset_name="hyperv_cluster_roles", +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_host_io_local.py b/cmk/plugins/hyperv/agent_based/hyperv_host_io_local.py new file mode 100644 index 00000000000..ae546b41178 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_host_io_local.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- +import time +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, + get_value_store, +) +from cmk.plugins.lib.diskstat import check_diskstat_dict +from cmk.plugins.hyperv.lib import parse_hyperv_io + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_host_io_local(section: Section) -> DiscoveryResult: + for lun in section.keys(): + yield Service(item=lun) + + +def check_hyperv_host_io_local( + item: str, params: Mapping[str, Any], section: Section +) -> CheckResult: + + lun = section.get(item, "") + + if not lun: + yield Result(state=State(3), summary="CSV not found in agent output") + return + + disk = { + "node": None, + "read_ql": float(lun["avg. disk read queue length"].replace(",", ".")), + "write_ql": float(lun["avg. disk write queue length"].replace(",", ".")), + "sec_per_read_counter": float(lun["avg. disk sec/read"].replace(",", ".")), + "sec_per_write_counter": float(lun["avg. disk sec/write"].replace(",", ".")), + "read_ios": float(lun["disk reads/sec"].replace(",", ".")), + "write_ios": float(lun["disk writes/sec"].replace(",", ".")), + "read_throughput": float(lun["disk read bytes/sec"].replace(",", ".")), + "write_throughput": float(lun["disk write bytes/sec"].replace(",", ".")), + } + yield from check_diskstat_dict( + params=params, + disk=disk, + value_store=get_value_store(), + this_time=time.time(), + ) + + +agent_section_hyperv_host_io_local = AgentSection( + name="hyperv_host_io_local", + parse_function=parse_hyperv_io, +) + +check_plugin_hyperv_host_io_local = CheckPlugin( + name="hyperv_host_io_local", + service_name="HyperV IO Local %s", + sections=["hyperv_host_io_local"], + check_default_parameters={}, + discovery_function=discovery_hyperv_host_io_local, + check_function=check_hyperv_host_io_local, + check_ruleset_name="diskstat", +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_host_io_remote.py b/cmk/plugins/hyperv/agent_based/hyperv_host_io_remote.py new file mode 100644 index 00000000000..e78256500a0 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_host_io_remote.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- +import time +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, + get_value_store, +) +from cmk.plugins.lib.diskstat import check_diskstat_dict +from cmk.plugins.hyperv.lib import parse_hyperv_io + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_host_io_remote(section: Section) -> DiscoveryResult: + for lun in section.keys(): + yield Service(item=lun) + + +def check_hyperv_host_io_remote( + item: str, params: Mapping[str, Any], section: Section +) -> CheckResult: + lun = section.get(item, "") + + if not lun: + yield Result(state=State(3), summary="CSV not found in agent output") + return + + yield from check_diskstat_dict( + params=params, + disk={ + "node": None, + "read_ql": float(lun["avg. disk read queue length"].replace(",", ".")), + "write_ql": float(lun["avg. disk write queue length"].replace(",", ".")), + "sec_per_read_counter": float(lun["avg. disk sec/read"].replace(",", ".")), + "sec_per_write_counter": float( + lun["avg. disk sec/write"].replace(",", ".") + ), + "read_ios": float(lun["disk reads/sec"].replace(",", ".")), + "write_ios": float(lun["disk writes/sec"].replace(",", ".")), + "read_throughput": float(lun["disk read bytes/sec"].replace(",", ".")), + "write_throughput": float(lun["disk write bytes/sec"].replace(",", ".")), + }, + value_store=get_value_store(), + this_time=time.time(), + ) + + +agent_section_hyperv_host_io_remote = AgentSection( + name="hyperv_host_io_remote", + parse_function=parse_hyperv_io, +) + +check_plugin_hyperv_host_io_remote = CheckPlugin( + name="hyperv_host_io_remote", + service_name="HyperV IO Remote %s", + sections=["hyperv_host_io_remote"], + check_default_parameters={}, + discovery_function=discovery_hyperv_host_io_remote, + check_function=check_hyperv_host_io_remote, + check_ruleset_name="diskstat", +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_checkpoints.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_checkpoints.py new file mode 100644 index 00000000000..f66e7ad45c3 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_checkpoints.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +import time +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Metric, + Result, + Service, + State, + render, +) +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_vm_checkpoints(section) -> DiscoveryResult: + if section: + yield Service() + + +def check_hyperv_vm_checkpoints( + params: Mapping[str, Any], section: Section +) -> CheckResult: + if len(section) > 0: + last_checkpoint = float("inf") + last_checkpoint_name = "" + oldest_checkpoint = 0 + + for checkpoint in section: + checkpoint_date = section[checkpoint].get("checkpoint.created") + checkpoint_time = time.strptime(checkpoint_date, "%d.%m.%Y %H:%M:%S") + checkpoint_age = time.time() - time.mktime(checkpoint_time) + if checkpoint_age > oldest_checkpoint: + oldest_checkpoint = checkpoint_age + oldest_checkpoint_name = checkpoint + if checkpoint_age < last_checkpoint: + last_checkpoint = checkpoint_age + last_checkpoint_name = checkpoint + + if params.get("age_oldest"): + warn, crit = params["age_oldest"] + if crit < warn: + crit = float("inf") + if oldest_checkpoint > crit: + message = f"Oldest Checkpoint is older than {render.timespan(crit)}" + yield Result(state=State(2), summary=message) + elif oldest_checkpoint > warn: + message = f"Oldest Checkpoint is older than {render.timespan(warn)}" + yield Result(state=State(1), summary=message) + yield Metric( + name="age_oldest", + value=oldest_checkpoint, + levels=(warn, crit), + ) + + if params.get("age"): + warn, crit = params["age"] + if crit < warn: + crit = float("inf") + if last_checkpoint > crit: + message = f"Last Checkpoint is older than { render.timespan(crit)}" + yield Result(state=State(2), summary=message) + elif last_checkpoint > warn: + message = f"Last Checkpoint is older than {render.timespan(warn)}" + yield Result(state=State(1), summary=message) + yield Metric( + name="age_last", + value=last_checkpoint, + levels=(warn, crit), + ) + else: + message = f"Last Checkpoint: {last_checkpoint_name} is {render.timespan(last_checkpoint)} old" + + yield Result(state=State(0), summary=message) + + yield Metric( + name="age_last", + value=last_checkpoint, + ) + + else: + yield Result(state=State(0), summary="No Checkpoints existing") + + +agent_section_hyperv_vm_checkpoints = AgentSection( + name="hyperv_vm_checkpoints", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_vm_checkpoints = CheckPlugin( + name="hyperv_vm_checkpoints", + service_name="HyperV Checkpoints", + sections=["hyperv_vm_checkpoints"], + check_default_parameters={}, + discovery_function=discovery_hyperv_vm_checkpoints, + check_function=check_hyperv_vm_checkpoints, + check_ruleset_name="vm_snapshots", +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_general.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_general.py new file mode 100644 index 00000000000..ffae216aaf5 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_general.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import hyperv_vm_convert + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_vm_general_name(section: Section) -> DiscoveryResult: + if "name" in section: + yield Service() + + +def check_hyperv_vm_general_name(section: Section) -> CheckResult: + yield Result(state=State(0), summary=section["name"]) + + +agent_section_hyperv_vm_general = AgentSection( + name="hyperv_vm_general", + parse_function=hyperv_vm_convert, +) + +check_plugin_hyperv_vm_general = CheckPlugin( + name="hyperv_vm_general", + service_name="HyperV Name", + sections=["hyperv_vm_general"], + discovery_function=discovery_hyperv_vm_general_name, + check_function=check_hyperv_vm_general_name, +) + + +def discovery_hyperv_vm_general_running_on(section): + if "runtime.host" in section: + yield Service() + + +def check_hyperv_vm_general_running_on(section: Section) -> CheckResult: + running_on = section.get("runtime.host") + state = section.get("runtime.powerState", "unknown") + + if not running_on: + yield Result(state=State(3), summary="Runtime host information is missing") + + message = f"Running on {running_on} with state {state}" + yield Result(state=State(0), summary=message) + + +check_plugin_hyperv_vm_general_running_on = CheckPlugin( + name="hyperv_vm_general_running_on", + service_name="HyperV Hostsystem", + sections=["hyperv_vm_general"], + discovery_function=discovery_hyperv_vm_general_running_on, + check_function=check_hyperv_vm_general_running_on, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_integration.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_integration.py new file mode 100644 index 00000000000..5249bb5757a --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_integration.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import hyperv_vm_convert + +Section = Dict[str, Mapping[str, Any]] + +hyperv_vm_integration_default_levels = { + "default_status": "active", + "match_services": [("Guest Service Interface", "inactive")], +} + + +def discovery_hyperv_vm_integration(section) -> DiscoveryResult: + if "guest.tools.number" in section: + yield Service() + + +def check_hyperv_vm_integration( + params: Mapping[str, Any], section: Section +) -> CheckResult: + is_state = { + "active": 0, + "inactive": 1, + } + for key in section: + if key.startswith("guest.tools.service"): + service = key.replace("guest.tools.service.", "").replace("_", " ") + if service in (item.get("service_name") for item in params["match_services"]): + serv_params = "" + for element in params["match_services"]: + if element.get("service_name") == service: + serv_params = element.get("state") + break + if section[key] == serv_params: + yield Result(state=State(0), summary=f"{service} - {section[key]}") + + else: + yield Result(state=State(1), summary=f"{service} - {section[key]}") + else: + state = is_state.get(section[key], 3) + yield Result(state=State(state), summary=f"{service} - {section[key]}") + + +agent_section_hyperv_vm_integration = AgentSection( + name="hyperv_vm_integration", + parse_function=hyperv_vm_convert, +) + +check_plugin_hyperv_vm_integration = CheckPlugin( + name="hyperv_vm_integration", + service_name="HyperV Integration Services", + sections=["hyperv_vm_integration"], + discovery_function=discovery_hyperv_vm_integration, + check_function=check_hyperv_vm_integration, + check_default_parameters=hyperv_vm_integration_default_levels, + check_ruleset_name="hyperv_vm_integration", +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_nic.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_nic.py new file mode 100644 index 00000000000..67086b1ad83 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_nic.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_vm_nic(section) -> DiscoveryResult: + for key, values in section.items(): + if "nic.connectionstate" in values: + yield Service(item=key) + + +def check_hyperv_vm_nic(item: str, section: Section) -> CheckResult: + data = section.get(item) + + if not data: + yield Result(state=State(0), summary="NIC information is missing") + return + + connection_state = data.get("nic.connectionstate", False) + vswitch = data.get("nic.vswitch", "no vSwitch") + vlan_id = data.get("nic.VLAN.id", 0) + # vlan_mode = data.get("nic.VLAN.mode", "Access") + + if connection_state == "True": + message = f"{item} connected to {vswitch} with VLAN ID {vlan_id}" + yield Result(state=State(0), summary=message) + else: + message = f"{item} disconnected" + yield Result(state=State(1), summary=message) + + +agent_section_hyperv_vm_nic = AgentSection( + name="hyperv_vm_nic", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_vm_nic = CheckPlugin( + name="hyperv_vm_nic", + service_name="HyperV NIC %s", + sections=["hyperv_vm_nic"], + discovery_function=discovery_hyperv_vm_nic, + check_function=check_hyperv_vm_nic, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_ram.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_ram.py new file mode 100644 index 00000000000..84477545c17 --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_ram.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, +) +from cmk.plugins.hyperv.lib import hyperv_vm_convert + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_vm_ram(section) -> DiscoveryResult: + if "config.hardware.RAMType" in section: + yield Service() + + +def check_hyperv_vm_ram(section: Section) -> CheckResult: + if not section: + yield Result(state=State(3), summary="RAM information is missing") + + elif section.get("config.hardware.RAMType") == "Dynamic Memory": + message = ( + "Dynamic Memory configured with %s MB minimum and %s MB maximum - start %s MB" + % ( + section.get("config.hardware.MinRAM", "missing"), + section.get("config.hardware.MaxRAM", "missing"), + section.get("config.hardware.StartRAM", "missing"), + ) + ) + else: + message = "Static Memory configured with %s MB" % section.get( + "config.hardware.RAM", "missing" + ) + + yield Result(state=State(0), summary=message) + + +agent_section_hyperv_vm_ram = AgentSection( + name="hyperv_vm_ram", + parse_function=hyperv_vm_convert, +) + +check_plugin_hyperv_vm_ram = CheckPlugin( + name="hyperv_vm_ram", + service_name="HyperV RAM", + sections=["hyperv_vm_ram"], + discovery_function=discovery_hyperv_vm_ram, + check_function=check_hyperv_vm_ram, +) diff --git a/cmk/plugins/hyperv/agent_based/hyperv_vm_vhd.py b/cmk/plugins/hyperv/agent_based/hyperv_vm_vhd.py new file mode 100644 index 00000000000..86c61f0c46d --- /dev/null +++ b/cmk/plugins/hyperv/agent_based/hyperv_vm_vhd.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# # -*- encoding: utf-8; py-indent-offset: 4 -*- + +# from cmk.base.check_legacy_includes.df import * +# from cmk.base.check_legacy_includes.size_trend import * + +from collections.abc import Mapping +from typing import Any, Dict + +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + Result, + Service, + State, + get_value_store, +) +from cmk.plugins.lib.df import FILESYSTEM_DEFAULT_PARAMS, df_check_filesystem_single +from cmk.plugins.hyperv.lib import parse_hyperv + +Section = Dict[str, Mapping[str, Any]] + + +def discovery_hyperv_vm_vhd(section) -> DiscoveryResult: + for key, values in section.items(): + if "vhd.path" in values: + yield Service(item=key) + + +def check_hyperv_vm_vhd( + item: str, params: Mapping[str, Any], section: Section +) -> CheckResult: + + value_store = get_value_store() + disk_types = { + "Differencing": (0, "Differencing disk size"), + "Dynamic": (0, "Dynamic disk size"), + "Fixed": (0, "Fixed disk size"), + "Unknown": (3, "Disk type not found"), + } + + data = section.get(item) + + if not data: + yield Result(state=State(3), summary="Drive information is missing") + return + else: + disk_type = data.get("vhd.type", "Unknown") + + disk_status, disk_txt = disk_types.get(disk_type, (3, "Disk type not found")) + yield Result(state=State(disk_status), summary=disk_txt) + + capacity = float(data.get("vhd.maximumcapacity", "0.0").replace(",", ".")) * 1.0 + used_space = float(data.get("vhd.usedcapacity", "0.0").replace(",", ".")) * 1.0 + avail_mb = capacity - used_space + + yield from df_check_filesystem_single( + value_store, item, capacity, avail_mb, 0, None, None, params=params + ) + + +agent_section_hyperv_vm_vhd = AgentSection( + name="hyperv_vm_vhd", + parse_function=parse_hyperv, +) + +check_plugin_hyperv_vm_vhd = CheckPlugin( + name="hyperv_vm_vhd", + service_name="HyperV VHD %s", + sections=["hyperv_vm_vhd"], + discovery_function=discovery_hyperv_vm_vhd, + check_function=check_hyperv_vm_vhd, + check_default_parameters=FILESYSTEM_DEFAULT_PARAMS, + check_ruleset_name="filesystem", +) diff --git a/cmk/plugins/hyperv/graphing/hyperv.py b/cmk/plugins/hyperv/graphing/hyperv.py new file mode 100644 index 00000000000..e4516332293 --- /dev/null +++ b/cmk/plugins/hyperv/graphing/hyperv.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +'''Translation for graphing values''' +# -*- encoding: utf-8; py-indent-offst: 4 -*- + +# (c) Andreas Doehler +# License: GNU General Public License v2 + +from cmk.graphing.v1 import translations + + +translation_hyperv = translations.Translation( + name="hyperv", + check_commands=[ + translations.PassiveCheck("hyperv_cluster_csv"), + translations.PassiveCheck("hyperv_vm_vhd"), + ], + translations={ + "fs_free": translations.ScaleBy(1048576), + "fs_size": translations.ScaleBy(1048576), + "fs_used": translations.ScaleBy(1048576), + "growth": translations.RenameToAndScaleBy( + "fs_growth", + 12.136296296296296, + ), + "overprovisioned": translations.ScaleBy(1048576), + "reserved": translations.ScaleBy(1048576), + "trend": translations.RenameToAndScaleBy( + "fs_trend", + 12.136296296296296, + ), + "trend_hoursleft": translations.ScaleBy(3600), + "uncommitted": translations.ScaleBy(1048576), + "~(?!inodes_used|fs_size|growth|trend|reserved|fs_free|fs_provisioning|uncommitted|overprovisioned|dedup_rate|file_count|fs_used_percent).*$": translations.RenameToAndScaleBy( + "fs_used", + 1048576, + ), + }, +) diff --git a/cmk/plugins/hyperv/lib.py b/cmk/plugins/hyperv/lib.py new file mode 100644 index 00000000000..b8aec3abd4b --- /dev/null +++ b/cmk/plugins/hyperv/lib.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# +# the parse functions should be adapted to json output from the agent scripts +# it would be way better than the current output +# + + +def hyperv_vm_convert(string_table): + parsed = {} + for line in string_table: + parsed[line[0]] = " ".join(line[1:]) + + return parsed + + +counter_translation = { + "durchschnittl. warteschlangenlänge der datenträger-lesevorgänge": "avg. disk read queue length", + "durchschnittl. warteschlangenlänge der datenträger-schreibvorgänge": "avg. disk write queue length", + "mittlere sek./lesevorgänge": "avg. disk sec/read", + "mittlere sek./schreibvorgänge": "avg. disk sec/write", + "lesevorgänge/s": "disk reads/sec", + "schreibvorgänge/s": "disk writes/sec", + "bytes gelesen/s": "disk read bytes/sec", + "bytes geschrieben/s": "disk write bytes/sec", +} + + +def parse_hyperv_io(string_table): + parsed = {} + for line in string_table: + value = line[-1] + data = " ".join(line[:-1]) + _empty, _empty2, host, lun, name = data.split("\\", 4) + if name in counter_translation.keys(): + name = counter_translation[name] + if lun not in parsed: + parsed[lun] = {} + parsed[lun][name] = value + parsed[lun]["node"] = host + return parsed + + +def parse_hyperv(string_table): + datatypes = { + "vhd": "vhd.name", + "nic": "nic.name", + "checkpoints": "checkpoint.name", + "cluster.number_of_nodes": "cluster.node.name", + "cluster.number_of_csv": "cluster.csv.name", + "cluster.number_of_disks": "cluster.disk.name", + "cluster.number_of_vms": "cluster.vm.name", + "cluster.number_of_roles": "cluster.role.name", + "cluster.number_of_networks": "cluster.network.name", + } + + parsed = {} + if len(string_table) == 0: + return parsed + + datatype = datatypes.get(string_table[0][0]) + element = "" + start = False + counter = 1 + for line in string_table: + if line[0] == datatype: + if start is True: + counter += 1 + else: + start = True + if datatype == "nic.name": + element = " ".join(line[1:]) + f" {counter}" + else: + element = " ".join(line[1:]) + parsed[element] = {} + elif start is True: + parsed[element][line[0]] = " ".join(line[1:]) + + return parsed diff --git a/cmk/plugins/hyperv/rulesets/hyperv_cluster_cee.py b/cmk/plugins/hyperv/rulesets/hyperv_cluster_cee.py new file mode 100644 index 00000000000..d7f6bbc352c --- /dev/null +++ b/cmk/plugins/hyperv/rulesets/hyperv_cluster_cee.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +'''Deployment ruleset for Hyper-V Cluster plugins.''' +# -*- encoding: utf-8; py-indent-offst: 4 -*- + +# (c) Andreas Doehler +# License: GNU General Public License v2 + +from cmk.rulesets.v1 import Title, Label, Help +from cmk.rulesets.v1.form_specs import ( + BooleanChoice, + DictElement, + Dictionary, +) +from cmk.rulesets.v1.rule_specs import AgentConfig, Topic + + +def _valuespec_agent_config_hyperv_cluster(): + return Dictionary( + title=Title("Hyper-V Cluster Plugins"), + help_text=Help( + "This plugin checks the status of Hyper-V Cluster." + ), + elements={ + "deploy": DictElement( + parameter_form=BooleanChoice( + label=Label("Deploy plugin for Hyper-V Cluster plugin"), + ), + required=True, + ), + }, + ) + + +rule_spec_agent_config_hyperv_cluster = AgentConfig( + title=Title("Hyper-V Cluster Plugins"), + topic=Topic.WINDOWS, + name="hyperv_cluster", + parameter_form=_valuespec_agent_config_hyperv_cluster, +) diff --git a/cmk/plugins/hyperv/rulesets/hyperv_cluster_roles_wato.py b/cmk/plugins/hyperv/rulesets/hyperv_cluster_roles_wato.py new file mode 100644 index 00000000000..24842ccaaca --- /dev/null +++ b/cmk/plugins/hyperv/rulesets/hyperv_cluster_roles_wato.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + List, + SingleChoice, + SingleChoiceElement, + String, +) +from cmk.rulesets.v1.rule_specs import ( + CheckParameters, + HostAndItemCondition, + LengthInRange, + Topic, +) + + +def _migrate_tuple(value) -> dict: + """ + Convert a list of tuple to a list of dictionary with keys 'service_name' and 'state'. + """ + if isinstance(value, list): + if all(isinstance(item, dict) for item in value): + return value + return [ + { + "service_name": item[0], + "state": item[1], + } + for item in value + if isinstance(item, tuple) and len(item) == 2 + ] + return value + + +def _parameter_valuespec_hyperv_cluster_roles(): + return Dictionary( + elements={ + "default_status": DictElement( + parameter_form=SingleChoice( + title=Title("Default State"), + elements=[ + SingleChoiceElement( + name="active", + title=Title("active"), + ), + SingleChoiceElement( + name="inactive", + title=Title("inactive"), + ), + SingleChoiceElement( + name="ignore", + title=Title("ignore"), + ), + ], + prefill=DefaultValue("active"), + ), + ), + "match_services": DictElement( + parameter_form=List( + title=Title("Special States"), + migrate=_migrate_tuple, + element_template=Dictionary( + elements={ + "service_name": DictElement( + required=True, + parameter_form=String( + title=Title("Service name"), + custom_validate=(LengthInRange(min_value=1),), + ), + ), + "state": DictElement( + required=True, + parameter_form=SingleChoice( + title=Title("State"), + elements=[ + SingleChoiceElement( + name="active", + title=Title("active"), + ), + SingleChoiceElement( + name="inactive", + title=Title("inactive"), + ), + SingleChoiceElement( + name="ignore", + title=Title("ignore"), + ), + ], + ), + ), + } + ), + ), + ), + } + ) + + +rule_spec_hyperv_cluster_roles = CheckParameters( + name="hyperv_cluster_roles", + title=Title("Hyper-V Cluster Role Status"), + topic=Topic.APPLICATIONS, + condition=HostAndItemCondition( + item_title=Title("Cluster Role"), + item_form=String(custom_validate=(LengthInRange(min_value=1),)), + ), + parameter_form=_parameter_valuespec_hyperv_cluster_roles, +) diff --git a/cmk/plugins/hyperv/rulesets/hyperv_vm_integration_wato.py b/cmk/plugins/hyperv/rulesets/hyperv_vm_integration_wato.py new file mode 100644 index 00000000000..364f53a1d7a --- /dev/null +++ b/cmk/plugins/hyperv/rulesets/hyperv_vm_integration_wato.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + List, + SingleChoice, + SingleChoiceElement, + String, +) +from cmk.rulesets.v1.rule_specs import ( + CheckParameters, + HostCondition, + LengthInRange, + Topic, +) + + +def _migrate_tuple(value) -> dict: + """ + Convert a list of tuple to a list of dictionary with keys 'service_name' and 'state'. + """ + if isinstance(value, list): + if all(isinstance(item, dict) for item in value): + return value + return [ + { + "service_name": item[0], + "state": item[1], + } + for item in value + if isinstance(item, tuple) and len(item) == 2 + ] + return value + + +def _parameter_valuespec_hyperv_vm_integration(): + return Dictionary( + elements={ + "default_status": DictElement( + parameter_form=SingleChoice( + title=Title("Default State"), + elements=[ + SingleChoiceElement( + name="active", + title=Title("active"), + ), + SingleChoiceElement( + name="inactive", + title=Title("inactive"), + ), + ], + prefill=DefaultValue("active"), + ), + ), + "match_services": DictElement( + parameter_form=List( + title=Title("Special States"), + migrate=_migrate_tuple, + element_template=Dictionary( + elements={ + "service_name": DictElement( + required=True, + parameter_form=String( + title=Title("Service name"), + custom_validate=(LengthInRange(min_value=1),), + ), + ), + "state": DictElement( + required=True, + parameter_form=SingleChoice( + title=Title("State"), + elements=[ + SingleChoiceElement( + name="active", + title=Title("active"), + ), + SingleChoiceElement( + name="inactive", + title=Title("inactive"), + ), + ], + ), + ), + } + ), + ), + ), + } + ) + + +rule_spec_hyperv_vm_integration = CheckParameters( + name="hyperv_vm_integration", + title=Title("Hyper-V Integration Services Status"), + topic=Topic.APPLICATIONS, + condition=HostCondition(), + parameter_form=_parameter_valuespec_hyperv_vm_integration, +) diff --git a/cmk/plugins/hyperv/rulesets/hyperv_vms_guestinfos_cee.py b/cmk/plugins/hyperv/rulesets/hyperv_vms_guestinfos_cee.py new file mode 100644 index 00000000000..9d480cce7bf --- /dev/null +++ b/cmk/plugins/hyperv/rulesets/hyperv_vms_guestinfos_cee.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +'''Deployment rule for Hyper-V VMs GuestInfos plugin''' +# -*- encoding: utf-8; py-indent-offst: 4 -*- + +# (c) Andreas Doehler +# License: GNU General Public License v2 + +from cmk.rulesets.v1 import Title, Label, Help +from cmk.rulesets.v1.form_specs import ( + BooleanChoice, + DictElement, + Dictionary, +) +from cmk.rulesets.v1.rule_specs import AgentConfig, Topic + + +def _valuespec_agent_config_hyperv_hyperv_vm_info(): + return Dictionary( + title=Title("Hyper-V VMs GuestInfos"), + help_text=Help( + "This plugin checks the status of Hyper-V VMs guestinfos." + ), + elements={ + "deploy": DictElement( + parameter_form=BooleanChoice( + label=Label("Deploy plugin for Hyper-V VMs guestinfos"), + ), + required=True, + ), + }, + ) + + +rule_spec_agent_config_hyperv_hyperv_vm_info = AgentConfig( + title=Title("Hyper-V VMs GuestInfos"), + topic=Topic.WINDOWS, + name="hyperv_vm_info", + parameter_form=_valuespec_agent_config_hyperv_hyperv_vm_info, +)