Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _item_name(item_data, padding):

def inventorize_redfish_firmware(section: RedfishAPIData) -> InventoryResult:
"""create inventory table for firmware"""
path = ["hardware", "firmware", "redfish"]
path = ["hardware", "firmware"]
if section.get("FirmwareInventory", {}).get("Current"):
data = section.get("FirmwareInventory", {}).get("Current")
padding = len(str(len(data)))
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""Redfish HW/SW inventory plugin"""

# (c) Andreas Doehler <andreas.doehler@bechtle.com/andreas.doehler@gmail.com>
# License: GNU General Public License v2

from collections.abc import Mapping, Sequence
from typing import Any

from cmk.agent_based.v2 import InventoryPlugin, InventoryResult, TableRow
from cmk.plugins.redfish.lib import RedfishAPIData

IDSET = set[tuple[str, str, str, str, str, str]]


def _extract_odata_ids(
data: None | RedfishAPIData | Sequence[Mapping[str, Any]], ids_set: IDSET
) -> IDSET:
if data is None or isinstance(data, (str, int, float, bool)):
return ids_set

if isinstance(data, Mapping):
for key, value in data.items():
if key == "@odata.id" and isinstance(value, str):
if "Oem" not in value:
key_name = data.get("Name")
if key_name:
ids_set.add(
(
value,
key_name,
(data.get("SerialNumber") or "nothing set").strip(),
(data.get("PartNumber") or "nothing set").strip(),
(data.get("Manufacturer") or "nothing set").strip(),
(data.get("Model") or "nothing set").strip(),
)
)
else:
ids_set = _extract_odata_ids(value, ids_set)
else:
for item in data:
ids_set = _extract_odata_ids(item, ids_set)

return ids_set


def inventory_redfish_data(
section_redfish_storage: None | RedfishAPIData,
section_redfish_processors: None | RedfishAPIData,
section_redfish_drives: None | RedfishAPIData,
section_redfish_psu: None | RedfishAPIData,
section_redfish_memory: None | RedfishAPIData,
section_redfish_power: None | RedfishAPIData,
section_redfish_thermal: None | RedfishAPIData,
section_redfish_networkadapters: None | RedfishAPIData,
) -> InventoryResult:
result_path = ["redfish"]

odata_ids_set: IDSET = set()
odata_ids_set = _extract_odata_ids(section_redfish_processors, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_storage, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_drives, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_psu, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_memory, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_power, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_thermal, odata_ids_set)
odata_ids_set = _extract_odata_ids(section_redfish_networkadapters, odata_ids_set)

for path, name, serial, part_number, manufacturer, model in odata_ids_set:
if (
serial in ("nothing set", "NOT AVAILABLE")
and part_number in ("nothing set", "NOT AVAILABLE")
and manufacturer == "nothing set"
and model == "nothing set"
):
continue
if path.startswith("/redfish/"):
segments = (
path.replace("#", "")
.replace(":", "-")
.replace(".", "_")
.replace("'", "")
.replace("(", "_")
.replace(")", "_")
.replace("%", "_")
.strip("/")
.split("/")
)
result_path = [element for element in segments if element != ""]
item_id = result_path.pop()
if result_path[0] == "redfish":
result_path = result_path[1:]
if result_path[0] == "v1":
result_path = result_path[1:]
final_path = ["hardware"] + result_path
yield TableRow(
path=final_path,
key_columns={"id": item_id},
inventory_columns={
"name": name,
"serial": serial,
"part_number": part_number,
"manufacturer": manufacturer,
"model": model,
},
)


inventory_plugin_redfish_data = InventoryPlugin(
name="redfish_data",
sections=[
"redfish_storage",
"redfish_processors",
"redfish_drives",
"redfish_psu",
"redfish_memory",
"redfish_power",
"redfish_thermal",
"redfish_networkadapters",
],
inventory_function=inventory_redfish_data,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
# 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 Mapping
from typing import Any

from cmk.agent_based.v2 import (
AgentSection,
CheckPlugin,
Expand All @@ -26,21 +29,55 @@
)


def discovery_redfish_drives(section: RedfishAPIData) -> DiscoveryResult:
for key in section.keys():
if section[key].get("Status", {}).get("State") == "Absent":
# '@odata.id': '/redfish/v1/Systems/0/Storage/1/Drives/4' -> item: "0:1:4"
# or
# '@odata.id': '/redfish/v1/Chassis/DE00B000/Drives/0' -> item: "DE00B000:0"
# or
# 'Id' + '-' + 'Name'


def _build_drive_item(data: Mapping[str, Any]) -> tuple[str, str]:
item1 = data["Id"] + "-" + data["Name"]
item2 = item1
if isinstance(data.get("@odata.id"), str):
odataid = str(data.get("@odata.id"))
parts = odataid.split("/")
try:
system_id = parts[parts.index("Systems") + 1]
storage_id = parts[parts.index("Storage") + 1]
drive_id = parts[parts.index("Drives") + 1]
item2 = ":".join([system_id, storage_id, drive_id])
except ValueError:
# Handle case like '/redfish/v1/Chassis/DE00B000/Drives/0'
try:
chassis_id = parts[parts.index("Chassis") + 1]
drive_id = parts[parts.index("Drives") + 1]
item2 = ":".join([chassis_id, drive_id])
except ValueError:
# Fallback to default if neither pattern matches
item2 = item1
return item1, item2


def discovery_redfish_drives(params: Mapping[str, Any], section: RedfishAPIData) -> DiscoveryResult:
for _key, data in section.items():
if data.get("Status", {}).get("State") == "Absent":
continue
if not section[key]["Name"]:
if not data["Name"]:
continue
item = section[key].get("Id", "0") + "-" + section[key]["Name"]
yield Service(item=item)
item1, item2 = _build_drive_item(data)
if params.get("item") == "classic":
yield Service(item=item1)
else:
yield Service(item=item2)


def check_redfish_drives(item: str, section: RedfishAPIData) -> CheckResult:
data = None
for key in section.keys():
if item == section[key].get("Id", "0") + "-" + section[key]["Name"]:
data = section.get(key, None)
for data_value in section.values():
item1, item2 = _build_drive_item(data_value)
if item in (item1, item2):
data = data_value
break
if data is None:
return
Expand Down Expand Up @@ -70,5 +107,7 @@ def check_redfish_drives(item: str, section: RedfishAPIData) -> CheckResult:
service_name="Drive %s",
sections=["redfish_drives"],
discovery_function=discovery_redfish_drives,
discovery_ruleset_name="discovery_redfish_drives",
discovery_default_parameters={"item": "classic"},
check_function=check_redfish_drives,
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CheckPlugin,
CheckResult,
DiscoveryResult,
render,
Result,
Service,
State,
Expand Down Expand Up @@ -52,37 +53,61 @@ def discovery_redfish_ethernetinterfaces(
continue
if section[key].get("LinkStatus", "NOLINK") in ["LinkUp"] and disc_state == "down":
continue
yield Service(item=section[key]["Id"])
yield Service(
item=section[key]["Id"],
parameters={
"discover_speed": section[key].get("SpeedMbps", 0)
if "SpeedMbps" in section[key]
else section[key].get("CurrentLinkSpeedMbps", 0),
"discover_link_status": section[key].get("LinkStatus", "NOLINK"),
},
)


def check_redfish_ethernetinterfaces(item: str, section: RedfishAPIData) -> CheckResult:
def _render_speed(speed: int) -> str:
factor = 1_000_000
return render.networkbandwidth(speed / 8 * factor)


def check_redfish_ethernetinterfaces(
item: str, params: Mapping[str, Any], section: RedfishAPIData
) -> CheckResult:
"""Check single interfaces"""
data = section.get(item, None)
if data is None:
return

mac_addr = ""
link_state = State.OK
link_summary = "Link: No info"
if (link_status := data.get("LinkStatus")) is not None:
link_summary = f"Link: {link_status}"
if (discover_link_changed := params.get("discover_link_status")) != link_status:
link_state = State(params.get("state_if_link_status_changed") or 0)
link_summary = f"Link: {link_status} (changed from {discover_link_changed})"
yield Result(state=link_state, summary=link_summary)

speed_state = State.OK
speed_summary = "Speed: No info"
link_speed: int | None = data.get("CurrentLinkSpeedMbps")
if link_speed is None: # Prioritize CurrentLinkSpeedMbps, fallback to SpeedMbps
link_speed = data.get("SpeedMbps")

if link_speed is not None:
speed_summary = f"Speed: {_render_speed(link_speed)}"
if (discover_speed := params.get("discover_speed") or 0) != link_speed:
speed_state = State(params.get("state_if_link_speed_changed") or 0)
speed_summary = (
f"Speed: {_render_speed(link_speed)} (changed from {_render_speed(discover_speed)})"
)
yield Result(state=speed_state, summary=speed_summary)

mac_addr = None
if data.get("AssociatedNetworkAddresses"):
mac_addr = ", ".join(data.get("AssociatedNetworkAddresses"))
elif data.get("MACAddress"):
mac_addr = data.get("MACAddress")

link_speed = 0
if data.get("CurrentLinkSpeedMbps"):
link_speed = data.get("CurrentLinkSpeedMbps")
elif data.get("SpeedMbps"):
link_speed = data.get("SpeedMbps")
if link_speed is None:
link_speed = 0

link_status = "Unknown"
if data.get("LinkStatus"):
link_status = data.get("LinkStatus")
if link_status is None:
link_status = "Down"

int_msg = f"Link: {link_status}, Speed: {link_speed:0.0f}Mbps, MAC: {mac_addr}"
yield Result(state=State(0), summary=int_msg)
if mac_addr:
yield Result(state=State.OK, summary=f"MAC Address: {mac_addr}")

if data.get("Status"):
dev_state, dev_msg = redfish_health_state(data.get("Status", {}))
Expand All @@ -103,4 +128,6 @@ def check_redfish_ethernetinterfaces(item: str, section: RedfishAPIData) -> Chec
discovery_ruleset_name="discovery_redfish_ethernetinterfaces",
discovery_default_parameters={"state": "updown"},
check_function=check_redfish_ethernetinterfaces,
check_default_parameters={},
check_ruleset_name="check_redfish_ethernetinterfaces",
)
Loading
Loading