diff --git a/cmk/plugins/hp_proliant/agent_based/hp_proliant_da_cntlr.py b/cmk/plugins/hp_proliant/agent_based/hp_proliant_da_cntlr.py
index ff408ef5c9a..633439b7d6f 100644
--- a/cmk/plugins/hp_proliant/agent_based/hp_proliant_da_cntlr.py
+++ b/cmk/plugins/hp_proliant/agent_based/hp_proliant_da_cntlr.py
@@ -4,7 +4,7 @@
# conditions defined in the file COPYING, which is part of this source code package.
from collections.abc import Mapping
from enum import StrEnum
-from typing import assert_never, NamedTuple, Self
+from typing import assert_never, NamedTuple, Self, TypedDict
from cmk.agent_based.v2 import (
CheckPlugin,
@@ -182,16 +182,45 @@ def from_line(cls, line: list[str]) -> Self | None:
)
+class Params(TypedDict):
+ condition_other_state: int
+ board_condition_other_state: int
+ board_status_other_state: int
+
+
+# Defaults reproduce the historic behaviour (the "other" value maps to WARN).
+# HPE ProLiant Gen11 / iLO 6 firmware tends to report the board condition as
+# "other" for perfectly healthy controllers, which makes the service WARN
+# forever; the check ruleset lets users remap each "other" value independently.
+DEFAULT_PARAMETERS: Params = {
+ "condition_other_state": State.WARN.value,
+ "board_condition_other_state": State.WARN.value,
+ "board_status_other_state": State.WARN.value,
+}
+
+
+def _monitoring_state(value: SNMPCondition | SNMPState, other_state: State) -> State:
+ if value in (SNMPCondition.OTHER, SNMPState.OTHER):
+ return other_state
+ return value.to_state()
+
+
def parse_hp_proliant_da_cntlr(string_table: StringTable) -> ParsedSection:
return {line[0]: ControllerData.from_line(line) for line in string_table}
def discovery_hp_proliant_da_cntlr(section: ParsedSection) -> DiscoveryResult:
- if section:
- yield from (Service(item=item) for item in section)
+ # Skip phantom/placeholder rows (parsed to ``None``): HPE ProLiant Gen11 /
+ # iLO 6 exposes a controller table row -- typically at index 0 -- whose
+ # condition / role / board-status / board-condition cells are all "0", a
+ # value the vendor MIB does not define. Discovering it created a service
+ # that was permanently UNKNOWN ("Controller not found in SNMP data").
+ yield from (Service(item=item) for item, data in section.items() if data is not None)
-def check_hp_proliant_da_cntlr(item: ControllerID, section: ParsedSection) -> CheckResult:
+def check_hp_proliant_da_cntlr(
+ item: ControllerID, params: Params, section: ParsedSection
+) -> CheckResult:
if not (subsection := section.get(item)):
yield Result(state=State.UNKNOWN, summary="Controller not found in SNMP data")
return
@@ -201,9 +230,16 @@ def check_hp_proliant_da_cntlr(item: ControllerID, section: ParsedSection) -> Ch
"Board-Condition": subsection.b_cond,
"Board-Status": subsection.b_status,
}
+ other_states: Mapping[str, State] = {
+ "Condition": State(params["condition_other_state"]),
+ "Board-Condition": State(params["board_condition_other_state"]),
+ "Board-Status": State(params["board_status_other_state"]),
+ }
yield Result(
- state=State.worst(*(state.to_state() for state in states.values())),
+ state=State.worst(
+ *(_monitoring_state(state, other_states[label]) for label, state in states.items())
+ ),
summary=(
f"{', '.join(f'{label}: {state}' for label, state in states.items())} "
f"(Role: {subsection.role}, Model: {subsection.model}, Slot: {subsection.slot}, "
@@ -231,4 +267,6 @@ def check_hp_proliant_da_cntlr(item: ControllerID, section: ParsedSection) -> Ch
service_name="HW Controller %s",
discovery_function=discovery_hp_proliant_da_cntlr,
check_function=check_hp_proliant_da_cntlr,
+ check_default_parameters=DEFAULT_PARAMETERS,
+ check_ruleset_name="hp_proliant_da_cntlr",
)
diff --git a/cmk/plugins/hp_proliant/checkman/hp_proliant_da_cntlr b/cmk/plugins/hp_proliant/checkman/hp_proliant_da_cntlr
index 0ecdadab696..89b93374a67 100644
--- a/cmk/plugins/hp_proliant/checkman/hp_proliant_da_cntlr
+++ b/cmk/plugins/hp_proliant/checkman/hp_proliant_da_cntlr
@@ -16,8 +16,16 @@ description:
This check has been developed with ProLiant DL360 G5 systems but should work
on other HPE Proliant Servers too.
+ The reported controller condition, board condition and board status can each
+ take the value {other}, which by default maps to {WARN}. On ProLiant Gen11 /
+ iLO 6 the board condition is reported as {other} for perfectly healthy
+ controllers; the check parameters allow remapping the state for the {other}
+ value of each field independently.
+
item:
Index of the controller
discovery:
- One service is created for each raid controller.
+ One service is created for each RAID controller. Placeholder rows that report
+ no valid controller data (all-zero cells, as seen on Gen11 / iLO 6) are
+ skipped.
diff --git a/cmk/plugins/hp_proliant/rulesets/hp_proliant_da_cntlr.py b/cmk/plugins/hp_proliant/rulesets/hp_proliant_da_cntlr.py
new file mode 100644
index 00000000000..6ab0e1af631
--- /dev/null
+++ b/cmk/plugins/hp_proliant/rulesets/hp_proliant_da_cntlr.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+# Copyright (C) 2026 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.rulesets.v1 import Help, Title
+from cmk.rulesets.v1.form_specs import (
+ DefaultValue,
+ DictElement,
+ Dictionary,
+ ServiceState,
+)
+from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic
+
+
+def _make_form() -> Dictionary:
+ return Dictionary(
+ help_text=Help(
+ "The RAID controllers of HPE ProLiant servers report a condition, a "
+ "board condition and a board status. Each of these can take the value "
+ "other, meaning the instrument agent does not recognize the "
+ "status. HPE ProLiant Gen11 / iLO 6 firmware reports the board "
+ "condition as other for perfectly healthy controllers, which "
+ "makes the service WARN permanently. Here you can remap the monitoring "
+ "state used for the other value of each field independently."
+ ),
+ elements={
+ "condition_other_state": DictElement(
+ required=False,
+ parameter_form=ServiceState(
+ title=Title("State when the controller condition is other"),
+ prefill=DefaultValue(ServiceState.WARN),
+ ),
+ ),
+ "board_condition_other_state": DictElement(
+ required=False,
+ parameter_form=ServiceState(
+ title=Title("State when the board condition is other"),
+ prefill=DefaultValue(ServiceState.WARN),
+ ),
+ ),
+ "board_status_other_state": DictElement(
+ required=False,
+ parameter_form=ServiceState(
+ title=Title("State when the board status is other"),
+ prefill=DefaultValue(ServiceState.WARN),
+ ),
+ ),
+ },
+ )
+
+
+rule_spec_hp_proliant_da_cntlr = CheckParameters(
+ name="hp_proliant_da_cntlr",
+ title=Title("HPE ProLiant RAID controller"),
+ topic=Topic.STORAGE,
+ parameter_form=_make_form,
+ condition=HostAndItemCondition(item_title=Title("Controller index")),
+)
diff --git a/tests/unit/cmk/plugins/hp_proliant/agent_based/test_hp_proliant_da_cntlr.py b/tests/unit/cmk/plugins/hp_proliant/agent_based/test_hp_proliant_da_cntlr.py
index 99b446da284..8709c60cebe 100644
--- a/tests/unit/cmk/plugins/hp_proliant/agent_based/test_hp_proliant_da_cntlr.py
+++ b/tests/unit/cmk/plugins/hp_proliant/agent_based/test_hp_proliant_da_cntlr.py
@@ -8,7 +8,9 @@
from cmk.plugins.hp_proliant.agent_based.hp_proliant_da_cntlr import (
check_hp_proliant_da_cntlr,
ControllerID,
+ DEFAULT_PARAMETERS,
discovery_hp_proliant_da_cntlr,
+ Params,
parse_hp_proliant_da_cntlr,
)
@@ -21,13 +23,14 @@
def test_discovery() -> None:
+ # The all-zero placeholder row ("9") is a phantom controller and must not be
+ # discovered, otherwise its service is permanently UNKNOWN.
assert list(
discovery_hp_proliant_da_cntlr(section=parse_hp_proliant_da_cntlr(STRING_TABLE))
) == [
Service(item="0"),
Service(item="3"),
Service(item="6"),
- Service(item="9"),
]
@@ -73,7 +76,34 @@ def test_discovery() -> None:
def test_check(item: ControllerID, expected: list[Result]) -> None:
assert (
list(
- check_hp_proliant_da_cntlr(item=item, section=parse_hp_proliant_da_cntlr(STRING_TABLE))
+ check_hp_proliant_da_cntlr(
+ item=item,
+ params=DEFAULT_PARAMETERS,
+ section=parse_hp_proliant_da_cntlr(STRING_TABLE),
+ )
)
== expected
)
+
+
+def test_check_remap_other_state() -> None:
+ # Controller "6" reports Board-Condition = other (WARN by default); on
+ # Gen11 / iLO 6 this is normal for healthy controllers. Remapping the
+ # "other" board condition to OK clears the false WARN.
+ params: Params = {
+ **DEFAULT_PARAMETERS,
+ "board_condition_other_state": State.OK.value,
+ }
+ assert list(
+ check_hp_proliant_da_cntlr(
+ item="6",
+ params=params,
+ section=parse_hp_proliant_da_cntlr(STRING_TABLE),
+ )
+ ) == [
+ Result(
+ state=State.OK,
+ summary="Condition: ok, Board-Condition: other, Board-Status: enabled (Role: other, Model: 1, Slot: 6, Serial: PEYHN0ARCC307J)",
+ details="The instrument agent does not recognize the status of the controller. You may need to upgrade the instrument agent.",
+ )
+ ]