Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
d82a602c8d | |||
977fecdda0 | |||
d7ac4ae68d | |||
48000a39d7 | |||
bbac7b11ca | |||
475fde79c1 | |||
0b42f6485f | |||
aa8e8493a4 | |||
509715cd38 | |||
a4169fb638 | |||
1b4fafb15e | |||
6d39ee351b | |||
1dca9e4a65 | |||
87b27b3b49 | |||
72e13f4204 | |||
1b54b70a8d | |||
d4b0c9497d | |||
270d9ac22c | |||
36a998cfc6 | |||
5fa472f450 | |||
52f417215b | |||
62518edf2c | |||
5ba99c1c15 | |||
8f9970a40c | |||
775f9515a1 | |||
15f69ddc0c | |||
3d9e54f470 | |||
6de602a399 | |||
44aa214b85 | |||
bb424f90fe | |||
|
0aa461839b | ||
|
d7ba6d93f4 | ||
|
c26de603bb | ||
|
b3a43a96b1 | ||
|
0f60e5725e | ||
a1b017d260 | |||
|
2b522d2553 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "juniper"]
|
||||
path = juniper
|
||||
url = https://github.com/spearheadsys/check_mk.git
|
||||
[submodule "check_mk-check-selinux"]
|
||||
path = selinux
|
||||
url = https://code.spearhead.cloud/Spearhead/check_mk-check-selinux.git
|
||||
|
BIN
amd-gpu/amd-gpu-0.1.1.mkp
Executable file
BIN
amd-gpu/amd-gpu-0.1.1.mkp
Executable file
Binary file not shown.
106
amd-gpu/local/lib/check_mk/base/plugins/agent_based/amd_gpu.py
Normal file
106
amd-gpu/local/lib/check_mk/base/plugins/agent_based/amd_gpu.py
Normal file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2024 Spearhead Systems SRL
|
||||
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import (
|
||||
register,
|
||||
Service,
|
||||
Result,
|
||||
Metric,
|
||||
State,
|
||||
)
|
||||
|
||||
|
||||
def discovery_amd_gpu(section):
|
||||
name = section[0][0]
|
||||
yield Service(item=name)
|
||||
|
||||
|
||||
def check_state(alert_percentages, measured_percent):
|
||||
if alert_percentages:
|
||||
if alert_percentages[1] <= measured_percent:
|
||||
return State.CRIT
|
||||
elif alert_percentages[0] <= measured_percent:
|
||||
return State.WARN
|
||||
return State.OK
|
||||
|
||||
|
||||
def get_levels(alert_levels, total=None):
|
||||
if alert_levels == None:
|
||||
return
|
||||
|
||||
if total == None:
|
||||
return alert_levels
|
||||
|
||||
return (alert_levels[0] / 100 * total, alert_levels[1] / 100 * total)
|
||||
|
||||
|
||||
def check_amd_gpu(item, params, section):
|
||||
if item != section[0][0]:
|
||||
return
|
||||
|
||||
gpu_percent = int(float(section[1][0]))
|
||||
vram_bytes_used = int(section[2][0])
|
||||
vram_bytes_total = int(section[3][0])
|
||||
vram_bytes_free = max(0, vram_bytes_total - vram_bytes_used)
|
||||
|
||||
vram_mb_used = vram_bytes_used // 1048576
|
||||
vram_mb_total = vram_bytes_total // 1048576
|
||||
vram_mb_free = vram_bytes_free // 1048576
|
||||
|
||||
alert_gpu_percent = params.get("gpu_percent")
|
||||
alert_vram_used_percent = params.get("vram_used_percent")
|
||||
alert_vram_free_percent = params.get("vram_free_percent")
|
||||
|
||||
vram_used_percent = vram_bytes_used / vram_bytes_total * 100
|
||||
vram_free_percent = 100 - vram_used_percent
|
||||
|
||||
yield Result(
|
||||
state=check_state(alert_gpu_percent, gpu_percent),
|
||||
summary=f"GPU: {gpu_percent}%"
|
||||
)
|
||||
|
||||
yield Result(
|
||||
state=check_state(alert_vram_free_percent, vram_free_percent),
|
||||
summary=f"VRAM free: {vram_mb_free} MiB"
|
||||
)
|
||||
|
||||
yield Result(
|
||||
state=check_state(alert_vram_used_percent, vram_used_percent),
|
||||
summary=f"VRAM used: {vram_mb_used} MiB"
|
||||
)
|
||||
|
||||
yield Result(
|
||||
state=State.OK,
|
||||
summary=f"VRAM total: {vram_mb_total} MiB"
|
||||
)
|
||||
|
||||
yield Metric(
|
||||
name="gpu_percent",
|
||||
value=gpu_percent,
|
||||
levels=get_levels(alert_gpu_percent),
|
||||
boundaries=(0, 100)
|
||||
)
|
||||
|
||||
yield Metric(
|
||||
name="vram_used",
|
||||
value=vram_mb_used,
|
||||
levels=get_levels(alert_vram_used_percent, vram_mb_total),
|
||||
boundaries=(0, vram_mb_total)
|
||||
)
|
||||
|
||||
yield Metric(
|
||||
name="vram_free",
|
||||
value=vram_mb_free,
|
||||
levels=get_levels(alert_vram_free_percent, vram_mb_total),
|
||||
boundaries=(0, vram_mb_total)
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name='amd_gpu',
|
||||
service_name='AMD GPU - %s',
|
||||
discovery_function=discovery_amd_gpu,
|
||||
check_function=check_amd_gpu,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name='amd_gpu',
|
||||
)
|
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2024 Spearhead Systems SRL
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersHardware,
|
||||
)
|
||||
from cmk.gui.valuespec import Dictionary, Percentage, TextInput, Tuple
|
||||
|
||||
|
||||
def _parameter_valuespec_amd_gpu():
|
||||
return Dictionary(
|
||||
title=_("GPU utilization"),
|
||||
help=_(
|
||||
"These metrics are queried directly from the AMD GPU. "
|
||||
"Upper and lower levels can be specified for individual metrics."
|
||||
),
|
||||
elements=[
|
||||
(
|
||||
"gpu_percent",
|
||||
Tuple(
|
||||
title=_("GPU Used"),
|
||||
help=_("If usage of total GPU compute goes above these percentages, issue alerts."),
|
||||
elements=[
|
||||
Percentage(
|
||||
title=_("Warn if above"),
|
||||
default_value=90
|
||||
),
|
||||
Percentage(
|
||||
title=_("Crit if above"),
|
||||
default_value=100
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
"vram_free_percent",
|
||||
Tuple(
|
||||
title=_("VRAM Free"),
|
||||
help=_("If free VRAM goes above these percentages, issue alerts."),
|
||||
elements=[
|
||||
Percentage(
|
||||
title="Warn if above",
|
||||
default_value=70
|
||||
),
|
||||
Percentage(
|
||||
title="Crit if above",
|
||||
default_value=90
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
"vram_used_percent",
|
||||
Tuple(
|
||||
title=_("VRAM Used"),
|
||||
help=_("If used VRAM goes above these percentages, issue alerts."),
|
||||
elements=[
|
||||
Percentage(
|
||||
title="Warn if above",
|
||||
default_value=70
|
||||
),
|
||||
Percentage(
|
||||
title="Crit if above",
|
||||
default_value=90
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="amd_gpu",
|
||||
group=RulespecGroupCheckParametersHardware,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_parameter_valuespec_amd_gpu,
|
||||
item_spec=lambda: TextInput(title=_("GPU")),
|
||||
title=lambda: _("AMD GPU Metrics"),
|
||||
)
|
||||
)
|
@ -0,0 +1,20 @@
|
||||
# Copyright 2024 Spearhead Systems SRL
|
||||
#
|
||||
# This goes in C:\ProgramData\checkmk\agent\plugins. It should be added automatically by
|
||||
# baking a new MSI after setting "Agent Rules" > "Deploy Custom Files With Agent" with
|
||||
# "Deploy Custom Files With Agent" including "amd_gpu".
|
||||
|
||||
foreach ($Item in Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}" -Name -Include 000*) {
|
||||
$Name = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\$Item" "DriverDesc"
|
||||
if ($Name -match 'Radeon') {
|
||||
$GpuBytesTotal = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\$Item" "HardwareInformation.qwMemorySize"
|
||||
$GpuRawName = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\$Item" "HardwareInformation.AdapterString"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$GpuName = [System.Text.Encoding]::Unicode.GetString($GpuRawName)
|
||||
$GpuPercent = (((Get-Counter "\GPU Engine(*)\Utilization Percentage" ).CounterSamples).CookedValue | measure -sum).sum
|
||||
$GpuBytesUsed = (((Get-Counter "\GPU Process Memory(*)\Dedicated Usage").CounterSamples).CookedValue | measure -sum).sum
|
||||
|
||||
Write-Output "<<<amd_gpu:sep(0)>>>", $GpuName, $GpuPercent, $GpuBytesUsed, $GpuBytesTotal
|
BIN
avocent/avocent_acs_800-1.0.1.mkp
Normal file
BIN
avocent/avocent_acs_800-1.0.1.mkp
Normal file
Binary file not shown.
BIN
avocent/cmk20-cmk21/Avocent-1.1.mkp
Normal file
BIN
avocent/cmk20-cmk21/Avocent-1.1.mkp
Normal file
Binary file not shown.
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from typing import List
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import (
|
||||
register,
|
||||
Result,
|
||||
Service,
|
||||
SNMPTree,
|
||||
startswith,
|
||||
State,
|
||||
)
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
|
||||
CheckResult,
|
||||
DiscoveryResult,
|
||||
StringTable,
|
||||
)
|
||||
|
||||
Section = []
|
||||
def parse_avocent_psu(string_table: List[StringTable]) -> Section:
|
||||
return string_table[0][0]
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="avocent_psu",
|
||||
detect=startswith(".1.3.6.1.2.1.1.1.0", "Avocent"),
|
||||
parse_function=parse_avocent_psu,
|
||||
fetch=[
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.10418.26.2.1.8",
|
||||
oids=[
|
||||
"1", #Number of PSU installed
|
||||
"2", #PowerSupply1 state
|
||||
"3", #PowerSupply2 state
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def discovery_avocent_psu(section: Section) -> DiscoveryResult:
|
||||
yield Service()
|
||||
|
||||
|
||||
def _power_supply_status_descr(status_nr: str) -> str:
|
||||
return {
|
||||
"1": "Powered On",
|
||||
"2": "Powered Off",
|
||||
"9999": "Power Supply is not installed",
|
||||
}.get(status_nr, status_nr)
|
||||
|
||||
def _power_supply_state(status_nr: str) -> State:
|
||||
return {
|
||||
"1": State.OK,
|
||||
"2": State.CRIT,
|
||||
"9999": State.OK
|
||||
}.get(status_nr, State.UNKNOWN)
|
||||
|
||||
|
||||
|
||||
def check_avocent_psu(
|
||||
section: Section,
|
||||
) -> CheckResult:
|
||||
number_of_psu=section[0]
|
||||
state_psu_1=section[1]
|
||||
state_psu_2=section[2]
|
||||
|
||||
yield Result(
|
||||
state=State.OK,
|
||||
summary="Number of PSU installed: %s" % number_of_psu,
|
||||
)
|
||||
|
||||
yield Result(
|
||||
state=_power_supply_state(state_psu_1),
|
||||
summary="Power Supply 1 is %s" % _power_supply_status_descr(state_psu_1),
|
||||
)
|
||||
|
||||
yield Result(
|
||||
state=_power_supply_state(state_psu_2),
|
||||
summary="Power Supply 2 is %s" % _power_supply_status_descr(state_psu_2),
|
||||
)
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="avocent_psu",
|
||||
sections=["avocent_psu"],
|
||||
service_name="Power Supplies",
|
||||
discovery_function=discovery_avocent_psu,
|
||||
check_function=check_avocent_psu
|
||||
)
|
||||
|
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
import dataclasses
|
||||
from typing import Mapping
|
||||
|
||||
from .agent_based_api.v1 import (
|
||||
contains,
|
||||
get_value_store,
|
||||
Metric,
|
||||
register,
|
||||
Result,
|
||||
Service,
|
||||
SNMPTree,
|
||||
State,
|
||||
startswith
|
||||
)
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
from .utils.temperature import check_temperature, TempParamType
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Sensor:
|
||||
value: float
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class VoltageSensor(Sensor):
|
||||
...
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Section:
|
||||
temperature_sensors: Mapping[str, Sensor]
|
||||
voltage_sensors: Mapping[str, Sensor]
|
||||
|
||||
temperature_sensors_name = ['CPU','Board']
|
||||
voltage_sensors_name = ['PSU 1','PSU 2']
|
||||
|
||||
def parse_avocent_sensors(string_table: StringTable) -> Section:
|
||||
temperature_sensors = {}
|
||||
voltage_sensors = {}
|
||||
position = 0
|
||||
for temp_sens_name in temperature_sensors_name:
|
||||
temperature_sensors[temp_sens_name] = Sensor(value=int(string_table[0][position]))
|
||||
position +=1
|
||||
|
||||
pos = 2
|
||||
for volt_sens_name in voltage_sensors_name:
|
||||
voltage_sensors[volt_sens_name] = Sensor(value=float(string_table[0][pos])/100)
|
||||
pos += 1
|
||||
|
||||
return Section(
|
||||
temperature_sensors=temperature_sensors,
|
||||
voltage_sensors=voltage_sensors,
|
||||
)
|
||||
|
||||
register.snmp_section(
|
||||
name="avocent_sensors",
|
||||
detect=startswith(".1.3.6.1.2.1.1.1.0", "Avocent"),
|
||||
parse_function=parse_avocent_sensors,
|
||||
fetch=SNMPTree(
|
||||
base=".1.3.6.1.4.1.10418.26.2.7",
|
||||
oids=[
|
||||
"1", #acsSensorsInternalCurrentCPUTemperature
|
||||
"6", #acsSensorsInternalCurrentBoardTemperature
|
||||
"17", #acsSensorsVoltagePowerSupply1
|
||||
"18", #acsSensorsVoltagePowerSupply2
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
def discover_avocent_voltage_sensors(section: Section) -> DiscoveryResult:
|
||||
yield from (Service(item=sensor_name) for sensor_name in section.voltage_sensors)
|
||||
|
||||
|
||||
def check_avocent_voltage_sensors(
|
||||
item: str,
|
||||
section: Section,
|
||||
) -> CheckResult:
|
||||
if not (sensor := section.voltage_sensors.get(item)):
|
||||
return
|
||||
|
||||
yield Result(
|
||||
state=State.OK,
|
||||
summary=f"{sensor.value:.1f} V",
|
||||
)
|
||||
|
||||
yield Metric(
|
||||
name="voltage",
|
||||
value=sensor.value,
|
||||
)
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="avocent_voltage_sensors",
|
||||
sections=["avocent_sensors"],
|
||||
service_name="Voltage %s",
|
||||
discovery_function=discover_avocent_voltage_sensors,
|
||||
check_function=check_avocent_voltage_sensors,
|
||||
)
|
||||
|
||||
|
||||
def discover_avocent_sensors_temp(section: Section) -> DiscoveryResult:
|
||||
yield from (Service(item=sensor_name) for sensor_name in section.temperature_sensors)
|
||||
|
||||
|
||||
def check_avocent_sensors_temp(
|
||||
item: str,
|
||||
params: TempParamType,
|
||||
section: Section,
|
||||
) -> CheckResult:
|
||||
if not (sensor := section.temperature_sensors.get(item)):
|
||||
return
|
||||
yield from check_temperature(
|
||||
reading=sensor.value,
|
||||
params=params,
|
||||
unique_name=item,
|
||||
value_store=get_value_store(),
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="avocent_sensors_temp",
|
||||
sections=["avocent_sensors"],
|
||||
service_name="Temperature %s",
|
||||
discovery_function=discover_avocent_sensors_temp,
|
||||
check_function=check_avocent_sensors_temp,
|
||||
check_ruleset_name="temperature",
|
||||
check_default_parameters={"device_levels_handling": "devdefault"},
|
||||
)
|
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
from ..agent_based_api.v1 import (
|
||||
all_of,
|
||||
any_of,
|
||||
contains,
|
||||
equals,
|
||||
exists,
|
||||
not_contains,
|
||||
not_equals,
|
||||
not_exists,
|
||||
not_startswith,
|
||||
startswith,
|
||||
)
|
||||
|
||||
# We are not sure how to safely detect the UCD SNMP Daemon. We know that
|
||||
# it is mainly used on Linux, but not only. But fetching and OID outside
|
||||
# of the info area for scanning is not a good idea. It will slow down
|
||||
# scans for *all* hosts.
|
||||
|
||||
# ---ucd cpu load---------------------------------------------------------
|
||||
|
||||
# We prefer HOST-RESOURCES-MIB implementation but not in case
|
||||
# of check 'ucd_cpu_load' because the HR-MIB has not data
|
||||
# about cpu load
|
||||
|
||||
# ---general ucd/hr-------------------------------------------------------
|
||||
|
||||
HR = exists(".1.3.6.1.2.1.25.1.1.0")
|
||||
|
||||
_NOT_HR = not_exists(".1.3.6.1.2.1.25.1.1.0")
|
||||
|
||||
UCD = any_of(
|
||||
contains(".1.3.6.1.2.1.1.1.0", "linux"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "cmc-tc"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "hp onboard administrator"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "barracuda"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "pfsense"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "genugate"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "bomgar"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "pulse secure"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "microsens"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "avocent"),
|
||||
all_of( # Artec email archive appliances
|
||||
equals(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.8072.3.2.10"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "version"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "serial"),
|
||||
),
|
||||
all_of(
|
||||
equals(".1.3.6.1.2.1.1.1.0", ""),
|
||||
exists(".1.3.6.1.4.1.2021.*"),
|
||||
),
|
||||
)
|
||||
|
||||
_NOT_UCD = all_of(
|
||||
# This is an explicit negation of the constant above.
|
||||
# We don't have a generic negation function as we want
|
||||
# discourage constructs like this.
|
||||
# In the future this will be acomplished using the 'supersedes'
|
||||
# feature (according to CMK-4232), and this can be removed.
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "linux"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "cmc-tc"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "hp onboard administrator"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "barracuda"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "pfsense"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "genugate"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "bomgar"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "pulse secure"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "microsens"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "avocent"),
|
||||
any_of( # Artec email archive appliances
|
||||
not_equals(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.8072.3.2.10"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "version"),
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "serial"),
|
||||
),
|
||||
)
|
||||
|
||||
PREFER_HR_ELSE_UCD = all_of(UCD, _NOT_HR)
|
||||
|
||||
# ---helper---------------------------------------------------------------
|
||||
|
||||
# Within _is_ucd or _is_ucd_mem we make use of a whitelist
|
||||
# in order to expand this list of devices easily.
|
||||
|
||||
_UCD_MEM = any_of(
|
||||
# Devices for which ucd_mem should be used
|
||||
# if and only if HR-table is not available
|
||||
all_of(
|
||||
contains(".1.3.6.1.2.1.1.1.0", "pfsense"),
|
||||
not_exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
all_of(
|
||||
contains(".1.3.6.1.2.1.1.1.0", "ironport model c3"),
|
||||
not_exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
all_of(
|
||||
contains(".1.3.6.1.2.1.1.1.0", "bomgar"),
|
||||
not_exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
all_of(
|
||||
# Astaro and Synology are Linux but should use hr_mem
|
||||
# Otherwise Cache/Buffers are included in used memory
|
||||
# generating critical state
|
||||
not_startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.8072."),
|
||||
# Otherwise use ucd_mem for listed devices in UCD.
|
||||
UCD,
|
||||
),
|
||||
)
|
||||
|
||||
_NOT_UCD_MEM = all_of(
|
||||
# This is an explicit negation of the constant above.
|
||||
# We don't have a generic negation function as we want
|
||||
# discourage constructs like this.
|
||||
# In the future this will be acomplished using the 'supersedes'
|
||||
# feature (according to CMK-4232), and this can be removed.
|
||||
any_of(
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "pfsense"),
|
||||
exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
any_of(
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "ironport model c3"),
|
||||
exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
any_of(
|
||||
not_contains(".1.3.6.1.2.1.1.1.0", "bomgar"),
|
||||
exists(".1.3.6.1.2.1.25.1.1.0"),
|
||||
),
|
||||
any_of(
|
||||
# Astaro and Synology are Linux but should use hr_mem
|
||||
# Otherwise Cache/Buffers are included in used memory
|
||||
# generating critical state
|
||||
startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.8072."),
|
||||
# Otherwise use ucd_mem for listed devices in UCD.
|
||||
_NOT_UCD,
|
||||
),
|
||||
)
|
||||
|
||||
# Some devices report incorrect data on both HR and UCD, eg. F5 BigIP
|
||||
_NOT_BROKEN_MEM = all_of(
|
||||
not_startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.3375"),
|
||||
not_startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.2620"),
|
||||
)
|
||||
|
||||
# ---memory---------------------------------------------------------------
|
||||
|
||||
USE_UCD_MEM = all_of(_NOT_BROKEN_MEM, _UCD_MEM)
|
||||
|
||||
USE_HR_MEM = all_of(_NOT_BROKEN_MEM, _NOT_UCD_MEM)
|
@ -0,0 +1,15 @@
|
||||
title: Avocent ACS 800 CPU and Board Temperature
|
||||
agents: snmp
|
||||
catalog: hw/network/avocent
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Checks by SNMP the Temperature for CPU and Board sensors of Avocent ACS 800 devices.
|
||||
|
||||
Return {OK} if no temperature rule is created, otherwise based on the level in
|
||||
the configured temperature rule.
|
||||
item:
|
||||
CPU Temperature and Board Temperature
|
||||
|
||||
discovery:
|
||||
One service is created for CPU Temperature and one service for Board Temperature
|
@ -0,0 +1,15 @@
|
||||
title: Avocent ACS 800 Power Supply Voltage Sensors
|
||||
agents: snmp
|
||||
catalog: hw/network/avocent
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Checks by SNMP the power supply voltage sensors of Avocent ACS 800 devices.
|
||||
|
||||
Returns {OK} Always.
|
||||
|
||||
item:
|
||||
The Voltage for each power supply.
|
||||
|
||||
discovery:
|
||||
Two services created, one for each Power Supply
|
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2023 Spearhead Systems SRL - License: GNU General Public License v2
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import register, Result, Service, State
|
||||
|
||||
|
||||
# Convert JSON entries into dictionaries indexed by certificate name.
|
||||
def parse_keyvault(string_table):
|
||||
raw_json = ""
|
||||
cert_data = []
|
||||
|
||||
for row in string_table:
|
||||
line = row[0]
|
||||
raw_json += line
|
||||
if line == "]":
|
||||
cert_data.extend(json.loads(raw_json))
|
||||
raw_json = ""
|
||||
|
||||
lookup = {}
|
||||
for cert in cert_data:
|
||||
lookup[cert["name"]] = cert
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
register.agent_section(
|
||||
name="azure_keyvault",
|
||||
parse_function=parse_keyvault
|
||||
)
|
||||
|
||||
|
||||
# Produce a list of certificates based on the parsed output.
|
||||
def discover_keyvault(section):
|
||||
for name, details in sorted(section.items()):
|
||||
yield Service(item=name)
|
||||
|
||||
|
||||
# Given a specific certificate, look it up in the parsed output, and produce
|
||||
# results on that service based upon the certificate's expiry.
|
||||
def check_keyvault(item, params, section):
|
||||
warn_days = params.get("warn_days")
|
||||
crit_days = params.get("crit_days")
|
||||
|
||||
cert = section.get(item)
|
||||
if cert is None:
|
||||
return
|
||||
|
||||
expires = datetime.fromisoformat(cert["attributes"]["expires"])
|
||||
now = datetime.now(timezone.utc)
|
||||
remaining_days = (expires - now).days
|
||||
|
||||
state = State.OK
|
||||
if crit_days is not None and remaining_days < crit_days:
|
||||
state = State.CRIT
|
||||
elif warn_days is not None and remaining_days < warn_days:
|
||||
state = State.WARN
|
||||
|
||||
yield Result(state=state, summary="Expires in %d days" % remaining_days)
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="azure_keyvault",
|
||||
service_name="Azure Keyvault Certificate %s",
|
||||
|
||||
check_function=check_keyvault,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="azure_keyvault",
|
||||
|
||||
discovery_function=discover_keyvault,
|
||||
)
|
26
azure-keyvault/local/share/check_mk/agents/special/agent_azure_keyvault
Executable file
26
azure-keyvault/local/share/check_mk/agents/special/agent_azure_keyvault
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2023 Spearhead Systems SRL - License: GNU General Public License v2
|
||||
|
||||
az=/usr/bin/az
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 4 ]; then
|
||||
echo "Usage: $0 <tenant> <user> <password> <vault1> ... [vaultN]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tenant="$1"
|
||||
user="$2"
|
||||
password="$3"
|
||||
vaults="${@:4}"
|
||||
|
||||
echo "<<<azure_keyvault:sep(0)>>>"
|
||||
|
||||
"$az" login --service-principal --tenant="$tenant" --user="$user" --password="$password" > /dev/null
|
||||
|
||||
for vault in $vaults; do
|
||||
"$az" keyvault certificate list --vault-name="$vault"
|
||||
done
|
||||
|
||||
"$az" logout
|
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2023 Spearhead Systems SRL - License: GNU General Public License v2
|
||||
|
||||
def agent_azure_keyvault(params, hostname, ipaddress):
|
||||
tenant = params["tenant"]
|
||||
client = params["client"]
|
||||
secret = params["secret"]
|
||||
|
||||
args = [tenant, client, secret]
|
||||
|
||||
for vault in params["vaults"]:
|
||||
args.extend([vault.strip()])
|
||||
|
||||
return args
|
||||
|
||||
special_agent_info["azure_keyvault"] = agent_azure_keyvault
|
@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2023 Spearhead Systems SRL - License: GNU General Public License v2
|
||||
|
||||
import copy
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
rulespec_registry,
|
||||
HostRulespec,
|
||||
IndividualOrStoredPassword,
|
||||
RulespecGroupCheckParametersDiscovery,
|
||||
CheckParameterRulespecWithItem,
|
||||
RulespecGroupCheckParametersApplications,
|
||||
)
|
||||
from cmk.gui.watolib.rulespecs import Rulespec
|
||||
from cmk.gui.valuespec import (
|
||||
Dictionary,
|
||||
TextInput,
|
||||
Integer,
|
||||
ListOfStrings,
|
||||
Password
|
||||
)
|
||||
|
||||
|
||||
def _valuespec_special_agents_azure_keyvault_check():
|
||||
return Dictionary(
|
||||
title=_("Azure Key Vault Certificate Checks"),
|
||||
optional_keys=["warn_days", "crit_days"],
|
||||
elements=[
|
||||
(
|
||||
"warn_days",
|
||||
Integer(
|
||||
minvalue=0,
|
||||
default_value=30,
|
||||
title=_("Certificate Days to Warn"),
|
||||
help=_(
|
||||
"How many days to warn before a certificate in this key vault will expire"
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
"crit_days",
|
||||
Integer(
|
||||
minvalue=0,
|
||||
default_value=3,
|
||||
title=_("Certificate Days to Crit"),
|
||||
help=_(
|
||||
"How many days to crit before a certificate in this key vault will expire"
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def _valuespec_special_agents_azure_keyvault_discovery():
|
||||
return Dictionary(
|
||||
title=_("Azure Key Vault Certificate Discovery"),
|
||||
elements=[
|
||||
(
|
||||
"tenant",
|
||||
TextInput(
|
||||
title=_("Tenant ID / Directory ID"),
|
||||
allow_empty=False,
|
||||
size=45,
|
||||
),
|
||||
),
|
||||
(
|
||||
"client",
|
||||
TextInput(
|
||||
title=_("Client ID / Application ID"),
|
||||
allow_empty=False,
|
||||
size=45,
|
||||
),
|
||||
),
|
||||
(
|
||||
"secret",
|
||||
IndividualOrStoredPassword(
|
||||
# Password(
|
||||
title=_("Client Secret"),
|
||||
allow_empty=False,
|
||||
size=45,
|
||||
),
|
||||
),
|
||||
(
|
||||
"vaults",
|
||||
ListOfStrings(
|
||||
title=_("Keyvaults"),
|
||||
allow_empty=False,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="azure_keyvault",
|
||||
group=RulespecGroupCheckParametersApplications,
|
||||
match_type='dict',
|
||||
parameter_valuespec=_valuespec_special_agents_azure_keyvault_check,
|
||||
)
|
||||
)
|
||||
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
group=RulespecGroupCheckParametersDiscovery,
|
||||
match_type='dict',
|
||||
name="special_agents:azure_keyvault",
|
||||
valuespec=_valuespec_special_agents_azure_keyvault_discovery,
|
||||
)
|
||||
)
|
BIN
azure/azure-spearhead-0.5.1.mkp
Executable file
BIN
azure/azure-spearhead-0.5.1.mkp
Executable file
Binary file not shown.
@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2024 Spearhead Systems SRL
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import register, Result, Service, State, Metric
|
||||
|
||||
|
||||
def check_state_below(alert_percentages, measured_percent):
|
||||
if alert_percentages:
|
||||
if alert_percentages[1] >= measured_percent:
|
||||
return State.CRIT
|
||||
elif alert_percentages[0] >= measured_percent:
|
||||
return State.WARN
|
||||
return State.OK
|
||||
|
||||
|
||||
def check_state_above(alert_percentages, measured_percent):
|
||||
if alert_percentages:
|
||||
if alert_percentages[1] <= measured_percent:
|
||||
return State.CRIT
|
||||
elif alert_percentages[0] <= measured_percent:
|
||||
return State.WARN
|
||||
return State.OK
|
||||
|
||||
|
||||
# Convert JSON entries into dictionaries indexed by name. We're assuming here
|
||||
# that the name is unique across AZs. If not, add the 'location' field in each
|
||||
# object to the name.
|
||||
def parse(string_table):
|
||||
lookup = {}
|
||||
|
||||
for json_data in string_table:
|
||||
obj = json.loads(json_data[0])
|
||||
name = obj["name"]
|
||||
group = obj["resource_group"]
|
||||
lookup[f"{name}#{group}"] = obj
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
# Produce a list of Azure objects for discovery. This applies for KeyVault and
|
||||
# Firewall.
|
||||
def discover(section):
|
||||
for name, details in sorted(section.items()):
|
||||
yield Service(item=name)
|
||||
|
||||
|
||||
# Produce a list of Azure resource group objects for discovery. This applies to
|
||||
# Defender. We also assume each section comes entirely from the same resource
|
||||
# group (this should be true given our special agent).
|
||||
def discover_defender(section):
|
||||
items = list(section.values())
|
||||
|
||||
if items != []:
|
||||
yield Service(item=items[0]["resource_group"])
|
||||
else:
|
||||
yield Service(item=None)
|
||||
|
||||
|
||||
# Given a specific keyvault metric, look it up in the parsed output, and produce
|
||||
# results on that service based upon the metric's range.
|
||||
def check_keyvault(item, params, section):
|
||||
vault = section.get(item)
|
||||
if vault is None:
|
||||
return
|
||||
|
||||
metrics = vault["metrics"]
|
||||
|
||||
availability = metrics.get("Availability")
|
||||
capacity = metrics.get("SaturationShoebox")
|
||||
latency = metrics.get("ServiceApiLatency", 0)
|
||||
hits = metrics.get("ServiceApiHit")
|
||||
results = metrics.get("ServiceApiResult")
|
||||
|
||||
alert_availability_percent = params.get("availability")
|
||||
alert_capacity_percent = params.get("capacity")
|
||||
alert_latency_milliseconds = params.get("latency")
|
||||
|
||||
if availability is not None:
|
||||
yield Result(
|
||||
state=check_state_below(alert_availability_percent, availability),
|
||||
summary=f"Availability: {availability}%",
|
||||
)
|
||||
yield Metric(
|
||||
name="availability",
|
||||
value=availability,
|
||||
boundaries=(0, 100),
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Availability: N/A",
|
||||
)
|
||||
|
||||
if capacity is not None:
|
||||
yield Result(
|
||||
state=check_state_above(alert_capacity_percent, capacity),
|
||||
summary=f"Capacity: {capacity}%"
|
||||
)
|
||||
yield Metric(
|
||||
name="capacity",
|
||||
value=capacity,
|
||||
boundaries=(0, 100),
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Capacity: N/A",
|
||||
)
|
||||
|
||||
|
||||
if latency is not None:
|
||||
yield Result(
|
||||
state=check_state_above(alert_latency_milliseconds, latency),
|
||||
summary=f"Latency: {latency}ms",
|
||||
)
|
||||
yield Metric(
|
||||
name="latency",
|
||||
value=latency,
|
||||
boundaries=(0, None),
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Latency: N/A",
|
||||
)
|
||||
|
||||
|
||||
if hits is not None:
|
||||
yield Metric(
|
||||
name="hits",
|
||||
value=hits,
|
||||
boundaries=(0, None),
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Hits: N/A",
|
||||
)
|
||||
|
||||
if results is not None:
|
||||
yield Metric(
|
||||
name="results",
|
||||
value=results,
|
||||
boundaries=(0, None),
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Results: N/A",
|
||||
)
|
||||
|
||||
|
||||
# Given a specific firewall metric, look it up in the parsed output, and produce
|
||||
# results on that service based upon the metric's range.
|
||||
def check_firewall(item, params, section):
|
||||
firewall = section.get(item)
|
||||
if firewall is None:
|
||||
return
|
||||
|
||||
metrics = firewall["metrics"]
|
||||
|
||||
availability = metrics.get("FirewallHealth")
|
||||
throughput = metrics.get("Throughput")
|
||||
latency = metrics.get("FirewallLatencyPng")
|
||||
|
||||
alert_availability_percent = params.get("availability")
|
||||
alert_latency_milliseconds = params.get("latency")
|
||||
|
||||
if availability is not None:
|
||||
yield Result(
|
||||
state=check_state_below(alert_availability_percent, availability),
|
||||
summary=f"Availability: {availability}%",
|
||||
)
|
||||
yield Metric(
|
||||
name="availability",
|
||||
value=availability,
|
||||
boundaries=(0, 100)
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Availability: N/A",
|
||||
)
|
||||
|
||||
if latency is not None:
|
||||
yield Result(
|
||||
state=check_state_above(alert_latency_milliseconds, latency),
|
||||
summary=f"Latency: {latency}ms",
|
||||
)
|
||||
yield Metric(
|
||||
name="latency",
|
||||
value=latency,
|
||||
boundaries=(0, None)
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Latency: N/A",
|
||||
)
|
||||
|
||||
if throughput is not None:
|
||||
yield Metric(
|
||||
name="throughput",
|
||||
value=throughput,
|
||||
boundaries=(0, None)
|
||||
)
|
||||
else:
|
||||
yield Result(
|
||||
state=State.UNKNOWN,
|
||||
summary="Throughput: N/A",
|
||||
)
|
||||
|
||||
def check_defender(item, params, section):
|
||||
num_high = 0
|
||||
num_med = 0
|
||||
num_low = 0
|
||||
num_info = 0
|
||||
|
||||
final_state = State.OK
|
||||
region_details = []
|
||||
|
||||
for name, alert in sorted(section.items()):
|
||||
details = alert["alert"]
|
||||
status = details["status"]
|
||||
|
||||
if status != "Active" and status != "InProgress":
|
||||
continue
|
||||
|
||||
severity = details["severity"]
|
||||
url = details["url"]
|
||||
info = details["info"]
|
||||
|
||||
if severity == "High":
|
||||
num_high += 1
|
||||
state = State(params.get("severity_high", State.CRIT))
|
||||
elif severity == "Medium":
|
||||
num_med += 1
|
||||
state = State(params.get("severity_medium", State.WARN))
|
||||
elif severity == "Low":
|
||||
num_low += 1
|
||||
state = State(params.get("severity_low", State.WARN))
|
||||
elif severity == "Informational":
|
||||
num_info += 1
|
||||
state = State(params.get("severity_informational", State.OK))
|
||||
else:
|
||||
state = State.UNKNOWN
|
||||
|
||||
final_state = State(max(final_state.value, state.value))
|
||||
|
||||
if state.value > State.OK.value:
|
||||
region_details.append(f"{severity}: {info}: {url}")
|
||||
|
||||
if region_details == []:
|
||||
region_details = ["No Defender alerts"]
|
||||
|
||||
yield Result(
|
||||
state=final_state,
|
||||
summary=f"High: {num_high}, Medium: {num_med}, Low: {num_low}, Informational: {num_info}",
|
||||
details="\n".join(region_details)
|
||||
)
|
||||
|
||||
|
||||
register.agent_section(
|
||||
name="azure_keyvault",
|
||||
parse_function=parse
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="azure_keyvault",
|
||||
service_name="Azure Keyvault Metric %s",
|
||||
|
||||
check_function=check_keyvault,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="azure_keyvault",
|
||||
|
||||
discovery_function=discover,
|
||||
)
|
||||
|
||||
register.agent_section(
|
||||
name="azure_firewall",
|
||||
parse_function=parse
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="azure_firewall",
|
||||
service_name="Azure Firewall Metric %s",
|
||||
|
||||
check_function=check_firewall,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="azure_firewall",
|
||||
|
||||
discovery_function=discover,
|
||||
)
|
||||
|
||||
register.agent_section(
|
||||
name="azure_defender",
|
||||
parse_function=parse
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="azure_defender",
|
||||
service_name="Azure Defender Alert %s",
|
||||
|
||||
check_function=check_defender,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="azure_defender",
|
||||
|
||||
discovery_function=discover_defender,
|
||||
)
|
215
azure/local/share/check_mk/agents/special/agent_azure_common
Executable file
215
azure/local/share/check_mk/agents/special/agent_azure_common
Executable file
@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2024 Spearhead Systems SRL
|
||||
|
||||
from urllib import request, parse, error
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
VAULT_METRICS = [
|
||||
'Availability',
|
||||
'SaturationShoebox',
|
||||
'ServiceApiLatency',
|
||||
'ServiceApiHit',
|
||||
'ServiceApiResult',
|
||||
]
|
||||
|
||||
FIREWALL_METRICS = [
|
||||
'FirewallHealth',
|
||||
'Throughput',
|
||||
'FirewallLatencyPng',
|
||||
]
|
||||
|
||||
REGION_RE = re.compile('/locations/(.+?)/')
|
||||
RESOURCE_GROUP_RE = re.compile('/resourceGroups/(.+?)/')
|
||||
|
||||
|
||||
# https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling
|
||||
def get_url(req, default):
|
||||
#import http.client
|
||||
#http.client.HTTPConnection.debuglevel = 1
|
||||
try:
|
||||
res = request.urlopen(req)
|
||||
return res.read()
|
||||
except error.HTTPError as e:
|
||||
if e.code == 429:
|
||||
return default
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def set_proxy(req, proxy):
|
||||
if proxy is None or proxy == '':
|
||||
return
|
||||
|
||||
match = re.match('(https?)://(.+?)/?$', proxy, re.I)
|
||||
req.set_proxy(match[2], match[1].lower())
|
||||
|
||||
# The explicit Host header is required for this to also work with a proxy.
|
||||
# If we don't include it, Python sends the proxy's Host to Microsoft
|
||||
# instead! So we have to set the Host to the Microsoft domain manually.
|
||||
match = re.match('https://(.+?)/', req.full_url, re.I)
|
||||
req.add_header('Host', match[1] + ":443")
|
||||
|
||||
|
||||
def get_token(tenant, username, password, proxy):
|
||||
data = parse.urlencode({
|
||||
'client_id': username,
|
||||
'client_secret': password,
|
||||
'grant_type': 'client_credentials',
|
||||
'claims': '{"access_token": {"xms_cc": {"values": ["CP1"]}}}',
|
||||
'scope': 'https://management.core.windows.net//.default offline_access openid profile',
|
||||
'client_info': 1,
|
||||
})
|
||||
|
||||
req = request.Request(f'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token',
|
||||
data=str.encode(data))
|
||||
set_proxy(req, proxy)
|
||||
|
||||
res = get_url(req, None)
|
||||
if res is None:
|
||||
return
|
||||
|
||||
token_data = json.loads(res)
|
||||
token = token_data['access_token']
|
||||
return token
|
||||
|
||||
|
||||
def get_json(token, proxy, path, version='2023-07-01'):
|
||||
url = f"https://management.azure.com{path}{'?' in path and '&' or '?'}api-version={version}"
|
||||
req = request.Request(url, headers={'Authorization': f'Bearer {token}'})
|
||||
set_proxy(req, proxy)
|
||||
res = get_url(req, "[]")
|
||||
data = json.loads(res)
|
||||
return data['value']
|
||||
|
||||
|
||||
def list_subscriptions(token, proxy):
|
||||
return get_json(token, proxy, '/subscriptions')
|
||||
|
||||
|
||||
def list_vaults(token, proxy, subscription):
|
||||
return get_json(token, proxy, f'/subscriptions/{subscription}/resources?$filter=resourceType%20eq%20%27Microsoft.KeyVault%2Fvaults%27')
|
||||
|
||||
|
||||
def list_firewalls(token, proxy, subscription):
|
||||
return get_json(token, proxy, f'/subscriptions/{subscription}/resources?$filter=resourceType%20eq%20%27Microsoft.Network%2FazureFirewalls%27')
|
||||
|
||||
|
||||
def list_defender_alerts(token, proxy, subscription):
|
||||
return get_json(token, proxy, f'/subscriptions/{subscription}/providers/Microsoft.Security/alerts', '2022-01-01')
|
||||
|
||||
|
||||
def get_recent_metrics(token, proxy, path, metrics):
|
||||
end = datetime.now()
|
||||
start = end - timedelta(minutes=2)
|
||||
|
||||
start_str = start.isoformat().split('.')[0] + 'Z'
|
||||
end_str = end.isoformat().split('.')[0] + 'Z'
|
||||
metrics_str = ','.join(metrics)
|
||||
|
||||
return get_json(token, proxy, f'{path}/providers/microsoft.insights/metrics?metricnames={metrics_str}×pan={start_str}/{end_str}', '2023-10-01')
|
||||
|
||||
|
||||
def metrics_to_lookup(metrics):
|
||||
lookup = {}
|
||||
|
||||
for metric in metrics:
|
||||
name = metric['name']['value']
|
||||
series = metric['timeseries']
|
||||
if series:
|
||||
value = series[0]['data'][-1]
|
||||
key = next(filter(lambda foo: foo != 'timeStamp', value), None)
|
||||
lookup[name] = value.get(key)
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
def get_args(argv):
|
||||
if (len(argv) != 5 and len(argv) != 6) or argv[1] not in ['keyvault', 'firewall', 'defender']:
|
||||
print(f"{sys.argv[0]} <command> <tenant ID> <username> <password> <proxy>", file=sys.stderr)
|
||||
print(f"Valid commands are: 'keyvault', 'firewall', 'defender'", file=sys.stderr)
|
||||
print(f"Proxy is an optional argument", file=sys.stderr)
|
||||
exit(1)
|
||||
return argv[1], argv[2], argv[3], argv[4], (argv[5] if len(argv) == 6 else None)
|
||||
|
||||
|
||||
def print_json(obj):
|
||||
print(json.dumps(obj))
|
||||
|
||||
|
||||
def get_resource_group(obj):
|
||||
found = re.search(RESOURCE_GROUP_RE, obj['id'])
|
||||
if found:
|
||||
return found[1]
|
||||
return None
|
||||
|
||||
|
||||
resource_groups = {}
|
||||
command, tenant, username, password, proxy = get_args(sys.argv)
|
||||
token = get_token(tenant, username, password, proxy)
|
||||
|
||||
for subscription in list_subscriptions(token, proxy):
|
||||
subscription_id = subscription['subscriptionId']
|
||||
|
||||
if command == 'defender':
|
||||
for alert in list_defender_alerts(token, proxy, subscription_id):
|
||||
properties = alert['properties']
|
||||
status = properties['status']
|
||||
|
||||
if not status in ['Active', 'InProgress']:
|
||||
continue
|
||||
|
||||
group = get_resource_group(alert)
|
||||
|
||||
resource_groups.setdefault(group, []).append({
|
||||
'type': command,
|
||||
'name': alert['name'],
|
||||
'location': re.search(REGION_RE, alert['id'])[1],
|
||||
'resource_group': group,
|
||||
'alert': {
|
||||
'status': status,
|
||||
'severity': properties['severity'],
|
||||
'url': properties['alertUri'],
|
||||
'info': properties['alertDisplayName']
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
elif command == 'firewall':
|
||||
for firewall in list_firewalls(token, proxy, subscription_id):
|
||||
metrics = get_recent_metrics(token, proxy, firewall['id'], FIREWALL_METRICS)
|
||||
group = get_resource_group(firewall)
|
||||
|
||||
resource_groups.setdefault(group, []).append({
|
||||
'type': command,
|
||||
'name': firewall['name'],
|
||||
'location': firewall['location'],
|
||||
'resource_group': get_resource_group(firewall),
|
||||
'metrics': metrics_to_lookup(metrics),
|
||||
})
|
||||
|
||||
elif command == 'keyvault':
|
||||
for vault in list_vaults(token, proxy, subscription_id):
|
||||
metrics = get_recent_metrics(token, proxy, vault['id'], VAULT_METRICS)
|
||||
group = get_resource_group(vault)
|
||||
|
||||
resource_groups.setdefault(group, []).append({
|
||||
'type': command,
|
||||
'name': vault['name'],
|
||||
'location': vault['location'],
|
||||
'resource_group': group,
|
||||
'metrics': metrics_to_lookup(metrics),
|
||||
})
|
||||
|
||||
for group, results in resource_groups.items():
|
||||
if group is None:
|
||||
print(f"<<<<>>>>")
|
||||
else:
|
||||
print(f"<<<<{group}>>>>")
|
||||
|
||||
print(f"<<<azure_{command}:sep(0)>>>")
|
||||
for result in results:
|
||||
print_json(result)
|
4
azure/local/share/check_mk/agents/special/agent_azure_defender
Executable file
4
azure/local/share/check_mk/agents/special/agent_azure_defender
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
dir=$(dirname -- "${BASH_SOURCE[0]}")
|
||||
"$dir"/agent_azure_common defender "$1" "$2" "$3" "$4"
|
4
azure/local/share/check_mk/agents/special/agent_azure_firewall
Executable file
4
azure/local/share/check_mk/agents/special/agent_azure_firewall
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
dir=$(dirname -- "${BASH_SOURCE[0]}")
|
||||
"$dir"/agent_azure_common firewall "$1" "$2" "$3" "$4"
|
4
azure/local/share/check_mk/agents/special/agent_azure_keyvault
Executable file
4
azure/local/share/check_mk/agents/special/agent_azure_keyvault
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
dir=$(dirname -- "${BASH_SOURCE[0]}")
|
||||
"$dir"/agent_azure_common keyvault "$1" "$2" "$3" "$4"
|
25
azure/local/share/check_mk/checks/agent_azure_common
Normal file
25
azure/local/share/check_mk/checks/agent_azure_common
Normal file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2024 Spearhead Systems SRL
|
||||
|
||||
import cmk.utils.password_store
|
||||
|
||||
def agent_azure_args(params, hostname, ipaddress):
|
||||
# Extract password either from params, or from password store:
|
||||
# ('password', '<some password>'): password is in params directly
|
||||
# ('store', '<password name>'): password must be looked up in store by name
|
||||
password_info = params["password"]
|
||||
if password_info[0] == "password":
|
||||
password = password_info[1]
|
||||
else:
|
||||
password = cmk.utils.password_store.extract(password_info[1])
|
||||
|
||||
return [
|
||||
params["tenant"],
|
||||
params["username"],
|
||||
password,
|
||||
params.get("proxy") or "" # optional
|
||||
]
|
||||
|
||||
special_agent_info["azure_keyvault"] = agent_azure_args
|
||||
special_agent_info["azure_firewall"] = agent_azure_args
|
||||
special_agent_info["azure_defender"] = agent_azure_args
|
286
azure/local/share/check_mk/web/plugins/wato/azure_common.py
Normal file
286
azure/local/share/check_mk/web/plugins/wato/azure_common.py
Normal file
@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2024 Spearhead Systems SRL
|
||||
|
||||
import copy
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import State
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
rulespec_registry,
|
||||
HostRulespec,
|
||||
IndividualOrStoredPassword,
|
||||
RulespecGroupCheckParametersDiscovery,
|
||||
CheckParameterRulespecWithItem,
|
||||
RulespecGroupCheckParametersApplications,
|
||||
)
|
||||
from cmk.gui.watolib.rulespecs import Rulespec
|
||||
from cmk.gui.valuespec import (
|
||||
Dictionary,
|
||||
TextInput,
|
||||
Integer,
|
||||
ListOfStrings,
|
||||
Password
|
||||
)
|
||||
|
||||
def _discovery(title):
|
||||
return Dictionary(
|
||||
title=_(title),
|
||||
required_keys=["tenant", "username", "password"],
|
||||
elements=[
|
||||
(
|
||||
"tenant",
|
||||
TextInput(
|
||||
title=_("Tenant ID / Directory ID"),
|
||||
allow_empty=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
TextInput(
|
||||
title=_("Client ID / Application ID"),
|
||||
allow_empty=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"password",
|
||||
IndividualOrStoredPassword(
|
||||
# Password(
|
||||
title=_("Client Secret"),
|
||||
allow_empty=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"proxy",
|
||||
TextInput(
|
||||
title=_("Proxy"),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
)
|
||||
|
||||
def _valuespec_special_agents_azure_keyvault_discovery():
|
||||
return _discovery("Azure Key Vault Metrics Discovery")
|
||||
|
||||
def _valuespec_special_agents_azure_firewall_discovery():
|
||||
return _discovery("Azure Firewall Metrics Discovery")
|
||||
|
||||
def _valuespec_special_agents_azure_defender_discovery():
|
||||
return _discovery("Azure Defender Alerts Discovery")
|
||||
|
||||
def _valuespec_special_agents_azure_keyvault_check():
|
||||
return Dictionary(
|
||||
title=_("Azure Key Vault Metric Checks"),
|
||||
elements=[
|
||||
(
|
||||
"availability",
|
||||
Tuple(
|
||||
title=_("Availability"),
|
||||
help=_("If drops below these percentages over the past minute, issue alert"),
|
||||
elements=[
|
||||
Percentage(
|
||||
title=_("Warn if below"),
|
||||
default_value=98
|
||||
),
|
||||
Percentage(
|
||||
title=_("Crit if below"),
|
||||
default_value=90
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
"capacity",
|
||||
Tuple(
|
||||
title=_("Capacity used"),
|
||||
help=_("If goes above these percentages over the past minute, issue alert"),
|
||||
elements=[
|
||||
Percentage(
|
||||
title=_("Warn if above"),
|
||||
default_value=80
|
||||
),
|
||||
Percentage(
|
||||
title=_("Crit if above"),
|
||||
default_value=98
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
"latency",
|
||||
Tuple(
|
||||
title=_("Request latency"),
|
||||
help=_("If goes above the average milliseconds over the past minute, issue alert"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warn if above"),
|
||||
default_value=100,
|
||||
minvalue=0,
|
||||
),
|
||||
Integer(
|
||||
title=_("Crit if above"),
|
||||
default_value=2000,
|
||||
minvalue=0,
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def _valuespec_special_agents_azure_firewall_check():
|
||||
return Dictionary(
|
||||
title=_("Azure Firewall Metric Checks"),
|
||||
elements=[
|
||||
(
|
||||
"availability",
|
||||
Tuple(
|
||||
title=_("Availability"),
|
||||
help=_("If drops below these percentages over the past minute, issue alert"),
|
||||
elements=[
|
||||
Percentage(
|
||||
title=_("Warn if below"),
|
||||
default_value=98
|
||||
),
|
||||
Percentage(
|
||||
title=_("Crit if below"),
|
||||
default_value=90
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
"latency",
|
||||
Tuple(
|
||||
title=_("Request latency"),
|
||||
help=_("If goes above the average milliseconds over the past minute, issue alert"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warn if above"),
|
||||
default_value=100,
|
||||
minvalue=0,
|
||||
),
|
||||
Integer(
|
||||
title=_("Crit if above"),
|
||||
default_value=2000,
|
||||
minvalue=0,
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def _valuespec_special_agents_azure_defender_check():
|
||||
return Dictionary(
|
||||
title=_("Azure Defender Alerts Severity"),
|
||||
elements=[
|
||||
(
|
||||
"severity_high",
|
||||
DropdownChoice(
|
||||
title=_("Defender severity 'High'"),
|
||||
help=_("What CheckMK criticality should this Azure Defender severity trigger"),
|
||||
default_value=State.CRIT.value,
|
||||
choices=[
|
||||
(State.CRIT.value, _(State.CRIT.name)),
|
||||
(State.WARN.value, _(State.WARN.name)),
|
||||
(State.OK.value, _(State.OK.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"severity_medium",
|
||||
DropdownChoice(
|
||||
title=_("Defender severity 'Medium'"),
|
||||
help=_("What CheckMK criticality should this Azure Defender severity trigger"),
|
||||
default_value=State.WARN.value,
|
||||
choices=[
|
||||
(State.CRIT.value, _(State.CRIT.name)),
|
||||
(State.WARN.value, _(State.WARN.name)),
|
||||
(State.OK.value, _(State.OK.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"severity_low",
|
||||
DropdownChoice(
|
||||
title=_("Defender severity 'Low'"),
|
||||
help=_("What CheckMK criticality should this Azure Defender severity trigger"),
|
||||
default_value=State.WARN.value,
|
||||
choices=[
|
||||
(State.CRIT.value, _(State.CRIT.name)),
|
||||
(State.WARN.value, _(State.WARN.name)),
|
||||
(State.OK.value, _(State.OK.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"severity_informational",
|
||||
DropdownChoice(
|
||||
title=_("Defender severity 'Informational'"),
|
||||
help=_("What CheckMK criticality should this Azure Defender severity trigger"),
|
||||
default_value=State.OK.value,
|
||||
choices=[
|
||||
(State.CRIT.value, _(State.CRIT.name)),
|
||||
(State.WARN.value, _(State.WARN.name)),
|
||||
(State.OK.value, _(State.OK.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
name="special_agents:azure_keyvault",
|
||||
group=RulespecGroupCheckParametersDiscovery,
|
||||
match_type='dict',
|
||||
valuespec=_valuespec_special_agents_azure_keyvault_discovery,
|
||||
)
|
||||
)
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
name="special_agents:azure_firewall",
|
||||
group=RulespecGroupCheckParametersDiscovery,
|
||||
match_type='dict',
|
||||
valuespec=_valuespec_special_agents_azure_firewall_discovery,
|
||||
)
|
||||
)
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
name="special_agents:azure_defender",
|
||||
group=RulespecGroupCheckParametersDiscovery,
|
||||
match_type='dict',
|
||||
valuespec=_valuespec_special_agents_azure_defender_discovery,
|
||||
)
|
||||
)
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="azure_keyvault",
|
||||
group=RulespecGroupCheckParametersApplications,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_valuespec_special_agents_azure_keyvault_check,
|
||||
item_spec=lambda: TextInput(title=_("Key Vault")),
|
||||
title=lambda: _("Azure Key Vault Metrics"),
|
||||
)
|
||||
)
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="azure_firewall",
|
||||
group=RulespecGroupCheckParametersApplications,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_valuespec_special_agents_azure_firewall_check,
|
||||
item_spec=lambda: TextInput(title=_("Firewall")),
|
||||
title=lambda: _("Azure Firewall Metrics"),
|
||||
)
|
||||
)
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="azure_defender",
|
||||
group=RulespecGroupCheckParametersApplications,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_valuespec_special_agents_azure_defender_check,
|
||||
item_spec=lambda: TextInput(title=_("Defender")),
|
||||
title=lambda: _("Azure Defender Alerts Severity"),
|
||||
)
|
||||
)
|
BIN
cisco-bgp-peer/Cisco_BGP_Peer3-1.1.mkp
Executable file
BIN
cisco-bgp-peer/Cisco_BGP_Peer3-1.1.mkp
Executable file
Binary file not shown.
1
cisco-bgp-peer/README.md
Normal file
1
cisco-bgp-peer/README.md
Normal file
@ -0,0 +1 @@
|
||||
This is a modification of a GPL plugin to additionally support CISCO-BGP4-MIB::CbgpPeer3Entry
|
@ -0,0 +1,665 @@
|
||||
#!/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 : 2017-12-26
|
||||
#
|
||||
# Monitor status of Cisco BGP Peer (IPv4 and IPv6)
|
||||
#
|
||||
# 2018-05-24: changed counters to 1/s
|
||||
# 2018-05-25: a lot of code cleanup
|
||||
# packet name changed from cisco_bgp to cisco_bgp_peer
|
||||
# added support of more then one address family per peer
|
||||
# (changed item from remoteip to remoteip+familyname, rewrite of parer, inventory and check function)
|
||||
# 2018-05-27: changed scan function from '.1.3.6.1.4.1.9.9.187.1.2.7.1.3.* to sysdecr contains cisco
|
||||
# 2018-05-28: changed wato, added peer alias, state if not found, infotext values
|
||||
# 2018-05-29: fixed longoutpout (removed not configured)
|
||||
# 2018-11-02: modified scanfunction (from "find 'cisco' =-1" to "'cisco' in OID"
|
||||
# 2019-18-02: added fix for empty values ("" instead of "0") sugested by Laurent Barbier (lbarbier[at]arkane-studios[dot]com)
|
||||
# 2020-02-24: added workaround for missing cbgpPeer2AddrFamily (example L2VPN EVPN peers, Fix for jonale82[at]gmail[dot]com)
|
||||
# 2020-03-02: changed handling of perfdata, add only data the are really there (not None, instead of setting them to 0)
|
||||
# 2020-06-04: code cleanup --> changed isdigit test to try/except loop, changed peer.get test to try/except loop
|
||||
# 2020-09-10: fixed typo in metrics file. FMS --> FSM (thanks martin[dot]pechstein[at]posteo[dot]de)
|
||||
# 2021-03-27: rewrite for CMK2.0
|
||||
# 2021-03-28: added warning for missing admin prefix limit/warn threshold
|
||||
# 2023-01-15: modified by Spearhead Systems to support CISCO-BGP4-MIB::CbgpPeer3Entry
|
||||
|
||||
#
|
||||
# snmpwalk sample
|
||||
#
|
||||
# CISCO-BGP4-MIB::cbgpPeer2AddrFamilyEntry
|
||||
#
|
||||
# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c <removed> simulant 1.3.6.1.4.1.9.9.187.1.2.7
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.62.214.127.57.1.1 = STRING: "IPv4 Unicast"
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.217.119.208.1.1.1 = STRING: "IPv4 Unicast"
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.217.119.208.33.1.1 = STRING: "IPv4 Unicast"
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.7.1.3.2.16.32.1.20.56.7.0.0.39.0.0.0.0.0.0.0.1.2.1 = STRING: "IPv6 Unicast"
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.7.1.3.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = STRING: "IPv6 Unicast"
|
||||
#
|
||||
# CISCO-BGP4-MIB::CbgpPeer2Entry (IPv4)
|
||||
#
|
||||
# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c <removed> simulant 1.3.6.1.4.1.9.9.187.1.2.5.1| grep 62.214.127.57
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.3.1.4.62.214.127.57 = INTEGER: 6
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.4.1.4.62.214.127.57 = INTEGER: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.5.1.4.62.214.127.57 = INTEGER: 4
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.6.1.4.62.214.127.57 = Hex-STRING: 3E D6 7F 3A
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.7.1.4.62.214.127.57 = Gauge32: 29418
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.8.1.4.62.214.127.57 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.9.1.4.62.214.127.57 = IpAddress: 217.119.208.2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.10.1.4.62.214.127.57 = Gauge32: 179
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.11.1.4.62.214.127.57 = Gauge32: 8881
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.12.1.4.62.214.127.57 = IpAddress: 62.214.127.57
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.13.1.4.62.214.127.57 = Counter32: 18
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.14.1.4.62.214.127.57 = Counter32: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.15.1.4.62.214.127.57 = Counter32: 205
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.16.1.4.62.214.127.57 = Counter32: 195
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.17.1.4.62.214.127.57 = Hex-STRING: 00 00
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.18.1.4.62.214.127.57 = Counter32: 1
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.19.1.4.62.214.127.57 = Gauge32: 10446
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.20.1.4.62.214.127.57 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.21.1.4.62.214.127.57 = INTEGER: 180
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.22.1.4.62.214.127.57 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.23.1.4.62.214.127.57 = INTEGER: 180
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.24.1.4.62.214.127.57 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.25.1.4.62.214.127.57 = INTEGER: 30
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.26.1.4.62.214.127.57 = INTEGER: 30
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.27.1.4.62.214.127.57 = Gauge32: 5824
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.28.1.4.62.214.127.57 = ""
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.29.1.4.62.214.127.57 = INTEGER: 5
|
||||
#
|
||||
#
|
||||
# CISCO-BGP4-MIB::CbgpPeer2Entry (IPv6)
|
||||
#
|
||||
# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c <removed> simulant 1.3.6.1.4.1.9.9.187.1.2.5.1| grep 16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.3.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 6
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.4.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.5.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 4
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.6.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Hex-STRING: 2A 05 57 C0 00 00 FF FF 00 00 00 00 00 00 00 11
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.7.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 179
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.8.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.9.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = IpAddress: 217.119.208.2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.10.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 35062
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.11.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 31259
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.12.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = IpAddress: 217.119.208.1
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.13.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Counter32: 5
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.14.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Counter32: 6
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.15.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Counter32: 157
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.16.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Counter32: 161
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.17.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Hex-STRING: 06 04
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.18.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Counter32: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.19.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 8430
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.20.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.21.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 180
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.22.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.23.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 180
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.24.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 60
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.25.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.26.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.27.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = Gauge32: 1494
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.28.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = STRING: "Administrative Reset"
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.5.1.29.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16 = INTEGER: 5
|
||||
#
|
||||
#
|
||||
# CISCO-BGP4-MIB::cbgpPeer2AddrFamilyPrefixEntry (IPv4)
|
||||
#
|
||||
# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c <removed> simulant 1.3.6.1.4.1.9.9.187.1.2.8.1| grep 16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.1.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Counter32: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.2.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.3.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 100000
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.4.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 85
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.5.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 80
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.6.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 10
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.7.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.8.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
#
|
||||
#
|
||||
# CISCO-BGP4-MIB::cbgpPeer2AddrFamilyPrefixEntry (IPv6)
|
||||
#
|
||||
# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c <removed> simulant 1.3.6.1.4.1.9.9.187.1.2.8.1| grep 16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.1.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Counter32: 2
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.2.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.3.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 100000
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.4.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 85
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.5.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 80
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.6.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 10
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.7.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
# .1.3.6.1.4.1.9.9.187.1.2.8.1.8.2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.16.2.1 = Gauge32: 0
|
||||
#
|
||||
#
|
||||
# sample info
|
||||
#
|
||||
# [
|
||||
# [
|
||||
# ['1.4.77.235.182.229', '6', '2', 'M\xeb\xb6\xe6', '0', '217.119.208.1', '21413', '77.235.182.229', '1', '3', '48',
|
||||
# '53', '\x04\x00', '8', '2581', '2581', 'hold time expired', '5'],
|
||||
# ['1.4.217.119.208.2', '6', '2', '\xd9w\xd0\x01', '0', '217.119.208.1', '31259', '217.119.208.2', '11', '23', '168',
|
||||
# '170', '\x06\x04', '3', '8380', '5774', 'Administrative Reset', '5'],
|
||||
# ['1.4.217.119.208.34', '6', '2', '\xd9w\xd0!', '0', '217.119.208.1', '31259', '217.119.208.2', '11', '23', '168',
|
||||
# '170', '\x06\x04', '3', '8377', '5774', 'Administrative Reset', '5'],
|
||||
# ['2.16.42.0.28.160.16.0.1.53.0.0.0.0.0.0.0.1', '6', '2', '*\x00\x1c\xa0\x10\x00\x015\x00\x00\x00\x00\x00\x00\x00\x02',
|
||||
# '0', '217.119.208.1', '21413', '77.235.182.229', '0', '4', '108', '121', '\x06\x04', '6', '6295', '0',
|
||||
# 'Administrative Reset', '5'],
|
||||
# ['2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17', '6', '2', '*\x05W\xc0\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x10',
|
||||
# '0', '217.119.208.1', '31259', '217.119.208.2', '6', '5', '160', '157', '\x06\x04', '2', '8380', '1409',
|
||||
# 'Administrative Reset', '5']
|
||||
# ],
|
||||
# [
|
||||
# ['1.4.77.235.182.229.1.1', 'IPv4 Unicast', '1', '0', '', '', '', '6', '0', '0'],
|
||||
# ['1.4.217.119.208.2.1.1', 'IPv4 Unicast', '4', '0', '', '', '', '17', '0', '10'],
|
||||
# ['1.4.217.119.208.34.1.1', 'IPv4 Unicast', '4', '0', '', '', '', '17', '0', '10'],
|
||||
# ['2.16.42.0.28.160.16.0.1.53.0.0.0.0.0.0.0.1.2.1', 'IPv6 Unicast', '0', '0', '100000', '85', '80', '6', '0', '0'],
|
||||
# ['2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17.2.1', 'IPv6 Unicast', '2', '0', '100000', '85', '80', '8', '0', '0']
|
||||
# ]
|
||||
# ]
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from dataclasses import dataclass
|
||||
import re, time
|
||||
from typing import Mapping, Dict, List, Tuple, NamedTuple
|
||||
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
|
||||
DiscoveryResult,
|
||||
CheckResult,
|
||||
StringTable,
|
||||
)
|
||||
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import (
|
||||
register,
|
||||
Service,
|
||||
Result,
|
||||
check_levels,
|
||||
State,
|
||||
SNMPTree,
|
||||
contains,
|
||||
OIDEnd,
|
||||
get_rate,
|
||||
get_value_store,
|
||||
Metric,
|
||||
render,
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Section:
|
||||
peer_prefixes: dict
|
||||
peer_table: dict
|
||||
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# DATA Parser function
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
|
||||
def parse_cisco_bgp_peer(string_table: List[StringTable]) -> Section:
|
||||
def bgp_render_ipv4_address(bytestring):
|
||||
return ".".join(["%s" % ord(m) for m in bytestring])
|
||||
|
||||
def bgp_shorten_ipv6_adress(address):
|
||||
address = address.split(':')
|
||||
span = 2
|
||||
address = [''.join(address[i:i + span]) for i in range(0, len(address), span)]
|
||||
for m in range(0, len(address)):
|
||||
address[m] = re.sub(r'^0{1,3}', r'', address[m])
|
||||
address = ':'.join(address)
|
||||
zeros = ':0:0:0:0:0:0:'
|
||||
while not zeros == '':
|
||||
if zeros in address:
|
||||
address = re.sub(r'%s' % zeros, r'::', address)
|
||||
zeros = ''
|
||||
else:
|
||||
zeros = zeros[:-2]
|
||||
return address
|
||||
|
||||
def bgp_render_ipv6_address(bytestring):
|
||||
address = ":".join(["%02s" % hex(ord(m))[2:] for m in bytestring]).replace(' ', '0').upper()
|
||||
address = bgp_shorten_ipv6_adress(address)
|
||||
|
||||
return address
|
||||
|
||||
def bgp_render_ip_address(bytestring):
|
||||
if len(bytestring) == 4:
|
||||
return bgp_render_ipv4_address(bytestring)
|
||||
elif len(bytestring) == 16:
|
||||
return bgp_render_ipv6_address(bytestring)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def cisco_bgp_get_peer(OID_END):
|
||||
# returns peer address string from OID_END
|
||||
# u'1.4.217.119.208.34.1.1' --> 217.119.208.34
|
||||
# u'2.16.42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17.2.1' --> 42.5.87.192.0.0.255.255.0.0.0.0.0.0.0.17
|
||||
peer_ip = ''
|
||||
OID_END = OID_END.split('.')
|
||||
if int(len(OID_END)) == 7: # length of ip address
|
||||
peer_ip = '.'.join(OID_END[3:7]) # ipv4 address
|
||||
elif int(OID_END[1]) == 16: # ipv6 address
|
||||
peer_ip = ':'.join('%02s' % hex(int(m))[2:] for m in OID_END[2:18]).replace(' ', '0').upper()
|
||||
peer_ip = bgp_shorten_ipv6_adress(peer_ip)
|
||||
|
||||
return peer_ip
|
||||
|
||||
def cisco_bgp_errors(bytestring):
|
||||
lasterrorhex = ''.join(["%02X " % ord(x) for x in bytestring]).strip()
|
||||
byte1, byte2 = lasterrorhex.split()
|
||||
|
||||
names = {}
|
||||
names[0] = {0: 'NO ERROR'}
|
||||
names[1] = {0: 'Message',
|
||||
2: 'Connection Not Synchronized',
|
||||
3: 'Bad Message Length',
|
||||
4: 'Bad Message Type',
|
||||
}
|
||||
names[2] = {0: 'OPEN',
|
||||
1: 'Unsupported Version Number',
|
||||
2: 'Bad Peer AS',
|
||||
3: 'Bad BGP Identifier',
|
||||
4: 'Unsupported Optional Parameter',
|
||||
5: 'Authentication Failure',
|
||||
6: 'Unacceptable Hold',
|
||||
}
|
||||
names[3] = {0: 'UPDATE',
|
||||
1: 'Malformed Attribute List',
|
||||
2: 'Unrecognized Well-known Attribute',
|
||||
3: 'Missing Well-known Attribute',
|
||||
4: 'Attribute Flags Error',
|
||||
5: 'Attribute Length Error',
|
||||
6: 'Invalid ORIGIN Attribute',
|
||||
7: 'AS Routing Loop',
|
||||
8: 'Invalid NEXT_HOP Attribute',
|
||||
9: 'Optional Attribute Error',
|
||||
10: 'Invalid Network Field',
|
||||
11: 'Malformed AS_PATH',
|
||||
}
|
||||
names[4] = {0: 'Hold Timer Expired', }
|
||||
|
||||
names[5] = {0: 'Finite State Machine Error', }
|
||||
|
||||
names[6] = {0: 'Administratively Shutdown',
|
||||
1: 'Max Prefix Reached',
|
||||
2: 'Peer Unconfigured',
|
||||
3: 'Administratively Reset',
|
||||
4: 'Connection Rejected',
|
||||
5: 'Other Configuration Change',
|
||||
}
|
||||
|
||||
return names[int(byte1, 16)].get(int(byte2, 16))
|
||||
|
||||
# bgp not active
|
||||
if not string_table == [[], [], []]:
|
||||
|
||||
cbgpPeer2Entry, cbgpPeer3Entry, cbgpPeer2AddrFamily = string_table
|
||||
cbgpPeerEntry = cbgpPeer2Entry + cbgpPeer3Entry
|
||||
|
||||
peer_prefixes = {}
|
||||
# create dictionary from cbgpPeer2AddrFamily ('remoteip addrfamilyname' as index)
|
||||
if len(cbgpPeer2AddrFamily) > 0:
|
||||
for entry in cbgpPeer2AddrFamily:
|
||||
oid_end, addrfamilyname, acceptedprefixes, deniedprefixes, prefixadminlimit, prefixthreshold, \
|
||||
prefixclearthreshold, advertisedprefixes, suppressedprefixes, withdrawnprefixes = entry
|
||||
|
||||
remoteaddr = cisco_bgp_get_peer(entry[0])
|
||||
peer = {
|
||||
'remoteaddr': remoteaddr,
|
||||
'addrfamilyname': addrfamilyname
|
||||
}
|
||||
|
||||
for key, value in [
|
||||
('prefixadminlimit', prefixadminlimit),
|
||||
('prefixthreshold', prefixthreshold),
|
||||
('prefixclearthreshold', prefixclearthreshold),
|
||||
('acceptedprefixes', acceptedprefixes),
|
||||
('advertisedprefixes', advertisedprefixes),
|
||||
('deniedprefixes', deniedprefixes),
|
||||
('suppressedprefixes', suppressedprefixes),
|
||||
('withdrawnprefixes', withdrawnprefixes),
|
||||
]:
|
||||
try:
|
||||
peer[key] = int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
peer_prefixes.update({'%s %s' % (remoteaddr, addrfamilyname): peer})
|
||||
|
||||
# workaround: get remote ip from cbgpPeerEntry if cbgpPeer2AddrFamilyName is missing :-(
|
||||
elif len(cbgpPeerEntry) > 0:
|
||||
for entry in cbgpPeerEntry:
|
||||
remoteaddr = cisco_bgp_get_peer(entry[0])
|
||||
addrfamilyname = ''
|
||||
|
||||
peer = {'remoteaddr': remoteaddr, }
|
||||
|
||||
peer_prefixes.update({'%s %s' % (remoteaddr, addrfamilyname): peer})
|
||||
|
||||
# create dictionary from cbgpPeerEntry (peer ip address as index)
|
||||
peer_table = {}
|
||||
for entry in cbgpPeerEntry:
|
||||
oid_end, state, adminstatus, localaddr, localas, localidentifier, remoteas, remoteidentifier, inupdates, \
|
||||
outupdates, intotalmessages, outtotalmessages, lasterror, fsmestablishedtransitions, fsmestablishedtime, \
|
||||
inupdateelapsedtime, lasterrortxt, prevstate = entry
|
||||
|
||||
peer = {'remoteaddr': cisco_bgp_get_peer(oid_end),
|
||||
'localaddr': bgp_render_ip_address(localaddr),
|
||||
'localid': localidentifier,
|
||||
'remoteid': remoteidentifier,
|
||||
'lasterror': cisco_bgp_errors(lasterror),
|
||||
'lasterrortxt': lasterrortxt,
|
||||
'prevstate': int(prevstate),
|
||||
}
|
||||
|
||||
for key, value in [
|
||||
('state', state),
|
||||
('adminstate', adminstatus),
|
||||
('localas', localas),
|
||||
('remoteas', remoteas),
|
||||
('inupdates', inupdates),
|
||||
('outupdates', outupdates),
|
||||
('intotalmessages', intotalmessages),
|
||||
('outtotalmessages', outtotalmessages),
|
||||
('fsmestablishedtransitions', fsmestablishedtransitions),
|
||||
('fsmestablishedtime', fsmestablishedtime),
|
||||
('inupdateelapsedtime', inupdateelapsedtime),
|
||||
]:
|
||||
try:
|
||||
peer[key] = int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
peer_table.update({'%s' % cisco_bgp_get_peer(oid_end): peer})
|
||||
|
||||
return Section(
|
||||
peer_prefixes=peer_prefixes,
|
||||
peer_table=peer_table,
|
||||
)
|
||||
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# INVENTORY function
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
|
||||
def discovery_cisco_bgp_peer(section: Section) -> DiscoveryResult:
|
||||
for key in section.peer_prefixes.keys():
|
||||
yield Service(item=key)
|
||||
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# CHECK function
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
|
||||
def check_cisco_bgp_peer(item, params, section) -> CheckResult:
|
||||
def cisco_bgp_adminstate(state):
|
||||
names = {1: 'stop',
|
||||
2: 'start', }
|
||||
return names.get(state, 'unknown (%s)' % state)
|
||||
|
||||
def cisco_bgp_peerstate(state):
|
||||
names = {0: 'none',
|
||||
1: 'idle',
|
||||
2: 'connect',
|
||||
3: 'active',
|
||||
4: 'opensned',
|
||||
5: 'openconfirm',
|
||||
6: 'established'}
|
||||
return names.get(state, 'unknown (%s)' % state)
|
||||
|
||||
peer_prefixes = section.peer_prefixes
|
||||
peer_table = section.peer_table
|
||||
|
||||
prefixes = peer_prefixes.get(item, None)
|
||||
|
||||
alias = ''
|
||||
peer_not_found_state = 3
|
||||
|
||||
for bgp_connection, bgp_alias, not_found_state in params.get('peer_list', []):
|
||||
if item == bgp_connection:
|
||||
alias = bgp_alias
|
||||
peer_not_found_state = not_found_state
|
||||
|
||||
if prefixes:
|
||||
longoutput = ''
|
||||
|
||||
peer = peer_table.get(prefixes.get('remoteaddr'))
|
||||
|
||||
if peer.get('localas') == 0:
|
||||
peer.update({'localas': params.get('useaslocalas')})
|
||||
|
||||
if alias != '':
|
||||
yield Result(state=State.OK, summary='Alias: %s' % alias)
|
||||
|
||||
peerstate = peer.get('state')
|
||||
adminstate = peer.get('adminstate')
|
||||
establishedtime = peer.get('fsmestablishedtime')
|
||||
|
||||
if peerstate == 1: # idle
|
||||
yield Result(state=State.CRIT, summary='Peer state: %s' % cisco_bgp_peerstate(peerstate))
|
||||
elif peerstate == 6: # established
|
||||
yield from check_levels(
|
||||
value=establishedtime,
|
||||
label='Uptime',
|
||||
levels_lower=params['minuptime'],
|
||||
render_func=render.timespan
|
||||
)
|
||||
|
||||
else: # everything else
|
||||
yield Result(state=State.WARN, summary='Peer state: %s' % cisco_bgp_peerstate(peerstate))
|
||||
|
||||
if not adminstate == 2: # not start
|
||||
yield Result(state=State.WARN, summary='Admin state: %s' % cisco_bgp_adminstate(adminstate))
|
||||
|
||||
for key, value in [
|
||||
('remoteid', 'Remote ID: %s'),
|
||||
('remoteas', 'Remote AS: %s'),
|
||||
('localaddr', 'Local address: %s'),
|
||||
('localid', 'Local ID: %s'),
|
||||
('localas', 'Local AS: %s'),
|
||||
]:
|
||||
if key in params['infotext_values']:
|
||||
try:
|
||||
yield Result(state=State.OK, summary=value % peer[key])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
bgptype = ''
|
||||
if not peer.get('localas') == 0:
|
||||
if peer.get('remoteas') == peer.get('localas'):
|
||||
bgptype = ' (iBGP)'
|
||||
else:
|
||||
bgptype = ' (eBGP)'
|
||||
|
||||
longoutput_data = [
|
||||
['IP-address (remote/local)', peer.get('remoteaddr'), peer.get('localaddr')],
|
||||
['Router-ID (remote/local)', peer.get('remoteid'), peer.get('localid')],
|
||||
['Autonomus System (remote/local)', peer.get('remoteas'), str(peer.get('localas')) + bgptype],
|
||||
['State', cisco_bgp_peerstate(peerstate), ''],
|
||||
['Admin state', cisco_bgp_adminstate(adminstate), ''],
|
||||
['Last error', peer.get('lasterror'), ''],
|
||||
['Last error text', peer.get('lasterrortxt'), ''],
|
||||
['Previous state', cisco_bgp_peerstate(peer.get('prevstate')), ''],
|
||||
['Address family name', prefixes.get('addrfamilyname', 'unknown'), ''],
|
||||
['Prefix clear threshold (%)', '%.0d' % prefixes.get('prefixclearthreshold', 0), '']
|
||||
,
|
||||
]
|
||||
|
||||
acceptedprefixes = prefixes.get('acceptedprefixes', None)
|
||||
prefixadminlimit = prefixes.get('prefixadminlimit', None)
|
||||
prefixthreshold = prefixes.get('prefixthreshold', None)
|
||||
|
||||
if prefixadminlimit is not None and prefixthreshold is not None:
|
||||
warnthreshold = prefixadminlimit / 100.0 * prefixthreshold # use float (100.0) to get xx.xx in division
|
||||
longoutput_data.append(['Prefix admin limit (prefixes)', '%.0d' % prefixadminlimit, ''])
|
||||
longoutput_data.append(['Prefix threshold (prefixes/%)', '%.0d' % warnthreshold, '%.0d' % prefixthreshold])
|
||||
else:
|
||||
yield Result(state=State(params['noprefixlimit']), notice='No admin prefix limit/warn threshold configured on the device.')
|
||||
warnthreshold = None
|
||||
|
||||
if params.get('htmloutput', False):
|
||||
#
|
||||
# disable 'Escape HTML codes in plugin output' in wato --> global settings
|
||||
#
|
||||
table_bracket = '<table border="1">%s</table>'
|
||||
line_bracket = '<tr>%s</tr>'
|
||||
cell_bracket = '<td>%s</td><td>%s</td><td>%s</td>'
|
||||
cell_seperator = ''
|
||||
|
||||
longoutput = '\n' + table_bracket % (''.join(
|
||||
[line_bracket % cell_seperator.join([cell_bracket % (entry[0], entry[1], entry[2])]) for entry in
|
||||
longoutput_data]))
|
||||
else:
|
||||
longoutput += '\nfor nicer output' \
|
||||
'\ndisable \'Escape HTML codes in plugin output\' in wato -> global settings and enable HTML output in \'Parameters for this service\''
|
||||
for entry in longoutput_data:
|
||||
if not entry[2] == '':
|
||||
longoutput += '\n{}: {} / {}'.format(entry[0], entry[1], entry[2])
|
||||
else:
|
||||
longoutput += '\n{}: {}'.format(entry[0], entry[1])
|
||||
|
||||
if prefixadminlimit is not None:
|
||||
yield from check_levels(
|
||||
value=acceptedprefixes,
|
||||
metric_name='cisco_bgp_peer_acceptedprefixes',
|
||||
levels_upper=(warnthreshold, prefixadminlimit),
|
||||
label='Prefixes accepted',
|
||||
render_func=lambda v: '%s' % str(v)
|
||||
)
|
||||
|
||||
now_time = time.time()
|
||||
value_store = get_value_store()
|
||||
rate_item = item.replace(' ', '_').replace(':', '_')
|
||||
|
||||
for key in [
|
||||
'deniedprefixes',
|
||||
'advertisedprefixes',
|
||||
'withdrawnprefixes',
|
||||
'suppressedprefixes',
|
||||
'inupdates',
|
||||
'outupdates',
|
||||
'intotalmessages',
|
||||
'outtotalmessages',
|
||||
]:
|
||||
try:
|
||||
value = get_rate(value_store, 'cisco_bgp_peer.%s.%s' % (key, rate_item), now_time, prefixes[key],
|
||||
raise_overflow=False)
|
||||
yield Metric(name='cisco_bgp_peer_%s' % key, value=value, boundaries=(0, None))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for key in [
|
||||
'fsmestablishedtransitions',
|
||||
'fsmestablishedtime',
|
||||
'inupdateelapsedtime'
|
||||
]:
|
||||
try:
|
||||
yield Metric(name='cisco_bgp_peer_%s' % key, value=peer[key], boundaries=(0, None))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
yield Result(state=State.OK, notice=longoutput)
|
||||
else:
|
||||
if alias != '':
|
||||
yield Result(state=State.OK, summary=', Alias: %s' % alias)
|
||||
yield Result(state=State(peer_not_found_state), summary='Item not found in SNMP data')
|
||||
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# CHECK info
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
register.snmp_section(
|
||||
name='cisco_bgp_peer',
|
||||
parse_function=parse_cisco_bgp_peer,
|
||||
fetch=[
|
||||
SNMPTree(
|
||||
base='.1.3.6.1.4.1.9.9.187.1.2.5.1', # CCISCO-BGP4-MIB::cbgpPeer2Entry
|
||||
oids=[
|
||||
OIDEnd(),
|
||||
'3', # cbgpPeer2State
|
||||
'4', # cbgpPeer2AdminStatus
|
||||
'6', # cbgpPeer2LocalAddr
|
||||
'8', # cbgpPeer2LocalAs -> empty
|
||||
'9', # cbgpPeer2LocalIdentifier
|
||||
'11', # cbgpPeer2RemoteAs
|
||||
'12', # cbgpPeer2RemoteIdentifier
|
||||
'13', # cbgpPeer2InUpdates
|
||||
'14', # cbgpPeer2OutUpdates
|
||||
'15', # cbgpPeer2InTotalMessages
|
||||
'16', # cbgpPeer2OutTotalMessages
|
||||
'17', # cbgpPeer2LastError
|
||||
'18', # cbgpPeer2FsmEstablishedTransitions
|
||||
'19', # cbgpPeer2FsmEstablishedTime
|
||||
'27', # cbgpPeer2InUpdateElapsedTime
|
||||
'28', # cbgpPeer2LastErrorTxt
|
||||
'29', # cbgpPeer2PrevState
|
||||
]
|
||||
),
|
||||
SNMPTree(
|
||||
base='.1.3.6.1.4.1.9.9.187.1.2.9.1', # CCISCO-BGP4-MIB::cbgpPeer3Entry
|
||||
oids=[
|
||||
OIDEnd(),
|
||||
'5', # cbgpPeer3State
|
||||
'6', # cbgpPeer3AdminStatus
|
||||
'8', # cbgpPeer3LocalAddr
|
||||
'10', # cbgpPeer3LocalAs -> empty
|
||||
'11', # cbgpPeer3LocalIdentifier
|
||||
'13', # cbgpPeer3RemoteAs
|
||||
'14', # cbgpPeer3RemoteIdentifier
|
||||
'15', # cbgpPeer3InUpdates
|
||||
'16', # cbgpPeer3OutUpdates
|
||||
'17', # cbgpPeer3InTotalMessages
|
||||
'18', # cbgpPeer3OutTotalMessages
|
||||
'19', # cbgpPeer3LastError
|
||||
'20', # cbgpPeer3FsmEstablishedTransitions
|
||||
'21', # cbgpPeer3FsmEstablishedTime
|
||||
'29', # cbgpPeer3InUpdateElapsedTime
|
||||
'30', # cbgpPeer3LastErrorTxt
|
||||
'31', # cbgpPeer3PrevState
|
||||
]
|
||||
),
|
||||
SNMPTree(
|
||||
base='.1.3.6.1.4.1.9.9.187.1.2', # cbgpPeer
|
||||
oids=[
|
||||
OIDEnd(), #
|
||||
# .7.1 --> cbgpPeer2AddrFamilyEntry
|
||||
'7.1.3', # cbgpPeer2AddrFamilyName
|
||||
# .8.1 --> cbgpPeer2AddrFamilyPrefixEntry
|
||||
'8.1.1', # cbgpPeer2AcceptedPrefixes
|
||||
'8.1.2', # cbgpPeer2DeniedPrefixes
|
||||
'8.1.3', # cbgpPeer2PrefixAdminLimit
|
||||
'8.1.4', # cbgpPeer2PrefixThreshold
|
||||
'8.1.5', # cbgpPeer2PrefixClearThreshold
|
||||
'8.1.6', # cbgpPeer2AdvertisedPrefixes
|
||||
'8.1.7', # cbgpPeer2SuppressedPrefixes
|
||||
'8.1.8', # cbgpPeer2WithdrawnPrefixes
|
||||
]
|
||||
)
|
||||
],
|
||||
detect=contains('.1.3.6.1.2.1.1.1.0', 'Cisco'),
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name='cisco_bgp_peer',
|
||||
service_name='Cisco BGP peer %s',
|
||||
discovery_function=discovery_cisco_bgp_peer,
|
||||
check_function=check_cisco_bgp_peer,
|
||||
check_default_parameters={
|
||||
'minuptime': (7200, 3600),
|
||||
'useaslocalas': 0,
|
||||
'htmloutput': False,
|
||||
'noprefixlimit': 1,
|
||||
'infotext_values': [],
|
||||
'peer_list': [],
|
||||
},
|
||||
check_ruleset_name='cisco_bgp_peer',
|
||||
)
|
@ -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 : 2017-12-26
|
||||
#
|
||||
# Cisco BGP Peer metrics plugin
|
||||
#
|
||||
# 2018-05-25: cleanup
|
||||
# 2020-09-10: fixed typo FMS --> FSM (Thanks martin[dot]pechstein[at]posteo[dot]de)
|
||||
# 2021-03-27: rewrite for CMK 2.0
|
||||
#
|
||||
from cmk.gui.i18n import _
|
||||
|
||||
from cmk.gui.plugins.metrics import (
|
||||
metric_info,
|
||||
graph_info,
|
||||
perfometer_info
|
||||
)
|
||||
|
||||
|
||||
#####################################################################################################################
|
||||
#
|
||||
# define metrics for bgp peer perfdata
|
||||
#
|
||||
#####################################################################################################################
|
||||
|
||||
metric_info['cisco_bgp_peer_acceptedprefixes'] = {
|
||||
'title': _('Prefixes accepted'),
|
||||
'help': _('number of accepted prefixes'),
|
||||
'unit': 'count',
|
||||
'color': '11/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_deniedprefixes'] = {
|
||||
'title': _('Prefixes denied'),
|
||||
'unit': '1/s',
|
||||
'color': '21/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_advertisedprefixes'] = {
|
||||
'title': _('Prefixes advertised'),
|
||||
'unit': '1/s',
|
||||
'color': '31/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_withdrawnprefixes'] = {
|
||||
'title': _('Prefixes withdrawn'),
|
||||
'unit': '1/s',
|
||||
'color': '41/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_suppressedprefixes'] = {
|
||||
'title': _('Prefixes suppressed'),
|
||||
'unit': '1/s',
|
||||
'color': '12/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_inupdates'] = {
|
||||
'title': _('Updates received'),
|
||||
'unit': '1/s',
|
||||
'color': '22/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_outupdates'] = {
|
||||
'title': _('Updates send'),
|
||||
'unit': '1/s',
|
||||
'color': '32/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_intotalmessages'] = {
|
||||
'title': _('Total messages received'),
|
||||
'unit': '1/s',
|
||||
'color': '42/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_outtotalmessages'] = {
|
||||
'title': _('Total messages send'),
|
||||
'unit': '1/s',
|
||||
'color': '13/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_fsmestablishedtransitions'] = {
|
||||
'title': _('FSM transitions'),
|
||||
'unit': 'count',
|
||||
'color': '23/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_fsmestablishedtime'] = {
|
||||
'title': _('FSM last change'),
|
||||
'unit': 's',
|
||||
'color': '26/a',
|
||||
}
|
||||
metric_info['cisco_bgp_peer_inupdateelapsedtime'] = {
|
||||
'title': _('Last update received'),
|
||||
'unit': 's',
|
||||
'color': '43/a',
|
||||
}
|
||||
|
||||
|
||||
######################################################################################################################
|
||||
#
|
||||
# how to graph perdata for bgp peer
|
||||
#
|
||||
######################################################################################################################
|
||||
|
||||
graph_info['cisco_bgp_peer.prefixes_accepted']={
|
||||
'title': _('Accepted Prefixes'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_acceptedprefixes', 'line'),
|
||||
],
|
||||
'scalars': [
|
||||
('cisco_bgp_peer_acceptedprefixes:crit', _('crit')),
|
||||
('cisco_bgp_peer_acceptedprefixes:warn', _('warn')),
|
||||
],
|
||||
}
|
||||
|
||||
graph_info['cisco_bgp_peer.prefixes_per_second']={
|
||||
'title': _('Prefixes/s'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_deniedprefixes', 'line'),
|
||||
('cisco_bgp_peer_advertisedprefixes', 'line'),
|
||||
('cisco_bgp_peer_withdrawnprefixes', 'line'),
|
||||
('cisco_bgp_peer_suppressedprefixes', 'line'),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
graph_info['cisco_bgp_peer.updates_in_out']={
|
||||
'title': _('Updates'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_inupdates', 'area'),
|
||||
('cisco_bgp_peer_outupdates', '-area'),
|
||||
]
|
||||
}
|
||||
|
||||
graph_info['cisco_bgp_peer.messages_in_out']={
|
||||
'title': _('Total messages'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_intotalmessages', 'area'),
|
||||
('cisco_bgp_peer_outtotalmessages', '-area'),
|
||||
]
|
||||
}
|
||||
|
||||
graph_info['cisco_bgp_peer.fms_transitions_from_to']={
|
||||
'title': _('FSM transitions from/to established'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_fsmestablishedtransitions', 'line'),
|
||||
],
|
||||
}
|
||||
|
||||
graph_info['cisco_bgp_peer.fms_transitions_last_change']={
|
||||
'title': _('FSM established last change'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_fsmestablishedtime', 'line'),
|
||||
]
|
||||
}
|
||||
|
||||
graph_info['cisco_bgp_peer.time_since_last_update']={
|
||||
'title': _('Time since last update received'),
|
||||
'metrics': [
|
||||
('cisco_bgp_peer_inupdateelapsedtime', 'line'),
|
||||
]
|
||||
}
|
||||
|
||||
######################################################################################################################
|
||||
#
|
||||
# define perf-o-meter for bgp peer uptime + prefixes accepted/advertised
|
||||
#
|
||||
######################################################################################################################
|
||||
|
||||
perfometer_info.append(('stacked', [
|
||||
{
|
||||
'type': 'logarithmic',
|
||||
'metric': 'cisco_bgp_peer_fsmestablishedtime',
|
||||
'half_value': 2592000.0, # ome month
|
||||
'exponent': 2,
|
||||
},
|
||||
{
|
||||
'type': 'logarithmic',
|
||||
'metric': 'cisco_bgp_peer_acceptedprefixes',
|
||||
'half_value': 500000.0,
|
||||
'exponent': 2,
|
||||
}
|
||||
]))
|
@ -0,0 +1,126 @@
|
||||
#!/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 : 2017-12-25
|
||||
#
|
||||
# Check_MK cisco_bgp_peers WATO plugin
|
||||
#
|
||||
# 2021-03-27: rewrite for CMK 2.0
|
||||
#
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.valuespec import (
|
||||
Dictionary,
|
||||
Integer,
|
||||
TextAscii,
|
||||
ListOfStrings,
|
||||
FixedValue,
|
||||
ListChoice,
|
||||
ListOf,
|
||||
Tuple,
|
||||
TextUnicode,
|
||||
MonitoringState,
|
||||
)
|
||||
|
||||
from cmk.gui.plugins.wato import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersNetworking,
|
||||
)
|
||||
|
||||
cisco_bgp_peer_infotext_values = [
|
||||
('remoteid', 'Remote router ID'),
|
||||
('remoteas', 'Remote autonomous system'),
|
||||
('localaddr', 'Local peer IP address'),
|
||||
('localid', 'Local router ID'),
|
||||
('localas', 'Local autonomous system'),
|
||||
]
|
||||
|
||||
|
||||
def _parameter_valuespec_cisco_bgp_peer():
|
||||
return Dictionary(elements=[
|
||||
('minuptime',
|
||||
Tuple(
|
||||
title=_('Minimum uptime for peer'),
|
||||
help=_('Set the time in seconds, a peer must be up before the peer is considered sable.'
|
||||
'If the peer uptime less then X, the check outcome is set to warning.'),
|
||||
elements=[
|
||||
Integer(title=_('Warning if below'), unit='seconds', default_value=7200, minvalue=0),
|
||||
Integer(title=_('Critical if below'), unit='seconsa', default_value=3600, minvalue=0)
|
||||
],
|
||||
),
|
||||
),
|
||||
('useaslocalas',
|
||||
Integer(
|
||||
help=_('Use this AS number if the SNMP Value for CISCO-BGP4-MIB::cbgpPeer2LocalAs is \'0\'.'),
|
||||
title=_('Use AS as local AS, if SNMP cbgpPeer2LocalAs is not valid.'),
|
||||
default_value=0,
|
||||
# allow_empty=False,
|
||||
),
|
||||
),
|
||||
('htmloutput',
|
||||
FixedValue(
|
||||
True,
|
||||
help=_('render long output of check plugin (multiline) as HTML table. Needs \'Escape HTML codes in plugin output\' in wato --> global settings disabled'),
|
||||
title=_('enable HTML Output for long output of check plugin (multiline)'),
|
||||
totext=_('enable HTML Output for long output of check plugin (multiline)'),
|
||||
default_value=False,
|
||||
)),
|
||||
('noprefixlimit',
|
||||
MonitoringState(
|
||||
default_value=1,
|
||||
title=_('State if no admin prefix limit/warn threshold configured.'),
|
||||
help=_('The admin prefix limit and warn threshold needs to be configured on the device. '
|
||||
'For example: neighbor 172.17.10.10 maximum-prefix 10000 80. The threshold is in percentage '
|
||||
'of the prefix limit')
|
||||
)),
|
||||
('infotext_values',
|
||||
ListChoice(
|
||||
title=_('Add values to check info'),
|
||||
help=_('Select values to add to the check output.'),
|
||||
choices=cisco_bgp_peer_infotext_values,
|
||||
default_value=[],
|
||||
)),
|
||||
('peer_list',
|
||||
ListOf(
|
||||
Tuple(
|
||||
# title=('BGP Peers'),
|
||||
elements=[
|
||||
TextUnicode(
|
||||
title=_('BGP Peer item name (without "Cisco BGP peer")'),
|
||||
help=_('The configured value must match a BGP item reported by the monitored '
|
||||
'device. For example: "10.194.115.98 IPv4 Unicast"'),
|
||||
allow_empty=False,
|
||||
),
|
||||
TextUnicode(
|
||||
title=_('BGP Peer Alias'),
|
||||
help=_('You can configure an individual alias here for the BGP peer matching '
|
||||
'the text configured in the "BGP Peer item name" field. The alias will '
|
||||
'be shown in the infotext'),
|
||||
),
|
||||
MonitoringState(
|
||||
default_value=2,
|
||||
title=_('State if not found'),
|
||||
help=_('You can configure an individual state if the BGP peer matching the text '
|
||||
'configured in the "BGP Peer item name" field is not found')
|
||||
),
|
||||
]),
|
||||
add_label=_('Add BGP peer'),
|
||||
movable=False,
|
||||
title=_('BGP Peers'),
|
||||
)),
|
||||
])
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name='cisco_bgp_peer',
|
||||
group=RulespecGroupCheckParametersNetworking,
|
||||
item_spec=lambda: TextAscii(title=_('BGP peer specific configuration'), ),
|
||||
match_type='dict',
|
||||
parameter_valuespec=_parameter_valuespec_cisco_bgp_peer,
|
||||
title=lambda: _('Cisco BGP peer'),
|
||||
))
|
1
cisco-gdoi/README.md
Normal file
1
cisco-gdoi/README.md
Normal file
@ -0,0 +1 @@
|
||||
This plugin checks the registration status (and KEK key timeout) of GDOI Group Members with GDOI Key Servers.
|
BIN
cisco-gdoi/cisco_gdoi-1.1.0.mkp
Executable file
BIN
cisco-gdoi/cisco_gdoi-1.1.0.mkp
Executable file
Binary file not shown.
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# More information about this Cisco system:
|
||||
# https://www.cisco.com/en/US/docs/ios-xml/ios/sec_conn_getvpn/configuration/15-2mt/sec-get-vpn.html
|
||||
#
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import (
|
||||
register,
|
||||
Service,
|
||||
Result,
|
||||
State,
|
||||
SNMPTree,
|
||||
contains,
|
||||
OIDEnd,
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Section:
|
||||
kek_info: dict
|
||||
|
||||
def chars_to_ip_addr(chars):
|
||||
return ".".join(map(lambda c: str(ord(c)), [*chars]))
|
||||
|
||||
conversions = {
|
||||
"1": "using",
|
||||
"2": "new",
|
||||
"3": "old",
|
||||
}
|
||||
|
||||
# SNMP parsing function
|
||||
def parse_cisco_gdoi(string_table):
|
||||
def parse(data):
|
||||
lookup = {}
|
||||
|
||||
for val in data:
|
||||
ip = chars_to_ip_addr(val[0])
|
||||
remaining = int(val[1])
|
||||
state = conversions[val[2]]
|
||||
|
||||
lookup.setdefault(ip, {})
|
||||
lookup[ip][state] = remaining
|
||||
|
||||
return lookup
|
||||
|
||||
if string_table == [[]]:
|
||||
return
|
||||
|
||||
return Section(
|
||||
kek_info=parse(string_table[0]),
|
||||
)
|
||||
|
||||
# Inventory function, returning inventory based upon SNMP parsed result above
|
||||
def discovery_cisco_gdoi(section):
|
||||
yield Service(item="Keyservers", parameters=section.kek_info)
|
||||
|
||||
# Check function, returning ok/crit based upon SNMP parsed result above
|
||||
def check_cisco_gdoi(item, params, section):
|
||||
state = params
|
||||
registered = False
|
||||
|
||||
for ip, state in params.items():
|
||||
in_use = state.get("using")
|
||||
|
||||
if in_use > 0:
|
||||
registered = True
|
||||
yield Result(state=State.OK, summary="Registered, using KEK from " + ip)
|
||||
|
||||
if not registered:
|
||||
yield Result(state=State.CRIT, summary="Unregistered")
|
||||
|
||||
register.snmp_section(
|
||||
name="cisco_gdoi",
|
||||
parse_function=parse_cisco_gdoi,
|
||||
fetch=[
|
||||
SNMPTree(
|
||||
# ciscoGdoiMIB::cgmGdoiGmKekRemainingLifetime
|
||||
base=".1.3.6.1.4.1.9.9.759.1.3.2.1",
|
||||
oids=[
|
||||
"5", # cgmGdoiGmKekSrcIdValue
|
||||
"20", # cgmGdoiGmKekRemainingLifetime
|
||||
"21", # cgmGdoiGmKekStatus
|
||||
]
|
||||
),
|
||||
],
|
||||
detect=contains(".1.3.6.1.2.1.1.1.0", "Cisco"),
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="cisco_gdoi",
|
||||
service_name="Cisco GDOI %s",
|
||||
discovery_function=discovery_cisco_gdoi,
|
||||
check_function=check_cisco_gdoi,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="cisco_gdoi",
|
||||
)
|
BIN
cisco-ip-sla/2.2/cisco_ip_sla-1.0.1.mkp
Normal file
BIN
cisco-ip-sla/2.2/cisco_ip_sla-1.0.1.mkp
Normal file
Binary file not shown.
@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersNetworking,
|
||||
)
|
||||
from cmk.gui.valuespec import Dictionary, DropdownChoice, Integer, TextInput, Tuple
|
||||
|
||||
|
||||
def _item_spec_cisco_ip_sla():
|
||||
return TextInput(
|
||||
title=_("RTT row index of the service"),
|
||||
allow_empty=True,
|
||||
)
|
||||
|
||||
|
||||
def _parameter_valuespec_cisco_ip_sla():
|
||||
return Dictionary(
|
||||
elements=[
|
||||
(
|
||||
"rtt_type",
|
||||
DropdownChoice(
|
||||
title=_("RTT type"),
|
||||
choices=[
|
||||
("echo", _("echo")),
|
||||
("path echo", _("path echo")),
|
||||
("file IO", _("file IO")),
|
||||
("UDP echo", _("UDP echo")),
|
||||
("TCP connect", _("TCP connect")),
|
||||
("HTTP", _("HTTP")),
|
||||
("DNS", _("DNS")),
|
||||
("jitter", _("jitter")),
|
||||
("DLSw", _("DLSw")),
|
||||
("DHCP", _("DHCP")),
|
||||
("FTP", _("FTP")),
|
||||
("VoIP", _("VoIP")),
|
||||
("RTP", _("RTP")),
|
||||
("LSP group", _("LSP group")),
|
||||
("ICMP jitter", _("ICMP jitter")),
|
||||
("LSP ping", _("LSP ping")),
|
||||
("LSP trace", _("LSP trace")),
|
||||
("ethernet ping", _("ethernet ping")),
|
||||
("ethernet jitter", _("ethernet jitter")),
|
||||
("LSP ping pseudowire", _("LSP ping pseudowire")),
|
||||
],
|
||||
default_value="echo",
|
||||
),
|
||||
),
|
||||
(
|
||||
"threshold",
|
||||
Integer(
|
||||
title=_("Treshold"),
|
||||
help=_(
|
||||
"Depending on the precision the unit can be "
|
||||
"either milliseconds or micoseconds."
|
||||
),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=5000,
|
||||
),
|
||||
),
|
||||
(
|
||||
"state",
|
||||
DropdownChoice(
|
||||
title=_("State"),
|
||||
choices=[
|
||||
("active", _("active")),
|
||||
("inactive", _("inactive")),
|
||||
("reset", _("reset")),
|
||||
("orderly stop", _("orderly stop")),
|
||||
("immediate stop", _("immediate stop")),
|
||||
("pending", _("pending")),
|
||||
("restart", _("restart")),
|
||||
],
|
||||
default_value="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"connection_lost_occured",
|
||||
DropdownChoice(
|
||||
title=_("Connection lost occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"timeout_occured",
|
||||
DropdownChoice(
|
||||
title=_("Timeout occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"completion_time_over_treshold_occured",
|
||||
DropdownChoice(
|
||||
title=_("Completion time over treshold occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"latest_rtt_completion_time",
|
||||
Tuple(
|
||||
title=_("Latest RTT completion time"),
|
||||
help=_(
|
||||
"Depending on the precision the unit can be "
|
||||
"either milliseconds or micoseconds."
|
||||
),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=200,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"latest_rtt_state",
|
||||
DropdownChoice(
|
||||
title=_("Latest RTT state"),
|
||||
choices=[
|
||||
("ok", _("OK")),
|
||||
("disconnected", _("disconnected")),
|
||||
("over treshold", _("over treshold")),
|
||||
("timeout", _("timeout")),
|
||||
("other", _("other")),
|
||||
],
|
||||
default_value="ok",
|
||||
),
|
||||
),
|
||||
(
|
||||
"packets_lost_src->dest",
|
||||
Tuple(
|
||||
title=_("Packets lost src->dest"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=1000,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"packets_lost_dest->src",
|
||||
Tuple(
|
||||
title=_("Packets lost dest->src"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=1000,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="cisco_ip_sla",
|
||||
group=RulespecGroupCheckParametersNetworking,
|
||||
item_spec=_item_spec_cisco_ip_sla,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_parameter_valuespec_cisco_ip_sla,
|
||||
title=lambda: _("Cisco IP SLA"),
|
||||
)
|
||||
)
|
237
cisco-ip-sla/2.2/local/share/check_mk/checks/cisco_ip_sla
Normal file
237
cisco-ip-sla/2.2/local/share/check_mk/checks/cisco_ip_sla
Normal file
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
factory_settings["cisco_ip_sla_default_levels"] = {
|
||||
"state": "active",
|
||||
"connection_lost_occured": "no",
|
||||
"timeout_occured": "no",
|
||||
"completion_time_over_treshold_occured": "no",
|
||||
"latest_rtt_completion_time": (250, 500),
|
||||
"latest_rtt_state": "ok",
|
||||
"packets_lost_src->dest": (100, 1000),
|
||||
"packets_lost_dest->src": (100, 1000),
|
||||
}
|
||||
|
||||
|
||||
def parse_cisco_ip_sla(info):
|
||||
precisions = {line[0]: "ms" if line[-1] == "1" else "us" for line in info[0]}
|
||||
|
||||
rtt_types = {
|
||||
"1": "echo",
|
||||
"2": "path echo",
|
||||
"3": "file IO",
|
||||
"4": "script",
|
||||
"5": "UDP echo",
|
||||
"6": "TCP connect",
|
||||
"7": "HTTP",
|
||||
"8": "DNS",
|
||||
"9": "jitter",
|
||||
"10": "DLSw",
|
||||
"11": "DHCP",
|
||||
"12": "FTP",
|
||||
"13": "VoIP",
|
||||
"14": "RTP",
|
||||
"15": "LSP group",
|
||||
"16": "ICMP jitter",
|
||||
"17": "LSP ping",
|
||||
"18": "LSP trace",
|
||||
"19": "ethernet ping",
|
||||
"20": "ethernet jitter",
|
||||
"21": "LSP ping pseudowire",
|
||||
}
|
||||
|
||||
states = {
|
||||
"1": "reset",
|
||||
"2": "orderly stop",
|
||||
"3": "immediate stop",
|
||||
"4": "pending",
|
||||
"5": "inactive",
|
||||
"6": "active",
|
||||
"7": "restart",
|
||||
}
|
||||
|
||||
rtt_states = {
|
||||
"0": "other",
|
||||
"1": "ok",
|
||||
"2": "disconnected",
|
||||
"3": "over threshold",
|
||||
"4": "timeout",
|
||||
"5": "busy",
|
||||
"6": "not connected",
|
||||
"7": "dropped",
|
||||
"8": "sequence error",
|
||||
"9": "verify error",
|
||||
"10": "application specific error",
|
||||
}
|
||||
|
||||
def to_ip_address(int_list):
|
||||
if len(int_list) == 4:
|
||||
return "%d.%d.%d.%d" % tuple(int_list)
|
||||
elif len(int_list) == 6:
|
||||
return "%d:%d:%d:%d:%d:%d" % tuple(int_list)
|
||||
return ""
|
||||
|
||||
# contains description, parse function, unit and type
|
||||
contents = [
|
||||
( # rttMonEchoAdminEntry
|
||||
("Target address", to_ip_address, "", None),
|
||||
("Source address", to_ip_address, "", None),
|
||||
# rttMonEchoAdminPrecision is deliberatly dropped by zip below
|
||||
),
|
||||
( # rttMonCtrlAdminEntry
|
||||
("Owner", None, "", None),
|
||||
("Tag", None, "", None),
|
||||
("RTT type", lambda x: rtt_types.get(x, "unknown"), "", "option"),
|
||||
("Threshold", int, "ms", "option"),
|
||||
),
|
||||
( # rttMonCtrlOperEntry
|
||||
("State", lambda x: states.get(x, "unknown"), "", "option"),
|
||||
("Text", None, "", None),
|
||||
("Connection lost occured", lambda x: "yes" if x == "1" else "no", "", "option"),
|
||||
("Timeout occured", lambda x: "yes" if x == "1" else "no", "", "option"),
|
||||
(
|
||||
"Completion time over treshold occured",
|
||||
lambda x: "yes" if x == "1" else "no",
|
||||
"",
|
||||
"option",
|
||||
),
|
||||
),
|
||||
( # rttMonLatestRttOperEntry
|
||||
("Latest RTT completion time", int, "ms/us", "level"),
|
||||
("Latest RTT state", lambda x: rtt_states.get(x, "unknown"), "", "option"),
|
||||
),
|
||||
( # rttMonJitterStatsEntry
|
||||
("Packets lost src->dest", int, "packets", "level"),
|
||||
("Packets lost dest->src", int, "packets", "level"),
|
||||
),
|
||||
]
|
||||
|
||||
parsed = {}
|
||||
for content, entries in zip(contents, info):
|
||||
if not entries:
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
index, values = entry[0], entry[1:]
|
||||
data = parsed.setdefault(index, [])
|
||||
for (description, parser, unit, type_), value in zip(content, values):
|
||||
if parser:
|
||||
value = parser(value)
|
||||
if unit == "ms/us":
|
||||
unit = precisions[index]
|
||||
data.append((description, value, unit, type_))
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def inventory_cisco_ip_sla(parsed):
|
||||
for index in parsed:
|
||||
yield index, {}
|
||||
|
||||
|
||||
@get_parsed_item_data
|
||||
def check_cisco_ip_sla(_item, params, data):
|
||||
for description, value, unit, type_ in data:
|
||||
if not value and "packets" not in unit:
|
||||
continue
|
||||
|
||||
state = 0
|
||||
if unit:
|
||||
infotext = "%s: %s %s" % (description, value, unit)
|
||||
else:
|
||||
infotext = "%s: %s" % (description, value)
|
||||
perfdata = []
|
||||
|
||||
param = params.get(description.lower().replace(" ", "_"))
|
||||
|
||||
if type_ == "option":
|
||||
if param and param != value:
|
||||
state = 1
|
||||
infotext += " (expected %s)" % param
|
||||
elif type_ == "level":
|
||||
warn, crit = param # a default level hat to exist
|
||||
if value >= crit:
|
||||
state = 2
|
||||
elif value >= warn:
|
||||
state = 1
|
||||
|
||||
if state:
|
||||
infotext += " (warn/crit at %s/%s)" % (warn, crit)
|
||||
|
||||
if unit == "ms" or unit == "us" or unit == "ms/us":
|
||||
factor = 1e3 if unit == "ms" else 1e6
|
||||
perfdata = [
|
||||
("rtt", value / factor, warn / factor, crit / factor)
|
||||
] # fixed: true-division
|
||||
elif unit == "packets":
|
||||
perfdata = [
|
||||
("lost", value, warn, crit)
|
||||
]
|
||||
|
||||
yield state, infotext, perfdata
|
||||
|
||||
|
||||
check_info["cisco_ip_sla"] = {
|
||||
"parse_function": parse_cisco_ip_sla,
|
||||
"inventory_function": inventory_cisco_ip_sla,
|
||||
"check_function": check_cisco_ip_sla,
|
||||
"service_description": "Cisco IP SLA %s",
|
||||
"group": "cisco_ip_sla",
|
||||
"default_levels_variable": "cisco_ip_sla_default_levels",
|
||||
"has_perfdata": True,
|
||||
"snmp_scan_function": lambda oid: "cisco" in oid(".1.3.6.1.2.1.1.1.0").lower()
|
||||
and "os" in oid(".1.3.6.1.2.1.1.1.0").lower()
|
||||
# and oid(".1.3.6.1.4.1.9.9.42.1.2.2.1.37.*"),
|
||||
"snmp_info": [
|
||||
(
|
||||
".1.3.6.1.4.1.9.9.42.1.2.2.1",
|
||||
[
|
||||
OID_END,
|
||||
BINARY(2), # rttMonEchoAdminTargetAddress
|
||||
BINARY(6), # rttMonEchoAdminSourceAddress
|
||||
# only needed to determine the unit (ms/us)
|
||||
37, # rttMonEchoAdminPrecision
|
||||
],
|
||||
),
|
||||
(
|
||||
".1.3.6.1.4.1.9.9.42.1.2.1.1",
|
||||
[
|
||||
OID_END,
|
||||
2, # rttMonCtrlAdminOwner
|
||||
3, # rttMonCtrlAdminTag
|
||||
4, # rttMonCtrlAdminRttType
|
||||
5, # rttMonCtrlAdminThreshold
|
||||
],
|
||||
),
|
||||
(
|
||||
".1.3.6.1.4.1.9.9.42.1.2.9.1",
|
||||
[
|
||||
OID_END,
|
||||
10, # rttMonCtrlOperState
|
||||
2, # rttMonCtrlOperDiagText
|
||||
5, # rttMonCtrlOperConnectionLostOccurred
|
||||
6, # rttMonCtrlOperTimeoutOccurred
|
||||
7, # rttMonCtrlOperOverThresholdOccurred
|
||||
],
|
||||
),
|
||||
(
|
||||
".1.3.6.1.4.1.9.9.42.1.2.10.1",
|
||||
[
|
||||
OID_END,
|
||||
1, # rttMonLatestRttOperCompletionTime
|
||||
2, # rttMonLatestRttOperSense
|
||||
],
|
||||
),
|
||||
(
|
||||
".1.3.6.1.4.1.9.9.42.1.5.2.1",
|
||||
[
|
||||
OID_END,
|
||||
26, # rttMonLatestJitterOperPacketLossSD
|
||||
27, # rttMonLatestJitterOperPacketLossDS
|
||||
]
|
||||
)
|
||||
],
|
||||
}
|
BIN
cisco-ip-sla/2.3/cisco_ip_sla-0.1.0.mkp
Executable file
BIN
cisco-ip-sla/2.3/cisco_ip_sla-0.1.0.mkp
Executable file
Binary file not shown.
@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersNetworking,
|
||||
)
|
||||
from cmk.gui.valuespec import Dictionary, DropdownChoice, Integer, TextInput, Tuple
|
||||
|
||||
|
||||
def _item_spec_cisco_ip_sla():
|
||||
return TextInput(
|
||||
title=_("RTT row index of the service"),
|
||||
allow_empty=True,
|
||||
)
|
||||
|
||||
|
||||
def _parameter_valuespec_cisco_ip_sla():
|
||||
return Dictionary(
|
||||
elements=[
|
||||
(
|
||||
"rtt_type",
|
||||
DropdownChoice(
|
||||
title=_("RTT type"),
|
||||
choices=[
|
||||
("echo", _("echo")),
|
||||
("path echo", _("path echo")),
|
||||
("file IO", _("file IO")),
|
||||
("UDP echo", _("UDP echo")),
|
||||
("TCP connect", _("TCP connect")),
|
||||
("HTTP", _("HTTP")),
|
||||
("DNS", _("DNS")),
|
||||
("jitter", _("jitter")),
|
||||
("DLSw", _("DLSw")),
|
||||
("DHCP", _("DHCP")),
|
||||
("FTP", _("FTP")),
|
||||
("VoIP", _("VoIP")),
|
||||
("RTP", _("RTP")),
|
||||
("LSP group", _("LSP group")),
|
||||
("ICMP jitter", _("ICMP jitter")),
|
||||
("LSP ping", _("LSP ping")),
|
||||
("LSP trace", _("LSP trace")),
|
||||
("ethernet ping", _("ethernet ping")),
|
||||
("ethernet jitter", _("ethernet jitter")),
|
||||
("LSP ping pseudowire", _("LSP ping pseudowire")),
|
||||
],
|
||||
default_value="echo",
|
||||
),
|
||||
),
|
||||
(
|
||||
"threshold",
|
||||
Integer(
|
||||
title=_("Treshold"),
|
||||
help=_(
|
||||
"Depending on the precision the unit can be "
|
||||
"either milliseconds or micoseconds."
|
||||
),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=5000,
|
||||
),
|
||||
),
|
||||
(
|
||||
"state",
|
||||
DropdownChoice(
|
||||
title=_("State"),
|
||||
choices=[
|
||||
("active", _("active")),
|
||||
("inactive", _("inactive")),
|
||||
("reset", _("reset")),
|
||||
("orderly stop", _("orderly stop")),
|
||||
("immediate stop", _("immediate stop")),
|
||||
("pending", _("pending")),
|
||||
("restart", _("restart")),
|
||||
],
|
||||
default_value="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"connection_lost_occured",
|
||||
DropdownChoice(
|
||||
title=_("Connection lost occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"timeout_occured",
|
||||
DropdownChoice(
|
||||
title=_("Timeout occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"completion_time_over_treshold_occured",
|
||||
DropdownChoice(
|
||||
title=_("Completion time over treshold occured"),
|
||||
choices=[
|
||||
("yes", _("yes")),
|
||||
("no", _("no")),
|
||||
],
|
||||
default_value="no",
|
||||
),
|
||||
),
|
||||
(
|
||||
"latest_rtt_completion_time",
|
||||
Tuple(
|
||||
title=_("Latest RTT completion time"),
|
||||
help=_(
|
||||
"Depending on the precision the unit can be "
|
||||
"either milliseconds or micoseconds."
|
||||
),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("ms/us"),
|
||||
minvalue=1,
|
||||
default_value=200,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"latest_rtt_state",
|
||||
DropdownChoice(
|
||||
title=_("Latest RTT state"),
|
||||
choices=[
|
||||
("ok", _("OK")),
|
||||
("disconnected", _("disconnected")),
|
||||
("over treshold", _("over treshold")),
|
||||
("timeout", _("timeout")),
|
||||
("other", _("other")),
|
||||
],
|
||||
default_value="ok",
|
||||
),
|
||||
),
|
||||
(
|
||||
"packets_lost_src->dest",
|
||||
Tuple(
|
||||
title=_("Packets lost src->dest"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=1000,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"packets_lost_dest->src",
|
||||
Tuple(
|
||||
title=_("Packets lost dest->src"),
|
||||
elements=[
|
||||
Integer(
|
||||
title=_("Warning at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=100,
|
||||
),
|
||||
Integer(
|
||||
title=_("Critical at"),
|
||||
unit=_("packets"),
|
||||
minvalue=1,
|
||||
default_value=1000,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="cisco_ip_sla",
|
||||
group=RulespecGroupCheckParametersNetworking,
|
||||
item_spec=_item_spec_cisco_ip_sla,
|
||||
match_type="dict",
|
||||
parameter_valuespec=_parameter_valuespec_cisco_ip_sla,
|
||||
title=lambda: _("Cisco IP SLA"),
|
||||
)
|
||||
)
|
225
cisco-ip-sla/2.3/local/share/check_mk/checks/cisco_ip_sla
Normal file
225
cisco-ip-sla/2.3/local/share/check_mk/checks/cisco_ip_sla
Normal file
@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from cmk.base.check_api import LegacyCheckDefinition
|
||||
from cmk.base.config import check_info
|
||||
|
||||
from cmk.agent_based.v2 import all_of, contains, exists, OIDBytes, OIDEnd, SNMPTree
|
||||
|
||||
|
||||
def parse_cisco_ip_sla(string_table):
|
||||
precisions = {line[0]: "ms" if line[-1] == "1" else "us" for line in string_table[0]}
|
||||
|
||||
rtt_types = {
|
||||
"1": "echo",
|
||||
"2": "path echo",
|
||||
"3": "file IO",
|
||||
"4": "script",
|
||||
"5": "UDP echo",
|
||||
"6": "TCP connect",
|
||||
"7": "HTTP",
|
||||
"8": "DNS",
|
||||
"9": "jitter",
|
||||
"10": "DLSw",
|
||||
"11": "DHCP",
|
||||
"12": "FTP",
|
||||
"13": "VoIP",
|
||||
"14": "RTP",
|
||||
"15": "LSP group",
|
||||
"16": "ICMP jitter",
|
||||
"17": "LSP ping",
|
||||
"18": "LSP trace",
|
||||
"19": "ethernet ping",
|
||||
"20": "ethernet jitter",
|
||||
"21": "LSP ping pseudowire",
|
||||
}
|
||||
|
||||
states = {
|
||||
"1": "reset",
|
||||
"2": "orderly stop",
|
||||
"3": "immediate stop",
|
||||
"4": "pending",
|
||||
"5": "inactive",
|
||||
"6": "active",
|
||||
"7": "restart",
|
||||
}
|
||||
|
||||
rtt_states = {
|
||||
"0": "other",
|
||||
"1": "ok",
|
||||
"2": "disconnected",
|
||||
"3": "over threshold",
|
||||
"4": "timeout",
|
||||
"5": "busy",
|
||||
"6": "not connected",
|
||||
"7": "dropped",
|
||||
"8": "sequence error",
|
||||
"9": "verify error",
|
||||
"10": "application specific error",
|
||||
}
|
||||
|
||||
def to_ip_address(int_list):
|
||||
if len(int_list) == 4:
|
||||
return "%d.%d.%d.%d" % tuple(int_list)
|
||||
elif len(int_list) == 6:
|
||||
return "%d:%d:%d:%d:%d:%d" % tuple(int_list)
|
||||
return ""
|
||||
|
||||
# contains description, parse function, unit and type
|
||||
contents: Sequence[tuple[tuple[str, Callable | None, str, str | None], ...]] = [
|
||||
( # rttMonEchoAdminEntry
|
||||
("Target address", to_ip_address, "", None),
|
||||
("Source address", to_ip_address, "", None),
|
||||
# rttMonEchoAdminPrecision is deliberatly dropped by zip below
|
||||
),
|
||||
( # rttMonCtrlAdminEntry
|
||||
("Owner", None, "", None),
|
||||
("Tag", None, "", None),
|
||||
("RTT type", lambda x: rtt_types.get(x, "unknown"), "", "option"),
|
||||
("Threshold", int, "ms", "option"),
|
||||
),
|
||||
( # rttMonCtrlOperEntry
|
||||
("State", lambda x: states.get(x, "unknown"), "", "option"),
|
||||
("Text", None, "", None),
|
||||
(
|
||||
"Connection lost occured",
|
||||
lambda x: "yes" if x == "1" else "no",
|
||||
"",
|
||||
"option",
|
||||
),
|
||||
("Timeout occured", lambda x: "yes" if x == "1" else "no", "", "option"),
|
||||
(
|
||||
"Completion time over treshold occured",
|
||||
lambda x: "yes" if x == "1" else "no",
|
||||
"",
|
||||
"option",
|
||||
),
|
||||
),
|
||||
( # rttMonLatestRttOperEntry
|
||||
("Latest RTT completion time", int, "ms/us", "level"),
|
||||
("Latest RTT state", lambda x: rtt_states.get(x, "unknown"), "", "option"),
|
||||
),
|
||||
( # rttMonJitterStatsEntry
|
||||
("Packets lost src->dest", int, "packets", "level"),
|
||||
("Packets lost dest->src", int, "packets", "level"),
|
||||
),
|
||||
]
|
||||
|
||||
parsed: dict[str, list] = {}
|
||||
for content, entries in zip(contents, string_table):
|
||||
if not entries:
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
index, values = entry[0], entry[1:]
|
||||
data = parsed.setdefault(index, [])
|
||||
for (description, parser, unit, type_), value in zip(content, values):
|
||||
if parser:
|
||||
value = parser(value)
|
||||
if unit == "ms/us":
|
||||
unit = precisions[index]
|
||||
data.append((description, value, unit, type_))
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def inventory_cisco_ip_sla(parsed):
|
||||
for index in parsed:
|
||||
yield index, {}
|
||||
|
||||
|
||||
def check_cisco_ip_sla(item, params, parsed):
|
||||
if not (data := parsed.get(item)):
|
||||
return
|
||||
for description, value, unit, type_ in data:
|
||||
if not value and "packets" not in unit:
|
||||
continue
|
||||
|
||||
state = 0
|
||||
if unit:
|
||||
infotext = f"{description}: {value} {unit}"
|
||||
else:
|
||||
infotext = f"{description}: {value}"
|
||||
perfdata = []
|
||||
|
||||
param = params.get(description.lower().replace(" ", "_"))
|
||||
|
||||
if type_ == "option":
|
||||
if param and param != value:
|
||||
state = 1
|
||||
infotext += " (expected %s)" % param
|
||||
elif type_ == "level":
|
||||
warn, crit = param # a default level hat to exist
|
||||
if value >= crit:
|
||||
state = 2
|
||||
elif value >= warn:
|
||||
state = 1
|
||||
|
||||
if state:
|
||||
infotext += f" (warn/crit at {warn}/{crit})"
|
||||
|
||||
# last check ("ms/us") is probably a harmless bug, but keeping
|
||||
if unit == "ms" or unit == "us" or unit == "ms/us":
|
||||
factor = 1e3 if unit == "ms" else 1e6
|
||||
perfdata = [
|
||||
("rtt", value / factor, warn / factor, crit / factor)
|
||||
] # fixed: true-division
|
||||
elif unit == "packets":
|
||||
perfdata = [
|
||||
("lost", value, warn, crit)
|
||||
]
|
||||
|
||||
yield state, infotext, perfdata
|
||||
|
||||
|
||||
check_info["cisco_ip_sla"] = LegacyCheckDefinition(
|
||||
detect=all_of(
|
||||
contains(".1.3.6.1.2.1.1.1.0", "cisco"),
|
||||
contains(".1.3.6.1.2.1.1.1.0", "ios"),
|
||||
exists(".1.3.6.1.4.1.9.9.42.1.2.2.1.37.*"),
|
||||
),
|
||||
fetch=[
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.9.9.42.1.2.2.1",
|
||||
oids=[OIDEnd(), OIDBytes("2"), OIDBytes("6"), "37"],
|
||||
),
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.9.9.42.1.2.1.1",
|
||||
oids=[OIDEnd(), "2", "3", "4", "5"],
|
||||
),
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.9.9.42.1.2.9.1",
|
||||
oids=[OIDEnd(), "10", "2", "5", "6", "7"],
|
||||
),
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.9.9.42.1.2.10.1",
|
||||
oids=[OIDEnd(), "1", "2"],
|
||||
),
|
||||
SNMPTree(
|
||||
base=".1.3.6.1.4.1.9.9.42.1.5.2.1",
|
||||
# rttMonLatestJitterOperPacketLossSD
|
||||
# rttMonLatestJitterOperPacketLossDS
|
||||
oids=[OIDEnd(), "26", "27"],
|
||||
),
|
||||
],
|
||||
parse_function=parse_cisco_ip_sla,
|
||||
service_name="Cisco IP SLA %s",
|
||||
discovery_function=inventory_cisco_ip_sla,
|
||||
check_function=check_cisco_ip_sla,
|
||||
check_ruleset_name="cisco_ip_sla",
|
||||
check_default_parameters={
|
||||
"state": "active",
|
||||
"connection_lost_occured": "no",
|
||||
"timeout_occured": "no",
|
||||
"completion_time_over_treshold_occured": "no",
|
||||
"latest_rtt_completion_time": (250, 500),
|
||||
"latest_rtt_state": "ok",
|
||||
"packets_lost_src->dest": (100, 1000),
|
||||
"packets_lost_dest->src": (100, 1000),
|
||||
},
|
||||
)
|
1
cisco-ip-sla/README.md
Normal file
1
cisco-ip-sla/README.md
Normal file
@ -0,0 +1 @@
|
||||
This is a modification of a Tribe29 GPL plugin to support tracking packet loss as well.
|
20
clever-pdu/cmk1.6/README
Normal file
20
clever-pdu/cmk1.6/README
Normal file
@ -0,0 +1,20 @@
|
||||
{'author': u'George Pochiscan',
|
||||
'description': u'',
|
||||
'download_url': '',
|
||||
'files': {'checkman': ['clever_pdu',
|
||||
'clever_pdu_humidity',
|
||||
'clever_pdu_temp'],
|
||||
'checks': ['clever_pdu_120',
|
||||
'clever_pdu_130',
|
||||
'clever_pdu_humidity_120',
|
||||
'clever_pdu_humidity_130',
|
||||
'clever_pdu_temp_120',
|
||||
'clever_pdu_temp_130'],
|
||||
'web': ['plugins/wato/clever_pdu.py']},
|
||||
'name': 'clever_pdu_1-6',
|
||||
'num_files': 10,
|
||||
'title': u'Clever PDU checks for Checkmk 1.6',
|
||||
'version': '1.1',
|
||||
'version.min_required': '1.6.0p20',
|
||||
'version.packaged': '1.6.0p29',
|
||||
'version.usable_until': '2.0.0p1'}
|
BIN
clever-pdu/cmk1.6/clever_pdu_1-6-1.1.mkp
Normal file
BIN
clever-pdu/cmk1.6/clever_pdu_1-6-1.1.mkp
Normal file
Binary file not shown.
13
clever-pdu/cmk1.6/local/share/check_mk/checkman/clever_pdu
Normal file
13
clever-pdu/cmk1.6/local/share/check_mk/checkman/clever_pdu
Normal file
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Power and Voltage
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Power, Voltage and energy on Clever PDU Units.
|
||||
|
||||
item:
|
||||
ID of the Line.
|
||||
|
||||
discovery:
|
||||
One service is created for each Line.
|
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Master Humidity
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Master Humidity on Clever PDU Units.
|
||||
|
||||
item:
|
||||
Master Humidity.
|
||||
|
||||
discovery:
|
||||
One service is created.
|
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Master Temperature
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Master Temperature on Clever PDU Units.
|
||||
|
||||
item:
|
||||
Master Temperature.
|
||||
|
||||
discovery:
|
||||
One service is created.
|
129
clever-pdu/cmk1.6/local/share/check_mk/checks/clever_pdu_120
Normal file
129
clever-pdu/cmk1.6/local/share/check_mk/checks/clever_pdu_120
Normal file
@ -0,0 +1,129 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_default_levels"] ={
|
||||
"voltage": (240, 250),
|
||||
"current": (32, 33),
|
||||
"energy": (35000, 36000),
|
||||
}
|
||||
|
||||
lines = {"Line 1", "Line 2", "Line 3"}
|
||||
_UNIT_MAP = {
|
||||
"voltage": "V" ,
|
||||
"current": "A" ,
|
||||
"energy": "W",
|
||||
}
|
||||
|
||||
def parse_clever_pdu_120(info):
|
||||
data=info[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Line 1" : {
|
||||
"voltage": float(data[0]),
|
||||
"current": float(data[3])/10,
|
||||
"energy": float(data[6]),
|
||||
},
|
||||
"Line 2" : {
|
||||
"voltage": float(data[1]),
|
||||
"current": float(data[4])/10,
|
||||
"energy": float(data[7]),
|
||||
},
|
||||
"Line 3" : {
|
||||
"voltage": float(data[2]),
|
||||
"current": float(data[5])/10,
|
||||
"energy": float(data[8]),
|
||||
},
|
||||
"Total Energy" : {
|
||||
"energy" : float((float(data[0])*float(data[3])/10)) + float((float(data[1])*float(data[4])/10)) + float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
|
||||
}
|
||||
return parsed
|
||||
|
||||
def inventory_clever_pdu_120(parsed):
|
||||
for line in parsed:
|
||||
yield line, {}
|
||||
|
||||
|
||||
def check_clever_pdu_120(item, params, parsed):
|
||||
if "Total" not in item:
|
||||
for param in params:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
|
||||
yield check_levels(
|
||||
parsed.get(item).get(param),
|
||||
param,
|
||||
(warn, crit),
|
||||
unit = _UNIT_MAP.get(param),
|
||||
infoname = param
|
||||
)
|
||||
else:
|
||||
for param in params:
|
||||
if "energy" in param:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
yield check_levels(
|
||||
parsed.get(item).get(param),
|
||||
param,
|
||||
(warn, crit),
|
||||
unit = _UNIT_MAP.get(param),
|
||||
infoname = param
|
||||
)
|
||||
|
||||
|
||||
|
||||
check_info['clever_pdu_120'] = {
|
||||
'parse_function' : parse_clever_pdu_120,
|
||||
'inventory_function' : inventory_clever_pdu_120,
|
||||
'check_function' : check_clever_pdu_120,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'group' : "clever_pdu",
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2',
|
||||
[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
],
|
||||
),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'default_levels_variable' : 'clever_pdu_default_levels',
|
||||
}
|
127
clever-pdu/cmk1.6/local/share/check_mk/checks/clever_pdu_130
Normal file
127
clever-pdu/cmk1.6/local/share/check_mk/checks/clever_pdu_130
Normal file
@ -0,0 +1,127 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_default_levels"] ={
|
||||
"voltage": (240, 250),
|
||||
"current": (32, 33),
|
||||
"energy": (35000, 36000),
|
||||
}
|
||||
|
||||
lines = {"Line 1", "Line 2", "Line 3"}
|
||||
_UNIT_MAP = {
|
||||
"voltage": "V" ,
|
||||
"current": "A" ,
|
||||
"energy": "W",
|
||||
}
|
||||
|
||||
|
||||
|
||||
def parse_clever_pdu(info):
|
||||
data=info[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Line 1" : {
|
||||
"voltage": float(data[0]),
|
||||
"current": float(float(data[3])/10),
|
||||
"energy" : float((float(data[0])*float(data[3])/10)),
|
||||
},
|
||||
"Line 2" : {
|
||||
"voltage": float(data[1]),
|
||||
"current": float(float(data[4])/10),
|
||||
"energy" : float((float(data[1])*float(data[4])/10)),
|
||||
},
|
||||
"Line 3" : {
|
||||
"voltage": float(data[2]),
|
||||
"current": float(float(data[5])/10),
|
||||
"energy" : float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
"Total Energy" : {
|
||||
"energy" : float((float(data[0])*float(data[3])/10)) + float((float(data[1])*float(data[4])/10)) + float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
|
||||
}
|
||||
return parsed
|
||||
|
||||
def inventory_clever_pdu(parsed):
|
||||
for line in parsed:
|
||||
yield line, {}
|
||||
|
||||
|
||||
def check_clever_pdu(item, params, parsed):
|
||||
if "Total" not in item:
|
||||
for param in params:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
|
||||
yield check_levels(
|
||||
parsed.get(item).get(param),
|
||||
param,
|
||||
(warn, crit),
|
||||
unit = _UNIT_MAP.get(param),
|
||||
infoname = param
|
||||
)
|
||||
else:
|
||||
for param in params:
|
||||
if "energy" in param:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
yield check_levels(
|
||||
parsed.get(item).get(param),
|
||||
param,
|
||||
(warn, crit),
|
||||
unit = _UNIT_MAP.get(param),
|
||||
infoname = param
|
||||
)
|
||||
|
||||
|
||||
check_info['clever_pdu'] = {
|
||||
'parse_function' : parse_clever_pdu,
|
||||
'inventory_function' : inventory_clever_pdu,
|
||||
'check_function' : check_clever_pdu,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'group' : "clever_pdu",
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2',
|
||||
[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
],
|
||||
),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and not oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'default_levels_variable' : 'clever_pdu_default_levels',
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_humidity_default_levels"] = {
|
||||
"levels": (60, 70),
|
||||
}
|
||||
|
||||
def inventory_clever_pdu_humidity_120(info):
|
||||
yield "Master humidity", {}
|
||||
|
||||
|
||||
def check_clever_pdu_humidity_120(item, params, info):
|
||||
return check_humidity(float(info[0][0]), params)
|
||||
|
||||
|
||||
check_info['clever_pdu_humidity_120'] = {
|
||||
'inventory_function' : inventory_clever_pdu_humidity_120,
|
||||
'check_function' : check_clever_pdu_humidity_120,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2.14', ['0']),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'group' : 'humidity',
|
||||
'default_levels_variable' : 'clever_pdu_humidity_default_levels',
|
||||
'includes' : ['humidity.include'],
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_humidity_default_levels"] = {
|
||||
"levels": (60, 70),
|
||||
}
|
||||
|
||||
def inventory_clever_pdu_humidity(info):
|
||||
yield "Master humidity", {}
|
||||
|
||||
|
||||
def check_clever_pdu_humidity(item, params, info):
|
||||
return check_humidity(float(info[0][0]), params)
|
||||
|
||||
|
||||
check_info['clever_pdu_humidity'] = {
|
||||
'inventory_function' : inventory_clever_pdu_humidity,
|
||||
'check_function' : check_clever_pdu_humidity,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2.11', ['0']),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and not oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'group' : 'humidity',
|
||||
'default_levels_variable' : 'clever_pdu_humidity_default_levels',
|
||||
'includes' : ['humidity.include'],
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_temp_default_levels"] = {
|
||||
"levels": (60, 70),
|
||||
}
|
||||
|
||||
def inventory_clever_pdu_temp_120(info):
|
||||
yield "Master Temperature", {}
|
||||
|
||||
|
||||
def check_clever_pdu_temp_120(item, params, info):
|
||||
return check_temperature(float(info[0][0]), params, "Master Temperature %s" %item)
|
||||
|
||||
|
||||
check_info['clever_pdu_temp_120'] = {
|
||||
'inventory_function' : inventory_clever_pdu_temp_120,
|
||||
'check_function' : check_clever_pdu_temp_120,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2.13', ['0']),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'group' : 'temperature',
|
||||
'default_levels_variable' : 'clever_pdu_temp_default_levels',
|
||||
'includes' : ['temperature.include'],
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
factory_settings["clever_pdu_temp_default_levels"] = {
|
||||
"levels": (60, 70),
|
||||
}
|
||||
|
||||
def inventory_clever_pdu_temp(info):
|
||||
yield "Master Temperature", {}
|
||||
|
||||
|
||||
def check_clever_pdu_temp(item, params, info):
|
||||
return check_temperature(float(info[0][0]), params, "Master Temperature %s" %item)
|
||||
|
||||
|
||||
check_info['clever_pdu_temp'] = {
|
||||
'inventory_function' : inventory_clever_pdu_temp,
|
||||
'check_function' : check_clever_pdu_temp,
|
||||
'service_description' : '%s',
|
||||
'has_perfdata' : True,
|
||||
'snmp_info' : ('.1.3.6.1.4.1.30966.10.3.2.10', ['0']),
|
||||
'snmp_scan_function' : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.30966") and not oid(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
'group' : 'temperature',
|
||||
'default_levels_variable' : 'clever_pdu_temp_default_levels',
|
||||
'includes' : ['temperature.include'],
|
||||
}
|
83
clever-pdu/cmk1.6/local/share/check_mk/web/plugins/wato/clever_pdu.py
Executable file
83
clever-pdu/cmk1.6/local/share/check_mk/web/plugins/wato/clever_pdu.py
Executable file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||
# +------------------------------------------------------------------+
|
||||
# | ____ _ _ __ __ _ __ |
|
||||
# | / ___| |__ ___ ___| | __ | \/ | |/ / |
|
||||
# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
|
||||
# | | |___| | | | __/ (__| < | | | | . \ |
|
||||
# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
|
||||
# | |
|
||||
# | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
|
||||
# +------------------------------------------------------------------+
|
||||
#
|
||||
# This file is part of Check_MK.
|
||||
# The official homepage is at http://mathias-kettner.de/check_mk.
|
||||
#
|
||||
# check_mk is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation in version 2. check_mk is distributed
|
||||
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
|
||||
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
|
||||
# tails. You should have received a copy of the GNU General Public
|
||||
# License along with GNU Make; see the file COPYING. If not, write
|
||||
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA.
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersEnvironment,
|
||||
)
|
||||
from cmk.gui.valuespec import Dictionary, Integer, TextAscii, Tuple
|
||||
|
||||
|
||||
def _parameter_valuespec_clever_pdu():
|
||||
return Dictionary(
|
||||
elements=[
|
||||
(
|
||||
"voltage",
|
||||
Tuple(
|
||||
title=_("Voltage on Line"),
|
||||
elements=[
|
||||
Integer(title=_("warning at"), unit=_("V")),
|
||||
Integer(title=_("critical at"), unit=_("V")),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"current",
|
||||
Tuple(
|
||||
title=_("Current on Power Channel"),
|
||||
elements=[
|
||||
Integer(title=_("warning if below"), unit=_("A")),
|
||||
Integer(title=_("critical if below"), unit=_("A")),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"energy",
|
||||
Tuple(
|
||||
title=_("Active Energy of Line"),
|
||||
elements=[
|
||||
Integer(title=_("warning at"), unit=_("W")),
|
||||
Integer(title=_("critical at"), unit=_("W")),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="clever_pdu",
|
||||
group=RulespecGroupCheckParametersEnvironment,
|
||||
item_spec=lambda: TextAscii(title=_("Line"),),
|
||||
match_type="dict",
|
||||
parameter_valuespec=_parameter_valuespec_clever_pdu,
|
||||
title=lambda: _("Levels for Clever AC PDU Devices"),
|
||||
)
|
||||
)
|
||||
|
13
clever-pdu/cmk2/checkman/clever_pdu
Normal file
13
clever-pdu/cmk2/checkman/clever_pdu
Normal file
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Power and Voltage
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Power, Voltage and energy on Clever PDU Units.
|
||||
|
||||
item:
|
||||
ID of the Line.
|
||||
|
||||
discovery:
|
||||
One service is created for each Line.
|
13
clever-pdu/cmk2/checkman/clever_pdu_humidity
Normal file
13
clever-pdu/cmk2/checkman/clever_pdu_humidity
Normal file
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Master Humidity
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Master Humidity on Clever PDU Units.
|
||||
|
||||
item:
|
||||
Master Humidity.
|
||||
|
||||
discovery:
|
||||
One service is created.
|
13
clever-pdu/cmk2/checkman/clever_pdu_temp
Normal file
13
clever-pdu/cmk2/checkman/clever_pdu_temp
Normal file
@ -0,0 +1,13 @@
|
||||
title: Clever PDU Units: Master Temperature
|
||||
agents: snmp
|
||||
catalog: hw/power/clever
|
||||
license: GPLv2
|
||||
distribution: check_mk
|
||||
description:
|
||||
Monitors Master Temperature on Clever PDU Units.
|
||||
|
||||
item:
|
||||
Master Temperature.
|
||||
|
||||
discovery:
|
||||
One service is created.
|
BIN
clever-pdu/cmk2/clever_pdu_2-1.0.1.mkp
Normal file
BIN
clever-pdu/cmk2/clever_pdu_2-1.0.1.mkp
Normal file
Binary file not shown.
20
clever-pdu/cmk2/info
Normal file
20
clever-pdu/cmk2/info
Normal file
@ -0,0 +1,20 @@
|
||||
{'author': 'George Pochiscan',
|
||||
'description': 'Ported Clever AC PDU from 2.1.0 checkmk version to 2.0.0 '
|
||||
'checkmk version.\n',
|
||||
'download_url': '',
|
||||
'files': {'agent_based': ['utils/humidity.py',
|
||||
'clever_pdu_120.py',
|
||||
'clever_pdu_130.py',
|
||||
'clever_pdu_humidity_120.py',
|
||||
'clever_pdu_humidity_130.py',
|
||||
'clever_pdu_temp_120.py',
|
||||
'clever_pdu_temp_130.py'],
|
||||
'checkman': ['clever_pdu', 'clever_pdu_humidity', 'clever_pdu_temp'],
|
||||
'web': ['plugins/wato/clever_pdu.py']},
|
||||
'name': 'clever_pdu_2',
|
||||
'num_files': 11,
|
||||
'title': 'Clever PDU checks for 2.0 checkmk version',
|
||||
'version': '1.0.1',
|
||||
'version.min_required': '2.0.0p20',
|
||||
'version.packaged': '2.0.0p29',
|
||||
'version.usable_until': '2.1.0p20'}
|
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Mapping, Tuple, NamedTuple
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, all_of, exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
|
||||
|
||||
def parse_clever_pdu_120(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Line 1" : {
|
||||
"voltage": float(data[0]),
|
||||
"current": float(data[3])/10,
|
||||
"energy": float(data[6]),
|
||||
},
|
||||
"Line 2" : {
|
||||
"voltage": float(data[1]),
|
||||
"current": float(data[4])/10,
|
||||
"energy": float(data[7]),
|
||||
},
|
||||
"Line 3" : {
|
||||
"voltage": float(data[2]),
|
||||
"current": float(data[5])/10,
|
||||
"energy": float(data[8]),
|
||||
},
|
||||
"Total Energy" : {
|
||||
"energy" : float((float(data[0])*float(data[3])/10)) + float((float(data[1])*float(data[4])/10)) + float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
_UNIT_MAP = {
|
||||
"voltage": "V" ,
|
||||
"current": "A" ,
|
||||
"energy": "W",
|
||||
}
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_120",
|
||||
parsed_section_name="clever_pdu_120",
|
||||
parse_function=parse_clever_pdu_120,
|
||||
detect = all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"1", # mVoltageA
|
||||
"2", # mVoltageB
|
||||
"3", # mVoltageC
|
||||
"4", # mCurrentA
|
||||
"5", # mCurrentB
|
||||
"6", # mCurrentC
|
||||
"7", # mEnergyA
|
||||
"8", # mEnergyB
|
||||
"9", # mEnergyC
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_120(section) -> DiscoveryResult:
|
||||
yield from (Service(item=line_num) for line_num in section)
|
||||
|
||||
|
||||
def check_clever_pdu_120(item, params, section) -> CheckResult:
|
||||
if "Total" not in item:
|
||||
for param in params:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
|
||||
yield from check_levels(
|
||||
section.get(item)[param],
|
||||
levels_upper = levels_upper,
|
||||
levels_lower = levels_lower,
|
||||
metric_name = param,
|
||||
render_func=lambda v: f"{v:.2f} {_UNIT_MAP[param]}",
|
||||
label = param,
|
||||
)
|
||||
else:
|
||||
for param in params:
|
||||
if "energy" in param:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
yield from check_levels(
|
||||
section.get(item)[param],
|
||||
levels_upper = levels_upper,
|
||||
levels_lower = levels_lower,
|
||||
metric_name = param,
|
||||
render_func=lambda v: f"{v:.2f} {_UNIT_MAP[param]}",
|
||||
label = param,
|
||||
)
|
||||
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_120",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_120,
|
||||
check_function=check_clever_pdu_120,
|
||||
check_ruleset_name="clever_pdu",
|
||||
check_default_parameters={
|
||||
"voltage": (220, 210),
|
||||
"current": (32, 33),
|
||||
"energy": (35000, 36000),
|
||||
},
|
||||
)
|
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Mapping, Tuple, NamedTuple
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, all_of, not_exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
|
||||
|
||||
def parse_clever_pdu_130(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Line 1" : {
|
||||
"voltage": float(data[0]),
|
||||
"current": float(data[3])/10,
|
||||
"energy" : float((float(data[0])*float(data[3])/10)),
|
||||
},
|
||||
"Line 2" : {
|
||||
"voltage": float(data[1]),
|
||||
"current": float(data[4])/10,
|
||||
"energy" : float((float(data[1])*float(data[4])/10)),
|
||||
},
|
||||
"Line 3" : {
|
||||
"voltage": float(data[2]),
|
||||
"current": float(data[5])/10,
|
||||
"energy" : float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
"Total Energy" : {
|
||||
"energy" : float((float(data[0])*float(data[3])/10)) + float((float(data[1])*float(data[4])/10)) + float((float(data[2])*float(data[5])/10)),
|
||||
},
|
||||
}
|
||||
return parsed
|
||||
|
||||
lines = {"Line 1", "Line 2", "Line 3"}
|
||||
|
||||
|
||||
_UNIT_MAP = {
|
||||
"voltage": "V" ,
|
||||
"current": "A" ,
|
||||
"energy": "W",
|
||||
}
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_130",
|
||||
parsed_section_name="clever_pdu_130",
|
||||
parse_function=parse_clever_pdu_130,
|
||||
detect = all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
not_exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"1", # mVoltageA
|
||||
"2", # mVoltageB
|
||||
"3", # mVoltageC
|
||||
"4", # mCurrentA
|
||||
"5", # mCurrentB
|
||||
"6", # mCurrentC
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_130(section) -> DiscoveryResult:
|
||||
yield from (Service(item=line_num) for line_num in section)
|
||||
|
||||
|
||||
def check_clever_pdu_130(item, params, section) -> CheckResult:
|
||||
if "Total" not in item:
|
||||
for param in params:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
|
||||
yield from check_levels(
|
||||
section.get(item)[param],
|
||||
levels_upper = levels_upper,
|
||||
levels_lower = levels_lower,
|
||||
metric_name = param,
|
||||
render_func=lambda v: f"{v:.2f} {_UNIT_MAP[param]}",
|
||||
label = param,
|
||||
)
|
||||
else:
|
||||
for param in params:
|
||||
if "energy" in param:
|
||||
levels_lower = levels_upper = None
|
||||
warn, crit = params.get(param)
|
||||
if warn > crit:
|
||||
levels_lower = warn, crit
|
||||
else:
|
||||
levels_upper = warn, crit
|
||||
yield from check_levels(
|
||||
section.get(item)[param],
|
||||
levels_upper = levels_upper,
|
||||
levels_lower = levels_lower,
|
||||
metric_name = param,
|
||||
render_func=lambda v: f"{v:.2f} {_UNIT_MAP[param]}",
|
||||
label = param,
|
||||
)
|
||||
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_130",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_130,
|
||||
check_function=check_clever_pdu_130,
|
||||
check_ruleset_name="clever_pdu",
|
||||
check_default_parameters={
|
||||
"voltage": (220, 210),
|
||||
"current": (32, 33),
|
||||
"energy": (35000, 36000),
|
||||
},
|
||||
)
|
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from typing import Mapping, Any
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, get_value_store, all_of, exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
from .utils.humidity import check_humidity
|
||||
|
||||
CheckParams = Mapping[str, Any]
|
||||
|
||||
def parse_clever_pdu_humidity_120(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Master Humidity" : int(data[0]),
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_humidity_120",
|
||||
parsed_section_name="clever_pdu_humidity_120",
|
||||
parse_function=parse_clever_pdu_humidity_120,
|
||||
detect=all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"14", # mHumidity
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_humidity_120(section) -> DiscoveryResult:
|
||||
if section.get("Master Humidity") == 0:
|
||||
return
|
||||
else:
|
||||
yield from (Service(item=item) for item in section)
|
||||
|
||||
|
||||
def check_clever_pdu_humidity_120(item, params: CheckParams, section) -> CheckResult:
|
||||
yield from check_humidity(
|
||||
section.get("Master Humidity"),
|
||||
params,
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_humidity_120",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_humidity_120,
|
||||
check_function=check_clever_pdu_humidity_120,
|
||||
check_ruleset_name="humidity",
|
||||
check_default_parameters={},
|
||||
|
||||
)
|
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from typing import Mapping, Any
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, get_value_store, all_of, not_exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
from .utils.humidity import check_humidity
|
||||
|
||||
CheckParams = Mapping[str, Any]
|
||||
|
||||
def parse_clever_pdu_humidity(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Master Humidity" : int(data[0]),
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_humidity",
|
||||
parsed_section_name="clever_pdu_humidity",
|
||||
parse_function=parse_clever_pdu_humidity,
|
||||
detect=all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
not_exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"11", # mHumidity
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_humidity(section) -> DiscoveryResult:
|
||||
if section.get("Master Humidity") == 0:
|
||||
return
|
||||
else:
|
||||
yield from (Service(item=item) for item in section)
|
||||
|
||||
|
||||
def check_clever_pdu_humidity(item, params: CheckParams, section) -> CheckResult:
|
||||
yield from check_humidity(
|
||||
section.get("Master Humidity"),
|
||||
params,
|
||||
)
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_humidity",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_humidity,
|
||||
check_function=check_clever_pdu_humidity,
|
||||
check_ruleset_name="humidity",
|
||||
check_default_parameters={},
|
||||
|
||||
)
|
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Mapping, Tuple, NamedTuple
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, get_value_store, all_of, exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
from .utils.temperature import check_temperature, TempParamDict
|
||||
|
||||
def parse_clever_pdu_temp_120(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Master Temperature" : int(data[0]),
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_temp_120",
|
||||
parsed_section_name="clever_pdu_temp_120",
|
||||
parse_function=parse_clever_pdu_temp_120,
|
||||
detect = all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"13", # mTemperature
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_temp_120(section) -> DiscoveryResult:
|
||||
if section.get("Master Temperature") == 0:
|
||||
return
|
||||
else:
|
||||
yield from (Service(item=item) for item in section)
|
||||
|
||||
|
||||
def check_clever_pdu_temp_120(item, params: TempParamDict, section) -> CheckResult:
|
||||
if (temperature := section.get(item)) is None:
|
||||
return
|
||||
yield from check_temperature(
|
||||
reading=temperature,
|
||||
params=params,
|
||||
unique_name=item,
|
||||
value_store=get_value_store(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_temp_120",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_temp_120,
|
||||
check_function=check_clever_pdu_temp_120,
|
||||
check_ruleset_name="temperature",
|
||||
check_default_parameters={},
|
||||
|
||||
)
|
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Mapping, Tuple, NamedTuple
|
||||
from .agent_based_api.v1 import check_levels, equals, register, Service, SNMPTree, get_value_store, all_of, not_exists
|
||||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable
|
||||
from .utils.temperature import check_temperature, TempParamDict
|
||||
|
||||
def parse_clever_pdu_temp(string_table: StringTable):
|
||||
data=string_table[0]
|
||||
parsed = {}
|
||||
parsed = {
|
||||
"Master Temperature" : int(data[0]),
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
|
||||
register.snmp_section(
|
||||
name="clever_pdu_temp",
|
||||
parsed_section_name="clever_pdu_temp",
|
||||
parse_function=parse_clever_pdu_temp,
|
||||
detect = all_of(
|
||||
equals(
|
||||
".1.3.6.1.2.1.1.2.0",
|
||||
".1.3.6.1.4.1.30966",
|
||||
),
|
||||
not_exists(".1.3.6.1.4.1.30966.10.3.2.70.0"),
|
||||
),
|
||||
fetch=SNMPTree(
|
||||
".1.3.6.1.4.1.30966.10.3.2",
|
||||
[
|
||||
"10", # mTemperature
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def discover_clever_pdu_temp(section) -> DiscoveryResult:
|
||||
if section.get("Master Temperature") == 0:
|
||||
return
|
||||
else:
|
||||
yield from (Service(item=item) for item in section)
|
||||
|
||||
|
||||
def check_clever_pdu_temp(item, params: TempParamDict, section) -> CheckResult:
|
||||
if (temperature := section.get(item)) is None:
|
||||
return
|
||||
yield from check_temperature(
|
||||
reading=temperature,
|
||||
params=params,
|
||||
unique_name=item,
|
||||
value_store=get_value_store(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="clever_pdu_temp",
|
||||
service_name="%s",
|
||||
discovery_function=discover_clever_pdu_temp,
|
||||
check_function=check_clever_pdu_temp,
|
||||
check_ruleset_name="temperature",
|
||||
check_default_parameters={},
|
||||
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
from typing import Any, List, Mapping, Optional, Tuple, Union
|
||||
|
||||
from ..agent_based_api.v1 import check_levels, render, type_defs
|
||||
|
||||
CheckParams = Union[
|
||||
None, Mapping[str, Any], Optional[List[float]], Tuple[float, float, float, float]
|
||||
]
|
||||
|
||||
|
||||
def check_humidity(humidity: float, params: CheckParams) -> type_defs.CheckResult:
|
||||
levels_upper, levels_lower = None, None
|
||||
if isinstance(params, dict):
|
||||
levels_upper = params.get("levels") or None
|
||||
levels_lower = params.get("levels_lower") or None
|
||||
elif isinstance(params, (list, tuple)):
|
||||
# old params = (crit_low , warn_low, warn, crit)
|
||||
levels_upper = params[2], params[3]
|
||||
levels_lower = params[1], params[0]
|
||||
|
||||
yield from check_levels(
|
||||
humidity,
|
||||
levels_upper=levels_upper,
|
||||
levels_lower=levels_lower,
|
||||
metric_name="humidity",
|
||||
render_func=render.percent,
|
||||
boundaries=(0, 100),
|
||||
)
|
65
clever-pdu/cmk2/plugins/wato/clever_pdu.py
Normal file
65
clever-pdu/cmk2/plugins/wato/clever_pdu.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
|
||||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
|
||||
# conditions defined in the file COPYING, which is part of this source code package.
|
||||
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
CheckParameterRulespecWithItem,
|
||||
rulespec_registry,
|
||||
RulespecGroupCheckParametersEnvironment,
|
||||
)
|
||||
from cmk.gui.valuespec import Dictionary, Integer, TextInput, Tuple
|
||||
|
||||
|
||||
def _parameter_valuespec_clever_pdu():
|
||||
return Dictionary(
|
||||
elements=[
|
||||
(
|
||||
"voltage",
|
||||
Tuple(
|
||||
title=_("Voltage on Line"),
|
||||
elements=[
|
||||
Integer(title=_("warning at"), unit=_("V")),
|
||||
Integer(title=_("critical at"), unit=_("V")),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"current",
|
||||
Tuple(
|
||||
title=_("Current on Power Channel"),
|
||||
elements=[
|
||||
Integer(title=_("warning if below"), unit=_("A")),
|
||||
Integer(title=_("critical if below"), unit=_("A")),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"energy",
|
||||
Tuple(
|
||||
title=_("Active Energy of Line"),
|
||||
elements=[
|
||||
Integer(title=_("warning at"), unit=_("W")),
|
||||
Integer(title=_("critical at"), unit=_("W")),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
CheckParameterRulespecWithItem(
|
||||
check_group_name="clever_pdu",
|
||||
group=RulespecGroupCheckParametersEnvironment,
|
||||
item_spec=lambda: TextInput(
|
||||
title=_("Line"), help=_("The Line Number. Example: 'Line 1'.")
|
||||
),
|
||||
match_type="dict",
|
||||
parameter_valuespec=_parameter_valuespec_clever_pdu,
|
||||
title=lambda: _("Levels for Clever AC PDU Devices"),
|
||||
)
|
||||
)
|
||||
|
68
cucm/local/lib/check_mk/base/plugins/agent_based/cucm_chk.py
Normal file
68
cucm/local/lib/check_mk/base/plugins/agent_based/cucm_chk.py
Normal file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Parses and checks non-phone devices from CUCM.
|
||||
|
||||
import json
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import register, Result, Service, State
|
||||
|
||||
|
||||
# Convert JSON entries into dictionaries indexed by name.
|
||||
def parse_cucm(string_table):
|
||||
lookup = {}
|
||||
|
||||
for row in string_table:
|
||||
device = json.loads(row[0])
|
||||
name = device["name"]
|
||||
lookup[name] = device
|
||||
|
||||
return lookup
|
||||
|
||||
register.agent_section(
|
||||
name="cucm_chk",
|
||||
parse_function=parse_cucm
|
||||
)
|
||||
|
||||
|
||||
# Produce a list of services based on the parsed devices.
|
||||
def discover_cucm(section):
|
||||
for name, details in sorted(section.items()):
|
||||
model_name = details["model_name"]
|
||||
ip = details.get("ip")
|
||||
gui_name = "%s %s (%s)" % (model_name, name, ip)
|
||||
yield Service(item=gui_name, parameters={"name": name})
|
||||
|
||||
|
||||
# Given a specific device, look it up in the parsed devices, and produce
|
||||
# results on that service based upon the devices' status.
|
||||
def check_cucm(item, params, section):
|
||||
name = params["name"]
|
||||
device = section.get(name)
|
||||
|
||||
if device is None:
|
||||
yield Result(state=State.WARN, summary="Not appearing in CUCM API")
|
||||
return
|
||||
|
||||
status = device.get("status")
|
||||
|
||||
if status is None:
|
||||
yield Result(state=State.WARN, summary="No status for this in CUCM API")
|
||||
elif status == "Registered":
|
||||
yield Result(state=State.OK, summary="Registered")
|
||||
elif ["Unregistered", "Rejected", "PartiallyRegistered", "Unknown"].count(status) == 1:
|
||||
summary = status
|
||||
msg = device.get("status_reason")
|
||||
if msg:
|
||||
summary += " " + msg
|
||||
yield Result(state=State.WARN, summary=summary)
|
||||
else:
|
||||
yield Result(state=State.WARN, summary="Unknown status: %s" % status)
|
||||
|
||||
|
||||
register.check_plugin(
|
||||
name="cucm_chk",
|
||||
service_name="CUCM %s",
|
||||
discovery_function=discover_cucm,
|
||||
check_function=check_cucm,
|
||||
check_default_parameters={},
|
||||
check_ruleset_name="cucm_chk",
|
||||
)
|
44
cucm/local/lib/check_mk/base/plugins/agent_based/cucm_inv.py
Normal file
44
cucm/local/lib/check_mk/base/plugins/agent_based/cucm_inv.py
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Parses and inventories phones.
|
||||
|
||||
# XXX for the inventory plugin, if mac/serial/model is None, do not update the inventory
|
||||
# XXX for checkmk, add a last_seen. If last_seen is older than six months, remove it.
|
||||
|
||||
import json
|
||||
from cmk.base.plugins.agent_based.agent_based_api.v1 import register, TableRow
|
||||
|
||||
|
||||
# Convert JSON entries into dictionaries indexed by name.
|
||||
def parse_cucm(string_table):
|
||||
lookup = {}
|
||||
|
||||
for row in string_table:
|
||||
phone = json.loads(row[0])
|
||||
name = phone["name"]
|
||||
lookup[name] = phone
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
# Produce a table of all phones parsed earlier.
|
||||
def inventory_cucm(section):
|
||||
path = ["phones"]
|
||||
for name, details in sorted(section.items()):
|
||||
details.pop("name")
|
||||
yield TableRow(
|
||||
path=path,
|
||||
key_columns={"name": name},
|
||||
inventory_columns=details
|
||||
)
|
||||
|
||||
|
||||
register.agent_section(
|
||||
name="cucm_inv",
|
||||
parse_function=parse_cucm
|
||||
)
|
||||
|
||||
register.inventory_plugin(
|
||||
name="cucm_inv",
|
||||
inventory_function=inventory_cucm,
|
||||
)
|
340
cucm/local/share/check_mk/agents/special/agent_cucm_chk
Executable file
340
cucm/local/share/check_mk/agents/special/agent_cucm_chk
Executable file
@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Contact CUCM to fetch the status of non-phone devices, and return the results
|
||||
# of each device as a JSON line.
|
||||
#
|
||||
# This file imports code from agent_cucm_inv, so much of the important logic
|
||||
# is found there.
|
||||
|
||||
from importlib.util import spec_from_loader, module_from_spec
|
||||
from importlib.machinery import SourceFileLoader
|
||||
import os, sys, json, urllib.request
|
||||
|
||||
# Since Python doesn't import files without .py extensions, we need to do a
|
||||
# little tapdance to import agent_cucm_inv.
|
||||
file = 'agent_cucm_inv'
|
||||
path = os.path.dirname(__file__) + '/' + file
|
||||
spec = spec_from_loader(file, SourceFileLoader(file, path))
|
||||
inv = module_from_spec(spec)
|
||||
spec.loader.exec_module(inv)
|
||||
|
||||
|
||||
|
||||
# Call the CUCM RisPort70 API synchronously, using a SOAP query to fetch
|
||||
# information about devices matching the requested device type. It returns
|
||||
# XML, which we parse.
|
||||
#
|
||||
# Be aware that the API will return information about a maximum of 2000 devices,
|
||||
# and provides no means of pagination. Having more than 2000 non-phone devices
|
||||
# would be quite exceptional, so we don't handle that here, but if you ever
|
||||
# need to support more than that look into how agent_cucm_inv uses AXL to
|
||||
# do pagination.
|
||||
#
|
||||
# See query_cucm_risport() in agent_cucm_inv for more info.
|
||||
def query_cucm(addr, port, user, password, insecure, device):
|
||||
url = 'https://%s:%s/realtimeservice2/services/RISService70/' % (addr, port)
|
||||
headers = [('Content-Type', 'text/plain')]
|
||||
|
||||
try:
|
||||
return inv.get_url(url, user, password, insecure, headers, f"""
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:soap="http://schemas.cisco.com/ast/soap">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<soap:selectCmDevice>
|
||||
<soap:StateInfo></soap:StateInfo>
|
||||
<soap:CmSelectionCriteria>
|
||||
<soap:MaxReturnedDevices>2000</soap:MaxReturnedDevices>
|
||||
<soap:DeviceClass>{device}</soap:DeviceClass>
|
||||
<soap:Model>255</soap:Model>
|
||||
<soap:Status></soap:Status>
|
||||
<soap:NodeName></soap:NodeName>
|
||||
<soap:SelectBy>Name</soap:SelectBy>
|
||||
<soap:SelectItems></soap:SelectItems>
|
||||
<soap:Protocol>Any</soap:Protocol>
|
||||
<soap:DownloadStatus>Any</soap:DownloadStatus>
|
||||
</soap:CmSelectionCriteria>
|
||||
</soap:selectCmDevice>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
""")
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.stderr.write("CUCM error: %s\n" % e)
|
||||
|
||||
|
||||
# Statuses listed here: https://developer.cisco.com/docs/sxml/#!risport70-api-reference/ReasonCode
|
||||
status_reason_lookup = {
|
||||
"0": None,
|
||||
"1": "Unknown",
|
||||
"6": "ConnectivityError",
|
||||
"8": "DeviceInitiatedReset",
|
||||
"9": "CallManagerReset",
|
||||
"10": "DeviceUnregistered",
|
||||
"11": "MalformedRegisterMsg",
|
||||
"12": "SCCPDeviceThrottling",
|
||||
"13": "KeepAliveTimeout",
|
||||
"14": "ConfigurationMismatch",
|
||||
"15": "CallManagerRestart",
|
||||
"16": "DuplicateRegistration",
|
||||
"17": "CallManagerApplyConfig",
|
||||
"18": "DeviceNoResponse",
|
||||
"19": "EMLoginLogout",
|
||||
"20": "EMCCLoginLogout",
|
||||
"25": "RegistrationSequenceError",
|
||||
"26": "InvalidCapabilities",
|
||||
"28": "FallbackInitiated",
|
||||
"29": "DeviceSwitch",
|
||||
"30": "DeviceWipe",
|
||||
"31": "DeviceForcedReset",
|
||||
"33": "LowBattery",
|
||||
"34": "ManualPowerOff",
|
||||
}
|
||||
|
||||
# Model names listed here: https://developer.cisco.com/docs/sxml/#!risport70-api-reference/risport70-api-reference
|
||||
model_name_lookup = {
|
||||
"1": "Cisco 30 SP+",
|
||||
"2": "Cisco 12 SP+",
|
||||
"3": "Cisco 12 SP",
|
||||
"4": "Cisco 12 S",
|
||||
"5": "Cisco 30 VIP",
|
||||
"6": "Cisco 7910",
|
||||
"7": "Cisco 7960",
|
||||
"8": "Cisco 7940",
|
||||
"9": "Cisco 7935",
|
||||
"10": "Cisco VGC Phone",
|
||||
"11": "Cisco VGC Virtual Phone",
|
||||
"12": "Cisco ATA 186",
|
||||
"15": "EMCC Base Phone",
|
||||
"20": "SCCP Phone",
|
||||
"30": "Analog Access",
|
||||
"40": "Digital Access",
|
||||
"42": "Digital Access+",
|
||||
"43": "Digital Access WS-X6608",
|
||||
"47": "Analog Access WS-X6624",
|
||||
"48": "VGC Gateway",
|
||||
"50": "Conference Bridge",
|
||||
"51": "Conference Bridge WS-X6608",
|
||||
"52": "Cisco IOS Conference Bridge (HDV2)",
|
||||
"53": "Cisco Conference Bridge (WS-SVC-CMM)",
|
||||
"61": "H.323 Phone",
|
||||
"62": "H.323 Gateway",
|
||||
"70": "Music On Hold",
|
||||
"71": "Device Pilot",
|
||||
"72": "CTI Port",
|
||||
"73": "CTI Route Point",
|
||||
"80": "Voice Mail Port",
|
||||
"83": "Cisco IOS Software Media Termination Point (HDV2)",
|
||||
"84": "Cisco Media Server (WS-SVC-CMM-MS)",
|
||||
"85": "Cisco Video Conference Bridge (IPVC-35xx)",
|
||||
"86": "Cisco IOS Heterogeneous Video Conference Bridge",
|
||||
"87": "Cisco IOS Guaranteed Audio Video Conference Bridge",
|
||||
"88": "Cisco IOS Homogeneous Video Conference Bridge",
|
||||
"90": "Route List",
|
||||
"100": "Load Simulator",
|
||||
"110": "Media Termination Point",
|
||||
"111": "Media Termination Point Hardware",
|
||||
"112": "Cisco IOS Media Termination Point (HDV2)",
|
||||
"113": "Cisco Media Termination Point (WS-SVC-CMM)",
|
||||
"115": "Cisco 7941",
|
||||
"119": "Cisco 7971",
|
||||
"120": "MGCP Station",
|
||||
"121": "MGCP Trunk",
|
||||
"122": "GateKeeper",
|
||||
"124": "7914 14-Button Line Expansion Module",
|
||||
"125": "Trunk",
|
||||
"126": "Tone Announcement Player",
|
||||
"131": "SIP Trunk",
|
||||
"132": "SIP Gateway",
|
||||
"133": "WSM Trunk",
|
||||
"134": "Remote Destination Profile",
|
||||
"227": "7915 12-Button Line Expansion Module",
|
||||
"228": "7915 24-Button Line Expansion Module",
|
||||
"229": "7916 12-Button Line Expansion Module",
|
||||
"230": "7916 24-Button Line Expansion Module",
|
||||
"232": "CKEM 36-Button Line Expansion Module",
|
||||
"253": "SPA8800",
|
||||
"254": "Unknown MGCP Gateway",
|
||||
"255": "Unknown",
|
||||
"302": "Cisco 7985",
|
||||
"307": "Cisco 7911",
|
||||
"308": "Cisco 7961G-GE",
|
||||
"309": "Cisco 7941G-GE",
|
||||
"335": "Motorola CN622",
|
||||
"336": "Third-party SIP Device (Basic)",
|
||||
"348": "Cisco 7931",
|
||||
"358": "Cisco Unified Personal Communicator",
|
||||
"365": "Cisco 7921",
|
||||
"369": "Cisco 7906",
|
||||
"374": "Third-party SIP Device (Advanced)",
|
||||
"375": "Cisco TelePresence",
|
||||
"376": "Nokia S60",
|
||||
"404": "Cisco 7962",
|
||||
"412": "Cisco 3951",
|
||||
"431": "Cisco 7937",
|
||||
"434": "Cisco 7942",
|
||||
"435": "Cisco 7945",
|
||||
"436": "Cisco 7965",
|
||||
"437": "Cisco 7975",
|
||||
"446": "Cisco 3911",
|
||||
"468": "Cisco Unified Mobile Communicator",
|
||||
"478": "Cisco TelePresence 1000",
|
||||
"479": "Cisco TelePresence 3000",
|
||||
"480": "Cisco TelePresence 3200",
|
||||
"481": "Cisco TelePresence 500-37",
|
||||
"484": "Cisco 7925",
|
||||
"486": "Syn-Apps Virtual Phone",
|
||||
"493": "Cisco 9971",
|
||||
"495": "Cisco 6921",
|
||||
"496": "Cisco 6941",
|
||||
"497": "Cisco 6961",
|
||||
"503": "Cisco Unified Client Services Framework",
|
||||
"505": "Cisco TelePresence 1300-65",
|
||||
"520": "Cisco TelePresence 1100",
|
||||
"521": "Transnova S3",
|
||||
"522": "BlackBerry MVS VoWifi",
|
||||
"527": "IPTrade TAD",
|
||||
"537": "Cisco 9951",
|
||||
"540": "Cisco 8961",
|
||||
"547": "Cisco 6901",
|
||||
"548": "Cisco 6911",
|
||||
"550": "Cisco ATA 187",
|
||||
"557": "Cisco TelePresence 200",
|
||||
"558": "Cisco TelePresence 400",
|
||||
"562": "Cisco Dual Mode for iPhone",
|
||||
"564": "Cisco 6945",
|
||||
"575": "Cisco Dual Mode for Android",
|
||||
"577": "Cisco 7926",
|
||||
"580": "Cisco E20",
|
||||
"582": "Generic Single Screen Room System",
|
||||
"583": "Generic Multiple Screen Room System",
|
||||
"584": "Cisco TelePresence EX90",
|
||||
"585": "Cisco 8945",
|
||||
"586": "Cisco 8941",
|
||||
"588": "Generic Desktop Video Endpoint",
|
||||
"590": "Cisco TelePresence 500-32",
|
||||
"591": "Cisco TelePresence 1300-47",
|
||||
"592": "Cisco 3905",
|
||||
"593": "Cisco Cius",
|
||||
"594": "VKEM 36-Button Line Expansion Module",
|
||||
"596": "Cisco TelePresence TX1310-65",
|
||||
"597": "Cisco TelePresence MCU",
|
||||
"598": "Ascom IP-DECT Device",
|
||||
"599": "Cisco TelePresence Exchange System",
|
||||
"604": "Cisco TelePresence EX60",
|
||||
"606": "Cisco TelePresence Codec C90",
|
||||
"607": "Cisco TelePresence Codec C60",
|
||||
"608": "Cisco TelePresence Codec C40",
|
||||
"609": "Cisco TelePresence Quick Set C20",
|
||||
"610": "Cisco TelePresence Profile 42 (C20)",
|
||||
"611": "Cisco TelePresence Profile 42 (C60)",
|
||||
"612": "Cisco TelePresence Profile 52 (C40)",
|
||||
"613": "Cisco TelePresence Profile 52 (C60)",
|
||||
"614": "Cisco TelePresence Profile 52 Dual (C60)",
|
||||
"615": "Cisco TelePresence Profile 65 (C60)",
|
||||
"616": "Cisco TelePresence Profile 65 Dual (C90)",
|
||||
"617": "Cisco TelePresence MX200",
|
||||
"619": "Cisco TelePresence TX9000",
|
||||
"621": "Cisco 7821",
|
||||
"620": "Cisco TelePresence TX9200",
|
||||
"622": "Cisco 7841",
|
||||
"623": "Cisco 7861",
|
||||
"626": "Cisco TelePresence SX20",
|
||||
"627": "Cisco TelePresence MX300",
|
||||
"628": "IMS-integrated Mobile (Basic)",
|
||||
"631": "Third-party AS-SIP Endpoint",
|
||||
"632": "Cisco Cius SP",
|
||||
"633": "Cisco TelePresence Profile 42 (C40)",
|
||||
"634": "Cisco VXC 6215",
|
||||
"635": "CTI Remote Device",
|
||||
"640": "Usage Profile",
|
||||
"642": "Carrier-integrated Mobile",
|
||||
"645": "Universal Device Template",
|
||||
"647": "Cisco DX650",
|
||||
"648": "Cisco Unified Communications for RTX",
|
||||
"652": "Cisco Jabber for Tablet",
|
||||
"659": "Cisco 8831",
|
||||
"682": "Cisco TelePresence SX10",
|
||||
"683": "Cisco 8841",
|
||||
"684": "Cisco 8851",
|
||||
"685": "Cisco 8861",
|
||||
"688": "Cisco TelePresence SX80",
|
||||
"689": "Cisco TelePresence MX200 G2",
|
||||
"690": "Cisco TelePresence MX300 G2",
|
||||
"20000": "Cisco 7905",
|
||||
"30002": "Cisco 7920",
|
||||
"30006": "Cisco 7970",
|
||||
"30007": "Cisco 7912",
|
||||
"30008": "Cisco 7902",
|
||||
"30016": "Cisco IP Communicator",
|
||||
"30018": "Cisco 7961",
|
||||
"30019": "Cisco 7936",
|
||||
"30027": "Analog Phone",
|
||||
"30028": "ISDN BRI Phone",
|
||||
"30032": "SCCP gateway virtual phone",
|
||||
"30035": "IP-STE",
|
||||
"36041": "Cisco TelePresence Conductor",
|
||||
"36042": "Cisco DX80",
|
||||
"36043": "Cisco DX70",
|
||||
"36049": "BEKEM 36-Button Line Expansion Module",
|
||||
"36207": "Cisco TelePresence MX700",
|
||||
"36208": "Cisco TelePresence MX800",
|
||||
}
|
||||
|
||||
|
||||
# Given CUCM XML, use XPath to extract relevant details for each device
|
||||
# searching based on device type. Return a list of devices' information.
|
||||
def get_device_details(xml, device):
|
||||
namespace = {"ns1": "http://schemas.cisco.com/ast/soap"}
|
||||
items = xml.findall(f".//ns1:DeviceClass[.='{device}']/..", namespace)
|
||||
|
||||
names_seen = {}
|
||||
trunk_details = []
|
||||
for item in items:
|
||||
ip = item.find(".//ns1:IP", namespace).text
|
||||
name = item.find("ns1:Name", namespace).text
|
||||
model = item.find("ns1:Model", namespace).text
|
||||
status = item.find("ns1:Status", namespace).text
|
||||
reason = item.find("ns1:StatusReason", namespace).text
|
||||
|
||||
if not names_seen.get(name):
|
||||
trunk_details.append({
|
||||
"ip": ip,
|
||||
"name": name,
|
||||
"status": status,
|
||||
"status_reason": status_reason_lookup.get(reason),
|
||||
"type": device,
|
||||
"model_name": model_name_lookup.get(model) or "Unknown"
|
||||
})
|
||||
names_seen[name] = True
|
||||
|
||||
return trunk_details
|
||||
|
||||
|
||||
# Contact CUCM and query it for device information for the following device
|
||||
# types: SIP trunks, hunt lists, H323 and media resources (e.g. IVR). Return
|
||||
# a list for devices' information.
|
||||
def get_devices(addr, port, user, password, insecure):
|
||||
devices = []
|
||||
|
||||
for device in ["SIPTrunk", "MediaResources", "H323", "HuntList"]:
|
||||
cucm_xml = query_cucm(addr, port, user, password, insecure, device)
|
||||
details = get_device_details(cucm_xml, device)
|
||||
devices.extend(details)
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
# Parse args, contact CUCM, check status of non-hone devices, and then print
|
||||
# results
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = inv.parse_arguments(argv)
|
||||
devices = get_devices(args.hostname, args.port, args.user, args.password,
|
||||
args.insecure)
|
||||
inv.print_out(devices, "cucm_chk")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
434
cucm/local/share/check_mk/agents/special/agent_cucm_inv
Executable file
434
cucm/local/share/check_mk/agents/special/agent_cucm_inv
Executable file
@ -0,0 +1,434 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Given a CUCM host, query the CUCM to get a complete list of phones, then
|
||||
# query all those phones concurrently for additional details, then print
|
||||
# the results out in a format CheckMK understands.
|
||||
#
|
||||
# Run the command on the console for a complete list of options.
|
||||
#
|
||||
# This script is designed to work with up to 90K phones, and ideally below 10K.
|
||||
# If more than 10K phones are queried, this script should be modified to
|
||||
# perform connection reuse to improve performance. Beyond 90K support for
|
||||
# paginating the AXL API must be added.
|
||||
|
||||
|
||||
phone_query_timeout = 10 # max time to query a single phone
|
||||
phone_queries_timeout = 45 # max time to query all phones
|
||||
cucm_page_size = 1000 # CUCM will not return pages larger than 2000.
|
||||
# Larger page sizes cause notably longer queries,
|
||||
# so a default of 1000 devices per query is a
|
||||
# safer number.
|
||||
|
||||
|
||||
import urllib.request, base64, sys, argparse, asyncio, re, json, ssl, html
|
||||
from xml.etree import ElementTree
|
||||
from textwrap import wrap
|
||||
|
||||
|
||||
# Create a TLS context for client connections.
|
||||
def create_ssl_ctx():
|
||||
ctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
return ctx
|
||||
|
||||
|
||||
# Typical GET or POST HTTP with Basic auth (using user and password
|
||||
# credientials). Returns data structure parsed from XML.
|
||||
def get_url(url, user, password, insecure, headers, data):
|
||||
request = urllib.request.Request(url, data=bytes(data, 'ascii'))
|
||||
|
||||
for header, value in headers:
|
||||
request.add_header(header, value)
|
||||
|
||||
if user and password:
|
||||
auth_str = base64.b64encode(bytes('%s:%s' % (user, password), 'ascii'))
|
||||
request.add_header('Authorization', 'Basic %s' % auth_str.decode('utf-8'))
|
||||
|
||||
ctx = None
|
||||
if insecure:
|
||||
ctx = create_ssl_ctx()
|
||||
|
||||
with urllib.request.urlopen(request, context=ctx) as conn:
|
||||
xml_data = conn.read()
|
||||
|
||||
return ElementTree.fromstring(xml_data)
|
||||
|
||||
|
||||
# Call the CUCM AXL API synchronously, using a SOAP query to fetch the names of
|
||||
# all phones. It returns XML, which we parse. We call the AXL API because the
|
||||
# RisPort70 API (see query_cucm_risport() below) does not support pagination,
|
||||
# so we need to get a full list of phone names from AXL first, then do multiple
|
||||
# queries on RisPort70 using subsets of the phone name list found from AXL.
|
||||
#
|
||||
# The AXL API has a return limit of 8MB, which is around 90K phones, so we
|
||||
# don't bother paginating the AXL API itself; if more than that is needed, add
|
||||
# pagination here.
|
||||
#
|
||||
# References:
|
||||
# https://developer.cisco.com/docs/axl/
|
||||
# https://github.com/reillychase/How-to-return-Cisco-RIS-with-more-than-1000-results/blob/master/main.py
|
||||
def query_cucm_axl(addr, port, user, password, insecure):
|
||||
url = 'https://%s:%s/axl/' % (addr, port)
|
||||
headers = [
|
||||
('Content-Type', 'text/xml'),
|
||||
('Accept', 'text/xml'),
|
||||
('SOAPAction', 'CUCM:DB ver=12.5'),
|
||||
]
|
||||
|
||||
try:
|
||||
return get_url(url, user, password, insecure, headers, """
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:ns="http://www.cisco.com/AXL/API/12.5">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<ns:listPhone>
|
||||
<searchCriteria>
|
||||
<name>%</name>
|
||||
</searchCriteria>
|
||||
<returnedTags>
|
||||
<name/>
|
||||
</returnedTags>
|
||||
</ns:listPhone>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
""")
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.stderr.write("AXL error: %s\n" % e)
|
||||
|
||||
|
||||
# Call the CUCM RisPort70 API synchronously, using a SOAP query to fetch
|
||||
# information about the phones with ids listed in the phone_ids arg. It returns
|
||||
# XML, which we parse.
|
||||
#
|
||||
# Be aware that the API will return information about a maximum of 2000 devices,
|
||||
# and provides no means of pagination. In order to do pagination, we first need
|
||||
# to query the AXL API for a list of phone names, then all this function
|
||||
# repeatedly with a different subset of 2000 phones from that complete list.
|
||||
#
|
||||
# Although this function will allow pages for 2000 devices, it's recommended to
|
||||
# use less for each call to avoid timeouts. The default maximum size of
|
||||
# phone_ids is 1000, although this can be varied by changing the cucm_page_size
|
||||
# at the top of this file.
|
||||
#
|
||||
# References:
|
||||
# https://developer.cisco.com/docs/sxml/#!risport70-api-reference
|
||||
# https://paultursan.com/2018/12/getting-cucm-real-time-data-via-risport70-with-python-and-zeep-cisco-serviceability-api/
|
||||
def query_cucm_risport(addr, port, user, password, insecure, phone_ids):
|
||||
assert len(phone_ids) <= 2000
|
||||
|
||||
url = 'https://%s:%s/realtimeservice2/services/RISService70/' % (addr, port)
|
||||
headers = [('Content-Type', 'text/plain')]
|
||||
|
||||
id_query = ''.join([f'<soap:item><soap:Item>{id}</soap:Item></soap:item>' for id in phone_ids])
|
||||
|
||||
try:
|
||||
return get_url(url, user, password, insecure, headers, f"""
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:soap="http://schemas.cisco.com/ast/soap">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<soap:selectCmDevice>
|
||||
<soap:StateInfo></soap:StateInfo>
|
||||
<soap:CmSelectionCriteria>
|
||||
<soap:MaxReturnedDevices>2000</soap:MaxReturnedDevices>
|
||||
<soap:DeviceClass>Any</soap:DeviceClass>
|
||||
<soap:Model>255</soap:Model>
|
||||
<soap:Status>Registered</soap:Status>
|
||||
<soap:NodeName></soap:NodeName>
|
||||
<soap:SelectBy>Name</soap:SelectBy>
|
||||
<soap:SelectItems>
|
||||
{id_query}
|
||||
</soap:SelectItems>
|
||||
<soap:Protocol>Any</soap:Protocol>
|
||||
<soap:DownloadStatus>Any</soap:DownloadStatus>
|
||||
</soap:CmSelectionCriteria>
|
||||
</soap:selectCmDevice>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
""")
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.stderr.write("CUCM error: %s\n" % e)
|
||||
|
||||
|
||||
# Given AXL XML, use XPath to extract names for all phones.
|
||||
def get_phone_ids(xml):
|
||||
# should this be ns2?
|
||||
namespace = {'ns': 'http://www.cisco.com/AXL/API/12.5'}
|
||||
items = xml.findall(".//phone/name", namespace)
|
||||
|
||||
names = []
|
||||
for item in items:
|
||||
names.append(item.text)
|
||||
|
||||
return names
|
||||
|
||||
|
||||
# Given CUCM XML, use XPath to extract a bunch of details for each phone.
|
||||
def get_phone_details(xml):
|
||||
namespace = {'ns1': 'http://schemas.cisco.com/ast/soap'}
|
||||
items = xml.findall(".//ns1:DeviceClass[.='Phone']/..", namespace)
|
||||
|
||||
names_seen = {}
|
||||
phone_details = []
|
||||
for item in items:
|
||||
ip = item.find('.//ns1:IP', namespace).text
|
||||
name = item.find('ns1:Name', namespace).text
|
||||
dir_num = item.find('ns1:DirNumber', namespace).text
|
||||
description = item.find('ns1:Description', namespace).text
|
||||
user = item.find('ns1:LoginUserId', namespace).text
|
||||
|
||||
# These come with a -Registered on the end of the numbers.
|
||||
# Since all numbers we get from CUCM are registered, there's no
|
||||
# need for the -Registered, and we cut it off here.
|
||||
if dir_num:
|
||||
dir_num = dir_num.split('-')[0]
|
||||
|
||||
if not names_seen.get(name):
|
||||
phone_details.append((name, ip, dir_num, user, description))
|
||||
names_seen[name] = True
|
||||
|
||||
return phone_details
|
||||
|
||||
|
||||
# If a phone (possibly) returns XML, attempt to extract the MAC, serial and
|
||||
# model. We're using regex here, instead of full-blown XML parsing, to minimize
|
||||
# the time and GC garbage generated.
|
||||
def get_phone_details_from_xml(data):
|
||||
# if the HTTP server didn't return a 200...
|
||||
if data.find('200 OK') == -1:
|
||||
return None, None, None
|
||||
|
||||
# attempt to extract info from XML
|
||||
mac = re.search('<MACAddress>(.+)</MACAddress>', data)
|
||||
serial = re.search('<serialNumber>(.+)</serialNumber>', data)
|
||||
model = re.search('<modelNumber>(.+)</modelNumber>', data)
|
||||
|
||||
mac_str = mac and normalize_mac(html.unescape(mac[1]))
|
||||
ser_str = serial and html.unescape(serial[1])
|
||||
mod_str = model and html.unescape(model[1])
|
||||
|
||||
return mac_str, ser_str, mod_str
|
||||
|
||||
|
||||
# If a phone (possibly) returns HTML, attempt to extract the MAC, serial and
|
||||
# model. We use regex here for the same reason we use it in
|
||||
# get_phone_details_from_xml().
|
||||
def get_phone_details_from_html(data):
|
||||
if data.find('200 OK') == -1:
|
||||
return None, None, None
|
||||
|
||||
mac = None
|
||||
serial = None
|
||||
model = None
|
||||
|
||||
# attempt to extract info from HTML
|
||||
matches = re.findall('<b>\s*(.*?)\s*</b>', data, re.M | re.I)
|
||||
for i, txt in enumerate(matches):
|
||||
txt = txt.lower()
|
||||
if not mac and txt == 'mac address':
|
||||
mac = normalize_mac(html.unescape(matches[i + 1]))
|
||||
elif not serial and txt == 'serial number':
|
||||
serial = html.unescape(matches[i + 1])
|
||||
elif not model and txt == 'model number':
|
||||
model = html.unescape(matches[i + 1])
|
||||
elif mac and serial and model:
|
||||
break
|
||||
|
||||
return mac, serial, model
|
||||
|
||||
|
||||
# Different phones return MACs in different formats. We convert them to a single
|
||||
# canonical format here.
|
||||
def normalize_mac(mac):
|
||||
if mac.find(":") != -1:
|
||||
return mac.lower()
|
||||
else:
|
||||
return ":".join(wrap(mac, 2)).lower()
|
||||
|
||||
|
||||
# Create a new HTTP/HTTPS connection, send a request, and extract any results.
|
||||
async def get_async_url(ip, url, insecure=False):
|
||||
ctx = None
|
||||
port = 80
|
||||
if not insecure:
|
||||
ctx = create_ssl_ctx()
|
||||
port = 443
|
||||
|
||||
# XXX switch from 127.0.0.1:8081 to ip:port
|
||||
# future = asyncio.open_connection('127.0.0.1', 8081, ssl=ctx)
|
||||
future = asyncio.open_connection(ip, port, ssl=ctx)
|
||||
reader, writer = await asyncio.wait_for(future, timeout=phone_query_timeout)
|
||||
query = f'GET {url} HTTP/1.1\r\nHost: {ip}\r\nConnection: close\r\n\r\n'
|
||||
writer.write(query.encode())
|
||||
await writer.drain()
|
||||
|
||||
data = ''
|
||||
while not reader.at_eof():
|
||||
raw = await reader.read(-1)
|
||||
data += raw.decode()
|
||||
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
return data
|
||||
|
||||
# Asynchronously contact the HTTP server in a phone. There are several
|
||||
# different URLs that might return information, depending on the model of phone.
|
||||
# To fetch the MAC and serial details we want requires us to potentially call
|
||||
# all endpoints until we get some results. Be aware that some phone HTTP servers
|
||||
# return 200 (and empty results) if we call the wrong URL for that model.
|
||||
#
|
||||
# We attempt to contact the phone using HTTPS first, falling back to HTTP if
|
||||
# attempts with HTTPS failed.
|
||||
#
|
||||
# Originally we tried to take advantage of HTTP connection reuse, but this was
|
||||
# causing some problems, and it wasn't worth the effort to handle the edge
|
||||
# cases. Now we always make a new connection per request, even when it's
|
||||
# multiple URLs on the same IP. If additional performance is ever needed, HTTP
|
||||
# connection reuse is worth adding; depending on the latency it can easily
|
||||
# 2x+ request rate.
|
||||
async def query_phone_info_now(details):
|
||||
name, ip, dir_num, user, description = details
|
||||
mac = None
|
||||
serial = None
|
||||
model = None
|
||||
|
||||
try:
|
||||
data = await get_async_url(ip, '/DeviceInformationX')
|
||||
mac, serial, model = get_phone_details_from_xml(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/Device_Information.html')
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/CGI/Java/Serviceability?adapter=device.statistics.device')
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/')
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
except (ConnectionRefusedError, asyncio.TimeoutError, ssl.SSLError):
|
||||
try:
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/DeviceInformationX', True)
|
||||
mac, serial, model = get_phone_details_from_xml(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/Device_Information.html', True)
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/CGI/Java/Serviceability?adapter=device.statistics.device', True)
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
|
||||
if not mac:
|
||||
data = await get_async_url(ip, '/', True)
|
||||
mac, serial, model = get_phone_details_from_html(data)
|
||||
except (ConnectionRefusedError, asyncio.TimeoutError):
|
||||
pass
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"ip": ip,
|
||||
"mac": mac,
|
||||
"serial": serial,
|
||||
"dir_num": dir_num,
|
||||
"model": model,
|
||||
"user": user,
|
||||
"description": description
|
||||
}
|
||||
|
||||
|
||||
# This functions job is solely to keep a limit on the concurrent number of
|
||||
# connections made to the phones. Without this limit we'd quickly run out of
|
||||
# spare sockets when dealing with large numbers of phones.
|
||||
async def query_phone_info(details, semaphore):
|
||||
async with semaphore:
|
||||
return await query_phone_info_now(details)
|
||||
|
||||
|
||||
# Given information about a list of phones (specifically, their IP addresses),
|
||||
# we call the HTTP server on each phone to extract the MAC and serial. We
|
||||
# return with a list of dicts containing information about all phones.
|
||||
# Contacting thousands of phones serially would take too long, so we keep 200
|
||||
# concurrent calls in-flight to the phones to shorten all querying to a few
|
||||
# seconds.
|
||||
async def query_phones(details):
|
||||
sem = asyncio.Semaphore(200)
|
||||
tasks = map(lambda d: asyncio.create_task(query_phone_info(d, sem)), details)
|
||||
done, pending = await asyncio.wait(tasks, timeout=phone_queries_timeout)
|
||||
# we silently ignore pending for now
|
||||
return map(lambda f: f.result(), done)
|
||||
|
||||
|
||||
# Given an array of phone names, do paginated queries to the CUCM for phone
|
||||
# information, then asynchronously query all the phones. While the CUCM has
|
||||
# most of the information we want about a phone, it critically lacks the serial
|
||||
# and MAC of the phone, which is why we need to fetch the details from the
|
||||
# phone itself over an HTTP server each phone has.
|
||||
def get_phones(addr, port, user, password, insecure):
|
||||
axl_xml = query_cucm_axl(addr, port, user, password, insecure)
|
||||
phone_ids = get_phone_ids(axl_xml)
|
||||
|
||||
phone_details = []
|
||||
page_size = cucm_page_size
|
||||
for i in range(0, len(phone_ids), page_size):
|
||||
ids = phone_ids[i:i + page_size]
|
||||
cucm_xml = query_cucm_risport(addr, port, user, password, insecure, ids)
|
||||
details = get_phone_details(cucm_xml)
|
||||
phone_details.extend(details)
|
||||
|
||||
return asyncio.run(query_phones(phone_details))
|
||||
|
||||
|
||||
# Print out all our results in a format that CheckMK understands. Most of our
|
||||
# output are in JSON rows.
|
||||
def print_out(device_info, agent_name):
|
||||
sys.stdout.write(f"<<<{agent_name}:sep(0)>>>\n")
|
||||
device_info = list(device_info)
|
||||
device_info.sort(key=lambda d: d["ip"])
|
||||
for entry in device_info:
|
||||
sys.stdout.write("%s\n" % json.dumps(entry))
|
||||
|
||||
|
||||
# Parse the command-line arguments. We have several options, but hostname is
|
||||
# always required. Print out help to console if we get no args.
|
||||
def parse_arguments(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"-u", "--user", default=None, help="Username for CUCM login"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--password", default=None, help="Password for CUCM login"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--port", default=443, type=int, help="Use alternative port (default: 443)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"hostname", metavar="HOSTNAME", help="Hostname of the CUCM to query."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k", "--insecure", default=False, help="Skip certificate verification",
|
||||
action="store_true"
|
||||
)
|
||||
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
# Parse args, contact CUCM, query phones, and then print results
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = parse_arguments(argv)
|
||||
phones = get_phones(args.hostname, args.port, args.user, args.password,
|
||||
args.insecure)
|
||||
print_out(phones, 'cucm_inv')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
24
cucm/local/share/check_mk/checks/agent_cucm
Normal file
24
cucm/local/share/check_mk/checks/agent_cucm
Normal file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
def agent_cucm_arguments(params, hostname, ipaddress):
|
||||
args = []
|
||||
|
||||
if "user" in params:
|
||||
args += ["-u", params["user"]]
|
||||
|
||||
if "password" in params:
|
||||
args += ["-s", params["password"]]
|
||||
|
||||
if "port" in params:
|
||||
args += ["-p", params["port"]]
|
||||
|
||||
if params.get("insecure"):
|
||||
args.append("-k")
|
||||
|
||||
args.append(params["instance"])
|
||||
|
||||
return args
|
||||
|
||||
special_agent_info["cucm_chk"] = agent_cucm_arguments
|
||||
special_agent_info["cucm_inv"] = agent_cucm_arguments
|
19
cucm/local/share/check_mk/web/plugins/views/cucm.py
Normal file
19
cucm/local/share/check_mk/web/plugins/views/cucm.py
Normal file
@ -0,0 +1,19 @@
|
||||
from cmk.gui.plugins.views import inventory_displayhints
|
||||
from cmk.gui.i18n import _l
|
||||
|
||||
# inventory list for phones found in CUCM
|
||||
|
||||
inventory_displayhints.update({
|
||||
".phones:": {
|
||||
"title": _l("Phones"),
|
||||
"keyorder": ["name", "user", "dir_num", "ip", "model", "serial", "mac", "description"],
|
||||
},
|
||||
".phones:*.name": {"title": _l("Name")},
|
||||
".phones:*.user": {"title": _l("User")},
|
||||
".phones:*.dir_num": {"title": _l("Dir Num")},
|
||||
".phones:*.ip": {"title": _l("IP Addr")},
|
||||
".phones:*.model": {"title": _l("Model")},
|
||||
".phones:*.serial": {"title": _l("Serial")},
|
||||
".phones:*.mac": {"title": _l("MAC Addr")},
|
||||
".phones:*.description": {"title": _l("Description")},
|
||||
})
|
120
cucm/local/share/check_mk/web/plugins/wato/cucm.py
Normal file
120
cucm/local/share/check_mk/web/plugins/wato/cucm.py
Normal file
@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# GUI configuration pages to set up inventorying and checks done by the CUCM
|
||||
# agent to CUCM. These two pages are for giving the agent the necessary details
|
||||
# to connect to CUCM (e.g. IP address, user, login, etc).
|
||||
#
|
||||
# Ideally, we'd have a single page to configure both the inventorying and
|
||||
# checks, since both contact the same CUCM instance. Unfortunately, I didn't
|
||||
# find a clean way to do it, so we're left with two identical GUI pages that
|
||||
# take identical information. At least we manage to share most of the code
|
||||
# here by taking a deep copy and modifying the title.
|
||||
|
||||
import copy
|
||||
from cmk.gui.i18n import _
|
||||
from cmk.gui.plugins.wato.utils import (
|
||||
rulespec_registry,
|
||||
HostRulespec,
|
||||
RulespecGroupCheckParametersHardware
|
||||
)
|
||||
from cmk.gui.plugins.wato.inventory import RulespecGroupInventory
|
||||
from cmk.gui.watolib.rulespecs import Rulespec
|
||||
from cmk.gui.valuespec import (
|
||||
Dictionary,
|
||||
TextInput,
|
||||
Hostname,
|
||||
NetworkPort,
|
||||
Password,
|
||||
TextAscii,
|
||||
FixedValue
|
||||
)
|
||||
|
||||
|
||||
# GUI config page for inventory.
|
||||
def _valuespec_special_agents_cucm_inv():
|
||||
return Dictionary(
|
||||
title=_("CUCM inventory"),
|
||||
help=_(""),
|
||||
optional_keys=["port", "user", "password", "insecure"],
|
||||
elements=[
|
||||
(
|
||||
"instance",
|
||||
Hostname(
|
||||
title=_("Hostname"),
|
||||
help=_(
|
||||
"Host of CUCM host for query"
|
||||
),
|
||||
allow_empty=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"port",
|
||||
NetworkPort(
|
||||
title=_("Port"),
|
||||
help=_(
|
||||
"Port of CUCM host for query"
|
||||
),
|
||||
minvalue=1,
|
||||
default_value=443,
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
TextInput(
|
||||
title=_("Username"),
|
||||
help=_(
|
||||
"Username used when querying CUCM"
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
"password",
|
||||
Password(
|
||||
title=_("Password"),
|
||||
help=_(
|
||||
"Password used when querying CUCM"
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
"insecure",
|
||||
FixedValue(
|
||||
True,
|
||||
title=_("Insecure"),
|
||||
totext=_("Disable SSL certificate verification"),
|
||||
help=_(
|
||||
"Ignore unverified HTTPS request warnings when contacting CUCM"
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# GUI config page for checks. We do a deep copy of the above function and just
|
||||
# change the title. A bit hackish since we're changing a private attribute.
|
||||
def _valuespec_special_agents_cucm_chk():
|
||||
inv_spec = _valuespec_special_agents_cucm_inv()
|
||||
chk_spec = copy.deepcopy(inv_spec)
|
||||
chk_spec._title=_("CUCM checks")
|
||||
return chk_spec
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
factory_default=Rulespec.FACTORY_DEFAULT_UNUSED,
|
||||
name="special_agents:cucm_inv",
|
||||
group=RulespecGroupInventory,
|
||||
valuespec=_valuespec_special_agents_cucm_inv,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
rulespec_registry.register(
|
||||
HostRulespec(
|
||||
factory_default=Rulespec.FACTORY_DEFAULT_UNUSED,
|
||||
name="special_agents:cucm_chk",
|
||||
group=RulespecGroupCheckParametersHardware,
|
||||
valuespec=_valuespec_special_agents_cucm_chk,
|
||||
)
|
||||
)
|
BIN
domains/domain_checks-0.2.0.mkp
Executable file
BIN
domains/domain_checks-0.2.0.mkp
Executable file
Binary file not shown.
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
import datetime
|
||||
import json
|
||||
from cmk.agent_based.v2 import Result, Service, State, CheckPlugin, AgentSection
|
||||
|
||||
|
||||
# Incoming agent output is JSON of the form:
|
||||
#
|
||||
# {"domain": "google.com", "state": "CRIT", "expires": "2028-09-14"}
|
||||
#
|
||||
# "domain" and "state" are always present, whereas "expires" is optional
|
||||
# (e.g. most ccTLDs do not return expiry information). The "state" is one of
|
||||
# (OK, WARN, CRIT, UNKNOWN).
|
||||
#
|
||||
# Return a dictionary which uses domain as key, and the rest of the dictionary
|
||||
# as a value.
|
||||
def parse_domains_expiry(string_table):
|
||||
results = {}
|
||||
|
||||
for [line] in string_table:
|
||||
result = json.loads(line)
|
||||
results[result["domain"]] = result
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def discover_domains_expiry(section):
|
||||
for domain in section.keys():
|
||||
yield Service(item=domain)
|
||||
|
||||
|
||||
def check_domains_expiry(item, params, section):
|
||||
data = section.get(item)
|
||||
if not data:
|
||||
yield Result(state=State.WARN, summary="Expiry not found in whois")
|
||||
return
|
||||
|
||||
state = State[data["state"]]
|
||||
expiry = data.get("expires")
|
||||
|
||||
if expiry:
|
||||
summary = f"Expires on {expiry}"
|
||||
else:
|
||||
summary = "No expiry found"
|
||||
|
||||
yield Result(state=state, summary=summary)
|
||||
|
||||
|
||||
agent_section_domains_expiry = AgentSection(
|
||||
name="domains_expiry",
|
||||
parse_function=parse_domains_expiry
|
||||
)
|
||||
|
||||
check_plugin_domains_expiry = CheckPlugin(
|
||||
name="domains_expiry",
|
||||
service_name="Expiry for Domain '%s'",
|
||||
|
||||
discovery_function=discover_domains_expiry,
|
||||
|
||||
check_function=check_domains_expiry,
|
||||
check_default_parameters={},
|
||||
)
|
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
import json
|
||||
from cmk.agent_based.v2 import Result, Service, State, CheckPlugin, AgentSection
|
||||
|
||||
|
||||
# Incoming agent output is JSON of the form:
|
||||
#
|
||||
# {"domain": "yahoo.com", "state": "WARN", "unexpected": ["ns5.yahoo.com"], "missing": ["ns6.yahoo.com"]}
|
||||
#
|
||||
# Each JSON row is for a single domain. Each row always has "domain" and "state"
|
||||
# fields, with optional "unexpected" and "missing" fields. The "state" field is
|
||||
# one of "OK", "WARN", "CRIT", or "UNKNOWN". The "unexpected" field is a list of
|
||||
# unwanted nameservers that are present in the dig query result, whereas the
|
||||
# "missing" field are wanted nameservers that are not present.
|
||||
#
|
||||
# Return a dictionary which uses domain as key, and a dictionary of
|
||||
# state/unexpected/missing.
|
||||
def parse_domains_nameservers(string_table):
|
||||
results = {}
|
||||
|
||||
for [line] in string_table:
|
||||
result = json.loads(line)
|
||||
results[result["domain"]] = result
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def discover_domains_nameservers(section):
|
||||
for domain in section.keys():
|
||||
yield Service(item=domain)
|
||||
|
||||
|
||||
def check_domains_nameservers(item, params, section):
|
||||
data = section.get(item)
|
||||
if not data:
|
||||
yield Result(state=State.WARN, summary="Nameservers missing")
|
||||
return
|
||||
|
||||
state = State[data["state"]]
|
||||
unexpected = data.get("unexpected")
|
||||
missing = data.get("missing")
|
||||
|
||||
summary = ""
|
||||
|
||||
if unexpected:
|
||||
summary += f"Unexpected nameserver(s) found: {''.join(unexpected)}. "
|
||||
|
||||
if missing:
|
||||
summary += f"Expected nameserver(s) missing: {''.join(missing)}. "
|
||||
|
||||
if not summary:
|
||||
summary = "All expected nameservers found, none unexpected found"
|
||||
|
||||
yield Result(state=state, summary=summary)
|
||||
|
||||
|
||||
agent_section_triton_wedge = AgentSection(
|
||||
name="domains_nameservers",
|
||||
parse_function=parse_domains_nameservers
|
||||
)
|
||||
|
||||
check_plugin_domains_nameservers = CheckPlugin(
|
||||
name="domains_nameservers",
|
||||
service_name="Nameservers for Domain '%s'",
|
||||
|
||||
discovery_function=discover_domains_nameservers,
|
||||
|
||||
check_function=check_domains_nameservers,
|
||||
check_default_parameters={},
|
||||
)
|
@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
set -eu
|
||||
|
||||
if [[ $# < 3 ]]; then
|
||||
echo "Usage: ${@: 0:1} <domains> <crit date> <warn date>" 1>&2
|
||||
echo "Example: ${@: 0:1} google.com yahoo.com 2024-05-07 2024-05-21" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract from args
|
||||
domains="${@: 1:$#-2}"
|
||||
warn="${@: -1:1}"
|
||||
crit="${@: -2:1}"
|
||||
|
||||
echo "<<<domains_expiry:sep(0)>>>"
|
||||
|
||||
for domain in $domains; do
|
||||
echo -n "{\"domain\": \"$domain\", \"state\": \""
|
||||
|
||||
# Unfortunately, there's no actual format for whois entries, so this is a
|
||||
# best-effort based on things seen in the wild. Note that ccTLDs usually
|
||||
# do not publish expiry dates at all.
|
||||
expires=$(whois "$domain" | grep 'Expir.*' | head -1 | grep -Eo '[0-9]{4}-[0-9]{2}-[0-9]{2}' || true)
|
||||
|
||||
if [[ "$expires" == "" ]]; then
|
||||
echo -n "UNKNOWN"
|
||||
elif [[ "$expires" < "$crit" ]]; then
|
||||
echo -n "CRIT"
|
||||
elif [[ "$expires" < "$warn" ]]; then
|
||||
echo -n "WARN"
|
||||
else
|
||||
echo -n "OK"
|
||||
fi
|
||||
|
||||
if [[ "$expires" == "" ]]; then
|
||||
echo "\"}"
|
||||
else
|
||||
echo "\", \"expires\": \"$expires\"}"
|
||||
fi
|
||||
done
|
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
|
||||
import json
|
||||
import optparse
|
||||
import subprocess
|
||||
|
||||
|
||||
print("<<<domains_nameservers:sep(0)>>>")
|
||||
|
||||
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-d', '--domains', action="append")
|
||||
parser.add_option('-n', '--nameservers', action="append")
|
||||
parser.add_option('-a', '--alert', default="WARN")
|
||||
opts, _ = parser.parse_args()
|
||||
|
||||
alert = opts.alert
|
||||
assert ["OK", "WARN", "CRIT", "UNKNOWN"].count(alert)
|
||||
|
||||
for domain_str, nameserver_str in zip(opts.domains, opts.nameservers):
|
||||
domains = domain_str.split(",")
|
||||
nameservers = set(nameserver_str.split(","))
|
||||
|
||||
for domain in domains:
|
||||
dig_result = subprocess.run(["dig", "+short", "NS", domain], capture_output=True)
|
||||
dig_ns = set(dig_result.stdout.decode("utf-8").split('.\n'))
|
||||
dig_ns.remove('')
|
||||
|
||||
result = {
|
||||
"domain": domain,
|
||||
"state": "OK",
|
||||
}
|
||||
|
||||
if dig_ns != nameservers:
|
||||
result["state"] = alert
|
||||
|
||||
unexpected = list(dig_ns - nameservers)
|
||||
missing = list(nameservers - dig_ns)
|
||||
|
||||
if unexpected:
|
||||
result["unexpected"] = unexpected
|
||||
if missing:
|
||||
result["missing"] = missing
|
||||
|
||||
print(json.dumps(result))
|
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
from cmk.rulesets.v1.form_specs.validators import LengthInRange, NumberInRange
|
||||
from cmk.rulesets.v1.form_specs import Dictionary, DictElement, List, String, Integer, DefaultValue
|
||||
from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic, Title, Help
|
||||
|
||||
|
||||
def _formspec():
|
||||
return Dictionary(
|
||||
title=Title("Domains Expiry"),
|
||||
elements={
|
||||
"domains": DictElement(
|
||||
required=True,
|
||||
parameter_form=List(
|
||||
title=Title("Domain names"),
|
||||
help_text=Help("List of domain names to check"),
|
||||
editable_order=False,
|
||||
custom_validate=(LengthInRange(min_value=1),),
|
||||
element_template=String(
|
||||
custom_validate=(LengthInRange(min_value=3),),
|
||||
),
|
||||
),
|
||||
),
|
||||
"days_warn": DictElement(
|
||||
required=True,
|
||||
parameter_form=Integer(
|
||||
title=Title("Warn if expires within days"),
|
||||
help_text=Help("If there are fewer days until one of the above domains expires, issue an alert"),
|
||||
custom_validate=(NumberInRange(min_value=0),),
|
||||
prefill=DefaultValue(30),
|
||||
),
|
||||
),
|
||||
"days_crit": DictElement(
|
||||
required=True,
|
||||
parameter_form=Integer(
|
||||
title=Title("Crit if expires within days"),
|
||||
help_text=Help("If there are fewer days until one of the above domains expires, issue an alert"),
|
||||
custom_validate=(NumberInRange(min_value=0),),
|
||||
prefill=DefaultValue(7),
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
rule_spec_agent_config_domains_expiry = SpecialAgent(
|
||||
topic=Topic.NETWORKING,
|
||||
name="domains_expiry",
|
||||
title=Title("Domains Expiry"),
|
||||
parameter_form=_formspec,
|
||||
)
|
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
from cmk.rulesets.v1.form_specs.validators import LengthInRange
|
||||
from cmk.rulesets.v1.form_specs import Dictionary, DictElement, SingleChoice, SingleChoiceElement, List, String, DefaultValue
|
||||
from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic, Title, Help
|
||||
from cmk.agent_based.v2 import State
|
||||
|
||||
|
||||
def _formspec():
|
||||
return Dictionary(
|
||||
title=Title("Domains Nameservers"),
|
||||
elements={
|
||||
"domain_nameservers": DictElement(
|
||||
required=True,
|
||||
parameter_form=List(
|
||||
title=Title("Domains to check"),
|
||||
help_text=Help("List of domain names to check"),
|
||||
editable_order=False,
|
||||
custom_validate=(LengthInRange(min_value=1),),
|
||||
element_template=Dictionary(
|
||||
elements={
|
||||
"domains": DictElement(
|
||||
required=True,
|
||||
parameter_form=List(
|
||||
title=Title("Domain names"),
|
||||
help_text=Help("List of domain names the below nameservers apply to"),
|
||||
custom_validate=(LengthInRange(min_value=1),),
|
||||
element_template=String(
|
||||
custom_validate=(LengthInRange(min_value=3),),
|
||||
),
|
||||
),
|
||||
),
|
||||
"nameservers": DictElement(
|
||||
required=True,
|
||||
parameter_form=List(
|
||||
title=Title("Nameservers"),
|
||||
help_text=Help("List of nameservers that the above domain names should have"),
|
||||
custom_validate=(LengthInRange(min_value=1),),
|
||||
element_template=String(
|
||||
custom_validate=(LengthInRange(min_value=3),),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
),
|
||||
"alert_level": DictElement(
|
||||
required=True,
|
||||
parameter_form=SingleChoice(
|
||||
title=Title("Alert level used on mismatch"),
|
||||
help_text=Help("Alert level used when there is a mismatch in domain name servers for a domain"),
|
||||
prefill=DefaultValue(State.WARN.name),
|
||||
elements=[
|
||||
SingleChoiceElement(name=State.CRIT.name, title=Title(State.CRIT.name)),
|
||||
SingleChoiceElement(name=State.WARN.name, title=Title(State.WARN.name)),
|
||||
SingleChoiceElement(name=State.OK.name, title=Title(State.OK.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
rule_spec_agent_config_domains_nameservers = SpecialAgent(
|
||||
topic=Topic.NETWORKING,
|
||||
name="domains_nameservers",
|
||||
title=Title("Domains Nameservers"),
|
||||
parameter_form=_formspec,
|
||||
)
|
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from cmk.server_side_calls.v1 import noop_parser, SpecialAgentConfig, SpecialAgentCommand
|
||||
|
||||
|
||||
def _agent_arguments(params, host_config):
|
||||
today = datetime.today()
|
||||
|
||||
args = params["domains"]
|
||||
for key in ["days_crit", "days_warn"]:
|
||||
date = (today + timedelta(days=params[key])).date()
|
||||
args.append(str(date))
|
||||
|
||||
yield SpecialAgentCommand(command_arguments=args)
|
||||
|
||||
|
||||
special_agent_domains_expiry = SpecialAgentConfig(
|
||||
name="domains_expiry",
|
||||
parameter_parser=noop_parser,
|
||||
commands_function=_agent_arguments,
|
||||
)
|
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Spearhead Systems SRL
|
||||
|
||||
from cmk.server_side_calls.v1 import noop_parser, SpecialAgentConfig, SpecialAgentCommand
|
||||
|
||||
|
||||
# Although the API makes it seem like we can yield several
|
||||
# SpecialAgentCommands, the use of a table for _elems in
|
||||
# lib/python3/cmk/base/sources/_builder.py (at least in 2.3.0p31) means that
|
||||
# we can only emit one SpecialAgentCommands. This CheckMK bug means that
|
||||
# SpecialAgentCommands() will be forgotten.
|
||||
#
|
||||
# Alas, that means we need to pack everything into a single command invocation.
|
||||
def _agent_arguments(params, host_config):
|
||||
args = []
|
||||
|
||||
alert_level = params["alert_level"]
|
||||
|
||||
for ele in params["domain_nameservers"]:
|
||||
domains = ele["domains"]
|
||||
nameservers = ele["nameservers"]
|
||||
|
||||
args.append("--domains=" + ",".join(domains))
|
||||
args.append("--nameservers=" + ",".join(nameservers))
|
||||
args.append("--alert=" + alert_level)
|
||||
|
||||
yield SpecialAgentCommand(command_arguments=args)
|
||||
|
||||
|
||||
special_agent_domains_nameservers = SpecialAgentConfig(
|
||||
name="domains_nameservers",
|
||||
parameter_parser=noop_parser,
|
||||
commands_function=_agent_arguments,
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user