-
Notifications
You must be signed in to change notification settings - Fork 474
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
15306 prism_host_networks: new check
Add a new check to monitor host networks via prism refs #638 Change-Id: I600a434b11f4c7aee3bdb29a30d3560c65e5542c Co-Authored-By: Benedikt Seidl <[email protected]>
- Loading branch information
1 parent
640eaac
commit 8f2bd9a
Showing
6 changed files
with
375 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Title: prism_host_networks: new check | ||
Class: feature | ||
Compatible: compat | ||
Component: checks | ||
Date: 1698910922 | ||
Edition: cre | ||
Level: 1 | ||
Version: 2.3.0b1 | ||
|
||
Add a new check to monitor host networks via prism. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
title: Nutanix Prism: Host Network Interfaces | ||
agents: nutanix | ||
catalog: virtual/nutanix | ||
license: GPLv2 | ||
distribution: check_mk | ||
description: | ||
This check monitors the status of the network interfaces in a Nutanix host. | ||
The interface parameter can be configured through Setup. | ||
It uses the standard interface parameters. | ||
|
||
This check requires the special agent configurable through "Nutanix Prism". | ||
|
||
item: | ||
The name or number of the interface. | ||
|
||
discovery: | ||
One service is created for each interface. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (C) 2023 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. | ||
|
||
|
||
import json | ||
from collections.abc import Mapping | ||
from typing import Any, Iterable, MutableMapping, NamedTuple, Sequence | ||
|
||
import pydantic | ||
|
||
from .agent_based_api.v1 import get_value_store, register | ||
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable | ||
from .utils import interfaces | ||
|
||
|
||
class Stats(pydantic.BaseModel): | ||
in_octets: float = pydantic.Field(validation_alias="network.received_bytes") | ||
in_ucast: float = pydantic.Field(validation_alias="network.received_pkts") | ||
in_mcast: float = pydantic.Field(validation_alias="network.multicast_received_pkts") | ||
in_bcast: float = pydantic.Field(validation_alias="network.broadcast_received_pkts") | ||
in_disc: float = pydantic.Field(validation_alias="network.dropped_received_pkts") | ||
in_err: float = pydantic.Field(validation_alias="network.error_received_pkts") | ||
out_octets: float = pydantic.Field(validation_alias="network.transmitted_bytes") | ||
out_ucast: float = pydantic.Field(validation_alias="network.transmitted_pkts") | ||
out_mcast: float = pydantic.Field(validation_alias="network.multicast_transmitted_pkts") | ||
out_bcast: float = pydantic.Field(validation_alias="network.broadcast_transmitted_pkts") | ||
out_disc: float = pydantic.Field(validation_alias="network.dropped_transmitted_pkts") | ||
out_err: float = pydantic.Field(validation_alias="network.error_transmitted_pkts") | ||
|
||
|
||
class RawData(pydantic.BaseModel): | ||
link_speed_in_kbps: None | int = None | ||
mac_address: None | str = None | ||
name: None | str = None | ||
stats: Stats | ||
|
||
|
||
class InterfaceElement(NamedTuple): | ||
name: str | ||
data: RawData | ||
|
||
|
||
def parse_prism_host_networks( | ||
string_table: StringTable, | ||
) -> interfaces.Section[interfaces.InterfaceWithRates]: | ||
try: | ||
data = pydantic.TypeAdapter(list[RawData]).validate_json(string_table[0][0]) | ||
except (IndexError, json.decoder.JSONDecodeError, pydantic.ValidationError): | ||
return [] | ||
|
||
def generator() -> Iterable[InterfaceElement]: | ||
for element in data: | ||
name = element.name | ||
if not name: | ||
if element.mac_address: | ||
# try to provide stable sorting without name | ||
name = f"unknown_{element.mac_address}" | ||
else: | ||
name = "unknown" | ||
yield InterfaceElement(name, element) | ||
|
||
return [ | ||
interfaces.InterfaceWithRates( | ||
attributes=interfaces.Attributes( | ||
index=str(index), | ||
descr=name, | ||
alias=name, | ||
type="6", | ||
speed=( | ||
0 if not raw_data.link_speed_in_kbps else raw_data.link_speed_in_kbps * 1000 | ||
), | ||
oper_status=("1" if raw_data.link_speed_in_kbps else "2"), | ||
phys_address=interfaces.mac_address_from_hexstring(raw_data.mac_address or ""), | ||
), | ||
rates=interfaces.Rates( | ||
# assuming a 30 seconds window in which those counters are accumulated. | ||
# could be verified via /get/hosts/{uuid}/host_nics/{pnic_id}/stats | ||
# https://www.nutanix.dev/api_references/prism-v2-0/#/3a7cca6f493d6-list-host-host-nic-stats | ||
in_octets=raw_data.stats.in_octets / 30, | ||
in_ucast=raw_data.stats.in_ucast / 30, | ||
in_mcast=raw_data.stats.in_mcast / 30, | ||
in_bcast=raw_data.stats.in_bcast / 30, | ||
in_disc=raw_data.stats.in_disc / 30, | ||
in_err=raw_data.stats.in_err / 30, | ||
out_octets=raw_data.stats.out_octets / 30, | ||
out_ucast=raw_data.stats.out_ucast / 30, | ||
out_mcast=raw_data.stats.out_mcast / 30, | ||
out_bcast=raw_data.stats.out_bcast / 30, | ||
out_disc=raw_data.stats.out_disc / 30, | ||
out_err=raw_data.stats.out_err / 30, | ||
), | ||
get_rate_errors=[], | ||
) | ||
for index, (name, raw_data) in enumerate(sorted(generator())) | ||
] | ||
|
||
|
||
register.agent_section( | ||
name="prism_host_networks", | ||
parse_function=parse_prism_host_networks, | ||
) | ||
|
||
|
||
def discovery_prism_host_networks( | ||
params: Sequence[Mapping[str, Any]], section: interfaces.Section[interfaces.InterfaceWithRates] | ||
) -> DiscoveryResult: | ||
yield from interfaces.discover_interfaces(params, section) | ||
|
||
|
||
def _check_prism_host_network( | ||
item: str, | ||
params: Mapping[str, Any], | ||
section: interfaces.Section[interfaces.InterfaceWithRates], | ||
value_store: MutableMapping[str, Any], | ||
) -> CheckResult: | ||
yield from interfaces.check_multiple_interfaces(item, params, section, value_store=value_store) | ||
|
||
|
||
def check_prism_host_networks( | ||
item: str, params: Mapping[str, Any], section: interfaces.Section[interfaces.InterfaceWithRates] | ||
) -> CheckResult: | ||
yield from _check_prism_host_network(item, params, section, get_value_store()) | ||
|
||
|
||
register.check_plugin( | ||
name="prism_host_networks", | ||
service_name="NTNX NIC %s", | ||
sections=["prism_host_networks"], | ||
discovery_ruleset_name="inventory_if_rules", | ||
discovery_ruleset_type=register.RuleSetType.ALL, | ||
discovery_default_parameters=dict(interfaces.DISCOVERY_DEFAULT_PARAMETERS), | ||
check_default_parameters=interfaces.CHECK_DEFAULT_PARAMETERS, | ||
discovery_function=discovery_prism_host_networks, | ||
check_function=check_prism_host_networks, | ||
check_ruleset_name="if", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
206 changes: 206 additions & 0 deletions
206
tests/unit/cmk/base/plugins/agent_based/test_prism_host_networks.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (C) 2023 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. | ||
import json | ||
from collections.abc import Mapping, Sequence | ||
from typing import Any | ||
|
||
import pytest | ||
|
||
from cmk.base.plugins.agent_based.agent_based_api.v1 import Metric, Result, Service, State | ||
from cmk.base.plugins.agent_based.prism_host_networks import ( | ||
_check_prism_host_network, | ||
discovery_prism_host_networks, | ||
parse_prism_host_networks, | ||
) | ||
from cmk.base.plugins.agent_based.utils import interfaces | ||
|
||
RAW = [ | ||
{ | ||
"link_speed_in_kbps": None, | ||
"mac_address": "0a:0b:0c:0d:0e:0f", | ||
"name": "eth0", | ||
"stats": { | ||
"network.broadcast_received_pkts": "-1", | ||
"network.broadcast_transmitted_pkts": "-1", | ||
"network.dropped_received_pkts": "0", | ||
"network.dropped_transmitted_pkts": "0", | ||
"network.error_received_pkts": "0", | ||
"network.error_transmitted_pkts": "0", | ||
"network.multicast_received_pkts": "-1", | ||
"network.multicast_transmitted_pkts": "-1", | ||
"network.received_bytes": "0", | ||
"network.received_pkts": "0", | ||
"network.transmitted_bytes": "0", | ||
"network.transmitted_pkts": "0", | ||
}, | ||
}, | ||
{ | ||
"link_speed_in_kbps": 10000000, | ||
"mac_address": "0a:0b:0c:0d:0e:11", | ||
"name": "eth2", | ||
"stats": { | ||
"network.broadcast_received_pkts": "-1", | ||
"network.broadcast_transmitted_pkts": "-1", | ||
"network.dropped_received_pkts": "0", | ||
"network.dropped_transmitted_pkts": "0", | ||
"network.error_received_pkts": "0", | ||
"network.error_transmitted_pkts": "0", | ||
"network.multicast_received_pkts": "-1", | ||
"network.multicast_transmitted_pkts": "-1", | ||
"network.received_bytes": "185547077", | ||
"network.received_pkts": "956968", | ||
"network.transmitted_bytes": "157213074", | ||
"network.transmitted_pkts": "135781", | ||
}, | ||
}, | ||
] | ||
|
||
|
||
@pytest.fixture(name="section", scope="module") | ||
def fixture_section() -> interfaces.Section[interfaces.InterfaceWithRates]: | ||
return parse_prism_host_networks([[json.dumps(RAW)]]) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["use_empty_section", "expected_discovery_result"], | ||
[ | ||
pytest.param( | ||
False, | ||
[ | ||
Service( | ||
item="1", | ||
parameters={"discovered_oper_status": ["1"], "discovered_speed": 10000000000.0}, | ||
), | ||
], | ||
id="For every network interface, a Service is discovered.", | ||
), | ||
pytest.param( | ||
True, | ||
[], | ||
id="If there are no items in the input, nothing is discovered.", | ||
), | ||
], | ||
) | ||
def test_discovery_prism_host_networks( | ||
use_empty_section: bool, | ||
expected_discovery_result: Sequence[Service], | ||
section: interfaces.Section[interfaces.InterfaceWithRates], | ||
) -> None: | ||
# could not find a way to use a fixture in param | ||
section = [] if use_empty_section else section | ||
assert ( | ||
list(discovery_prism_host_networks([(interfaces.DISCOVERY_DEFAULT_PARAMETERS)], section)) | ||
== expected_discovery_result | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["item", "params", "expected_check_result"], | ||
[ | ||
pytest.param( | ||
"1", | ||
{ | ||
"errors": {"both": ("abs", (10, 20))}, | ||
"discovered_speed": 10000000000, | ||
"discovered_oper_status": ["1"], | ||
}, | ||
[ | ||
Result(state=State.OK, summary="[eth2]"), | ||
Result(state=State.OK, summary="(up)", details="Operational state: up"), | ||
Result(state=State.OK, summary="MAC: 0A:0B:0C:0D:0E:11"), | ||
Result(state=State.OK, summary="Speed: 10 GBit/s"), | ||
Result(state=State.OK, summary="In: 6.18 MB/s (0.49%)"), | ||
Metric("in", 6184902.566666666, boundaries=(0.0, 1250000000.0)), | ||
Result(state=State.OK, summary="Out: 5.24 MB/s (0.42%)"), | ||
Metric("out", 5240435.8, boundaries=(0.0, 1250000000.0)), | ||
Result(state=State.OK, notice="Errors in: 0 packets/s"), | ||
Metric("inerr", 0.0, levels=(10.0, 20.0)), | ||
Result(state=State.OK, notice="Discards in: 0 packets/s"), | ||
Metric("indisc", 0.0), | ||
Result(state=State.OK, notice="Multicast in: -0.03 packets/s"), | ||
Metric("inmcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Broadcast in: -0.03 packets/s"), | ||
Metric("inbcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Unicast in: 31898.93 packets/s"), | ||
Metric("inucast", 31898.933333333334), | ||
Result(state=State.OK, notice="Non-unicast in: -0.07 packets/s"), | ||
Metric("innucast", -0.06666666666666667), | ||
Result(state=State.OK, notice="Errors out: 0 packets/s"), | ||
Metric("outerr", 0.0, levels=(10.0, 20.0)), | ||
Result(state=State.OK, notice="Discards out: 0 packets/s"), | ||
Metric("outdisc", 0.0), | ||
Result(state=State.OK, notice="Multicast out: -0.03 packets/s"), | ||
Metric("outmcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Broadcast out: -0.03 packets/s"), | ||
Metric("outbcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Unicast out: 4526.03 packets/s"), | ||
Metric("outucast", 4526.033333333334), | ||
Result(state=State.OK, notice="Non-unicast out: -0.07 packets/s"), | ||
Metric("outnucast", -0.06666666666666667), | ||
], | ||
id="If the network interface is in expected state, the check result is OK.", | ||
), | ||
pytest.param( | ||
"1", | ||
{ | ||
"errors": {"both": ("abs", (10, 20))}, | ||
"discovered_speed": 1000000000, | ||
"discovered_oper_status": ["1"], | ||
}, | ||
[ | ||
Result(state=State.OK, summary="[eth2]"), | ||
Result(state=State.OK, summary="(up)", details="Operational state: up"), | ||
Result(state=State.OK, summary="MAC: 0A:0B:0C:0D:0E:11"), | ||
Result(state=State.WARN, summary="Speed: 10 GBit/s (expected: 1 GBit/s)"), | ||
Result(state=State.OK, summary="In: 6.18 MB/s (0.49%)"), | ||
Metric("in", 6184902.566666666, boundaries=(0.0, 1250000000.0)), | ||
Result(state=State.OK, summary="Out: 5.24 MB/s (0.42%)"), | ||
Metric("out", 5240435.8, boundaries=(0.0, 1250000000.0)), | ||
Result(state=State.OK, notice="Errors in: 0 packets/s"), | ||
Metric("inerr", 0.0, levels=(10.0, 20.0)), | ||
Result(state=State.OK, notice="Discards in: 0 packets/s"), | ||
Metric("indisc", 0.0), | ||
Result(state=State.OK, notice="Multicast in: -0.03 packets/s"), | ||
Metric("inmcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Broadcast in: -0.03 packets/s"), | ||
Metric("inbcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Unicast in: 31898.93 packets/s"), | ||
Metric("inucast", 31898.933333333334), | ||
Result(state=State.OK, notice="Non-unicast in: -0.07 packets/s"), | ||
Metric("innucast", -0.06666666666666667), | ||
Result(state=State.OK, notice="Errors out: 0 packets/s"), | ||
Metric("outerr", 0.0, levels=(10.0, 20.0)), | ||
Result(state=State.OK, notice="Discards out: 0 packets/s"), | ||
Metric("outdisc", 0.0), | ||
Result(state=State.OK, notice="Multicast out: -0.03 packets/s"), | ||
Metric("outmcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Broadcast out: -0.03 packets/s"), | ||
Metric("outbcast", -0.03333333333333333), | ||
Result(state=State.OK, notice="Unicast out: 4526.03 packets/s"), | ||
Metric("outucast", 4526.033333333334), | ||
Result(state=State.OK, notice="Non-unicast out: -0.07 packets/s"), | ||
Metric("outnucast", -0.06666666666666667), | ||
], | ||
id="If the interface has the wrong speed, the check result is WARN.", | ||
), | ||
], | ||
) | ||
def test_check_prism_host_networks( | ||
item: str, | ||
params: Mapping[str, Any], | ||
expected_check_result: Sequence[Result], | ||
section: interfaces.Section[interfaces.InterfaceWithRates], | ||
) -> None: | ||
assert ( | ||
list( | ||
_check_prism_host_network( | ||
item=item, | ||
params=params, | ||
section=section, | ||
value_store={}, | ||
) | ||
) | ||
== expected_check_result | ||
) |