Skip to content

Commit

Permalink
15306 prism_host_networks: new check
Browse files Browse the repository at this point in the history
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
Yogibaer75 and BenediktSeidl committed Nov 8, 2023
1 parent 640eaac commit 8f2bd9a
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .werks/15306
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.
17 changes: 17 additions & 0 deletions checkman/prism_host_networks
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.
138 changes: 138 additions & 0 deletions cmk/base/plugins/agent_based/prism_host_networks.py
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",
)
1 change: 1 addition & 0 deletions cmk/gui/plugins/metrics/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@
check_metrics["check_mk-winperf_if"] = if_translation
check_metrics["check_mk-gcp_gce_network"] = if_translation
check_metrics["check_mk-azure_vm_network_io"] = if_translation
check_metrics["check_mk-prism_host_networks"] = if_translation
check_metrics["check_mk-brocade_fcport"] = {
"in": {
"name": "fc_rx_bytes",
Expand Down
3 changes: 3 additions & 0 deletions cmk/special_agents/agent_prism.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def output_hosts(requester: Requester) -> None:
with ConditionalPiggybackSection(element.get("name")):
with SectionWriter("prism_host") as w:
w.append_json(element)
networks = requester.get("hosts/%s/host_nics" % element.get("uuid"))
with SectionWriter("prism_host_networks") as w:
w.append_json(networks)


def output_protection(requester: Requester) -> None:
Expand Down
206 changes: 206 additions & 0 deletions tests/unit/cmk/base/plugins/agent_based/test_prism_host_networks.py
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
)

0 comments on commit 8f2bd9a

Please sign in to comment.