Skip to content

Commit 224b4bb

Browse files
nightwatcher74Frans Fuerst
authored andcommitted
16926 FIX Raritan PDU residual current: crash when only a critical threshold is reported
SUP-29960 closes: #935 Change-Id: I41b178720191f1f0183ff09446b58837431164b4
1 parent ddc7005 commit 224b4bb

4 files changed

Lines changed: 314 additions & 115 deletions

File tree

.werks/16926.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[//]: # (werk v3)
2+
# Raritan PDU residual current: fix crash when only a critical threshold is reported
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-07-21T12:56:00.977993+00:00
7+
version | 2.5.0p10
8+
class | fix
9+
edition | community
10+
component | checks
11+
level | 1
12+
compatible | yes
13+
14+
Raritan PDUs report residual current thresholds independently: by default a
15+
device may send only a critical level (e.g. 0.030 A) and no warning level.
16+
17+
Previously the *Residual Current* service crashed in this situation because the
18+
missing warning threshold was passed on as an unusable value.
19+
20+
The check now handles a partially configured threshold set. If only one of the
21+
warning or critical levels is present, the reported level is used for both, so
22+
that an alert is still raised instead of the check failing.
23+

cmk/plugins/raritan/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,26 @@
1212
# (c) 2024 SWR
1313
# @author Frank Baier <frank.baier@swr.de>
1414
#
15+
from cmk.plugins.raritan.lib import (
16+
InletDeviceCapabilities,
17+
InletPoleCapabilities,
18+
InletSensorEnabledThresholds,
19+
PDU,
20+
RaritanData,
21+
Sensor,
22+
SensorValues,
23+
TYPE_MAPPING,
24+
UNIT_MAPPING,
25+
)
26+
27+
__all__ = [
28+
"InletSensorEnabledThresholds",
29+
"InletDeviceCapabilities",
30+
"InletPoleCapabilities",
31+
"RaritanData",
32+
"SensorValues",
33+
"Sensor",
34+
"PDU",
35+
"TYPE_MAPPING",
36+
"UNIT_MAPPING",
37+
]

cmk/plugins/raritan/agent_based/raritan_px2_residual_operating_current.py

Lines changed: 33 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717

1818
from collections.abc import Callable, Sequence
19-
from dataclasses import dataclass, field
2019
from typing import (
2120
NotRequired,
2221
TypedDict,
@@ -41,65 +40,17 @@
4140
State,
4241
StringByteTable,
4342
)
44-
45-
TYPE_MAPPING = {
46-
"1": ("current", "RMS"),
47-
"2": ("peak", "Peak"),
48-
"3": ("unbalanced", "Unbalanced"),
49-
"4": ("voltage", "RMS"),
50-
"5": ("power", "Active"),
51-
"6": ("appower", "Apparent"),
52-
# power factor is defined as the ratio of the real power flowing
53-
# to the load to the apparent power
54-
"7": ("power_factor", "Power Factor"),
55-
"8": ("energy", "Active"),
56-
"9": ("energy", "Apparent"),
57-
"10": ("temp", ""),
58-
"11": ("humidity", ""),
59-
"12": ("airflow", ""),
60-
"13": ("pressure_pa", "Air"),
61-
"14": ("binary", "On/Off"),
62-
"15": ("binary", "Trip"),
63-
"16": ("binary", "Vibration"),
64-
"17": ("binary", "Water Detector"),
65-
"18": ("binary", "Smoke Detector"),
66-
"19": ("binary", ""),
67-
"20": ("binary", "Contact"),
68-
"21": ("fanspeed", ""),
69-
"26": ("residual_current", "Residual Current"),
70-
"30": ("", "Other"),
71-
"31": ("", "None"),
72-
}
73-
74-
UNIT_MAPPING = {
75-
"-1": "",
76-
"0": " Other",
77-
"1": " V",
78-
"2": " A",
79-
"3": " W",
80-
"4": " VA",
81-
"5": " Wh",
82-
"6": " VAh",
83-
# for dev_unit in check_temperature
84-
"7": "c",
85-
"8": " hz",
86-
"9": "%",
87-
"10": " m/s",
88-
"11": " Pa",
89-
# 1 psi = 6894,757293168 Pa
90-
"12": " psi",
91-
"13": " g",
92-
# for dev_unit in check_temperature
93-
"14": "f",
94-
"15": " ft",
95-
"16": " inch",
96-
"17": " cm",
97-
"18": " m",
98-
"19": " RPM",
99-
}
100-
101-
102-
RESIDUAL_BITMASK = 0b01000000
43+
from cmk.plugins.raritan import (
44+
InletDeviceCapabilities,
45+
InletPoleCapabilities,
46+
InletSensorEnabledThresholds,
47+
PDU,
48+
RaritanData,
49+
Sensor,
50+
SensorValues,
51+
TYPE_MAPPING,
52+
UNIT_MAPPING,
53+
)
10354

10455
_RENDER_FUNCTION_AND_UNIT: dict[str, tuple[Callable | None, str]] = {
10556
"%": (
@@ -119,46 +70,6 @@ class Params(TypedDict):
11970
residual_levels: NotRequired[NoLevelsT | FixedLevelsT]
12071

12172

122-
@dataclass(frozen=True, kw_only=True)
123-
class PDU:
124-
pdu_index: str
125-
label: str
126-
name: str
127-
plug: str
128-
pole_count: str
129-
rated_voltage: str
130-
rated_current: str
131-
rated_frequency: str
132-
rated_va: str
133-
plug_descriptor: str
134-
enable_state: str
135-
device_capabilities: list[int]
136-
pole_capabilities: list[int]
137-
138-
139-
@dataclass(frozen=True, kw_only=True)
140-
class SensorValues:
141-
sensor_value: float
142-
sensor_upper_crit: float
143-
sensor_upper_warn: float
144-
145-
146-
@dataclass(frozen=True, kw_only=True)
147-
class Sensor:
148-
availability: str
149-
sensor_name: str
150-
sensor_type: str
151-
sensor_values: SensorValues
152-
sensor_unit: str
153-
sensor_thresholds: list[int]
154-
155-
156-
@dataclass(kw_only=True)
157-
class RaritanData:
158-
pdu: PDU
159-
sensors: dict[str, dict[str, Sensor]] = field(default_factory=dict)
160-
161-
16273
def _parse_pdu_data(raw_pdu_data: StringByteTable) -> PDU:
16374
pdu_data = raw_pdu_data[0]
16475

@@ -172,8 +83,8 @@ def _parse_pdu_data(raw_pdu_data: StringByteTable) -> PDU:
17283
rated_current=str(pdu_data[6]),
17384
rated_frequency=str(pdu_data[7]),
17485
rated_va=str(pdu_data[8]),
175-
device_capabilities=pdu_data[9] if isinstance(pdu_data[9], list) else [int(pdu_data[9])],
176-
pole_capabilities=pdu_data[10] if isinstance(pdu_data[10], list) else [int(pdu_data[10])],
86+
device_capabilities=InletDeviceCapabilities.from_snmp_bits(pdu_data[9]),
87+
pole_capabilities=InletPoleCapabilities.from_snmp_bits(pdu_data[10]),
17788
plug_descriptor=str(pdu_data[11]),
17889
enable_state=str(pdu_data[12]),
17990
)
@@ -235,9 +146,7 @@ def _parse_sensor_data(sensor_data: StringByteTable) -> dict[str, dict[str, Sens
235146
decimal_digits_int,
236147
),
237148
sensor_unit=sensor_unit.strip(),
238-
sensor_thresholds=enabled_thresholds
239-
if isinstance(enabled_thresholds, list)
240-
else [int(enabled_thresholds)],
149+
sensor_thresholds=InletSensorEnabledThresholds.from_snmp_bits(enabled_thresholds),
241150
)
242151

243152
return sensors
@@ -250,9 +159,9 @@ def parse_raritan_inlet_sensors(
250159
return None
251160

252161
section = RaritanData(pdu=_parse_pdu_data(raw_pdu_data))
253-
if int(section.pdu.device_capabilities[3]) & RESIDUAL_BITMASK:
162+
if section.pdu.device_capabilities.residualCurrent:
254163
section.sensors.update(_parse_sensor_data(string_table[1]))
255-
if int(section.pdu.pole_capabilities[3]) & RESIDUAL_BITMASK:
164+
if section.pdu.pole_capabilities.residualCurrent:
256165
section.sensors.update(_parse_sensor_data(string_table[2]))
257166

258167
return section
@@ -346,16 +255,25 @@ def _create_levels(
346255
params: Params,
347256
sensor_data: Sensor,
348257
) -> NoLevelsT | FixedLevelsT:
349-
thresholds = sensor_data.sensor_thresholds[0]
350-
has_warn = thresholds & 0b00100000 and sensor_data.sensor_values.sensor_upper_warn != 0
351-
has_crit = thresholds & 0b00010000 and sensor_data.sensor_values.sensor_upper_crit != 0
352258

353-
if has_warn or has_crit:
354-
levels_upper_warn = sensor_data.sensor_values.sensor_upper_warn if has_warn else None
355-
levels_upper_crit = sensor_data.sensor_values.sensor_upper_crit if has_crit else None
356-
return ("fixed", (levels_upper_warn, levels_upper_crit))
259+
has_warn = (
260+
sensor_data.sensor_thresholds.upperWarning
261+
and sensor_data.sensor_values.sensor_upper_warn != 0
262+
)
263+
has_crit = (
264+
sensor_data.sensor_thresholds.upperCritical
265+
and sensor_data.sensor_values.sensor_upper_crit != 0
266+
)
357267

358-
return params.get("residual_levels", ("no_levels", None))
268+
if params.get("residual_levels", False):
269+
levels_upper = params.get("residual_levels", ("no_levels", None))
270+
elif has_warn or has_crit:
271+
levels_upper_warn = sensor_data.sensor_values.sensor_upper_warn if has_warn else has_crit
272+
levels_upper_crit = sensor_data.sensor_values.sensor_upper_crit if has_crit else has_warn
273+
levels_upper = ("fixed", (levels_upper_warn, levels_upper_crit))
274+
else:
275+
levels_upper = ("no_levels", None)
276+
return levels_upper
359277

360278

361279
def _check_missing_levels(

0 commit comments

Comments
 (0)