xref: /openbmc/openbmc-test-automation/lib/sensor_info_record.py (revision cb61ff0fd22543473630ba5a6e6d5714718efee0)
1"""
2This module provides functions for retrieving and validating sensor information
3from D-Bus and Redfish endpoints. It includes functionalities for:
4- Validating sensor threshold values according to IPMI specifications.
5- Checking sensor reading value lengths.
6- Converting sensor names to a 16-byte format as required by IPMI.
7- Creating a list of sensors that do not have a single threshold value.
8These functions support automated sensor validations as part of the Robot
9Framework tests in an OpenBMC environment.
10"""
11
12from robot.libraries.BuiltIn import BuiltIn
13
14
15def validate_threshold_values(sensor_threshold_values, sensor_id):
16    r"""
17    Validate sensor thresholds per IPMI spec:
18      Lower thresholds: lnr < lcr < lnc
19      Upper thresholds: unc > ucr > unr
20
21    Description of arguments:
22    sensor_threshold_values: Dictionary of threshold values.
23    sensor_id: Sensor identifier (e.g. "fan_1").
24    """
25    try:
26        # Validate lower thresholds
27        # lnr = Lower Non-Recoverable
28        # lcr = Lower Critical
29        # lnc = Lower Non-Critical
30        lnr = float(sensor_threshold_values["lnr"])
31        lcr = float(sensor_threshold_values["lcr"])
32        lnc = float(sensor_threshold_values["lnc"])
33        if not (lnr < lcr < lnc):
34            error_msg = (
35                f"{sensor_id}: Lower thresholds violate IPMI spec\n"
36                f"lnr={lnr} lcr={lcr} lnc={lnc}"
37            )
38            BuiltIn().fail(error_msg)
39    except Exception as e:
40        BuiltIn().fail(
41            f"- Error: {e}\n"
42            f"- Threshold Values:\n"
43            f"  • Lower Non-Recoverable (lnr): {lnr}\n"
44            f"  • Lower Critical (lcr): {lcr}\n"
45            f"  • Lower Non-Critical (lnc): {lnc}\n"
46        )
47
48    try:
49        # Validate upper thresholds
50        # unc = Upper Non-Critical
51        # ucr = Upper Critical
52        # unr = Upper Non-Recoverable
53        unc = float(sensor_threshold_values["unc"])
54        ucr = float(sensor_threshold_values["ucr"])
55        unr = float(sensor_threshold_values["unr"])
56        if not (unc > ucr > unr):
57            error_msg = (
58                f"{sensor_id}: Upper thresholds violate IPMI spec\n"
59                f"unc={unc} ucr={ucr} unr={unr}"
60            )
61            BuiltIn().fail(error_msg)
62    except Exception as e:
63        BuiltIn().fail(
64            f"- Error: {e}\n"
65            f"- Threshold Values:\n"
66            f"  • Upper Non-Critical (unc): {unc}\n"
67            f"  • Upper Critical (ucr): {ucr}\n"
68            f"  • Upper Non-Recoverable (unr): {unr}\n"
69        )
70
71
72def check_reading_value_length(sensor_reading, sensor_id, sensor_unit):
73    r"""
74    Validate sensor reading length per IPMI spec.
75
76    Description of arguments:
77    sensor_reading: Reading value (e.g. "1234.567").
78    sensor_id: Sensor identifier (e.g. "temp_ambient").
79    sensor_unit: Sensor unit (e.g. "RPM").
80    """
81    max_int_len = 6 if sensor_unit == "RPM" else 4
82    max_frac_len = 3 if sensor_unit == "RPM" else 4
83
84    if "." in sensor_reading:
85        integer_part, fractional_part = sensor_reading.split(".", 1)
86    else:
87        integer_part = sensor_reading
88        fractional_part = ""
89
90    if len(integer_part) > max_int_len:
91        BuiltIn().fail(
92            f"{sensor_id}: Integer part exceeds {max_int_len} digits "
93            f"({integer_part})"
94        )
95
96    if len(fractional_part) > max_frac_len:
97        BuiltIn().fail(
98            f"{sensor_id}: Fractional part exceeds {max_frac_len} digits "
99            f"({fractional_part})"
100        )
101
102
103def convert_sensor_name_as_per_ipmi_spec(sensor_name):
104    r"""
105    Convert sensor name to 16-byte IPMI-compliant format.
106
107    Description of arguments:
108    sensor_name: Original sensor name.
109
110    Example:
111    Input: "very_long_sensor_name_12345"
112    Output: "very_long_sensor" (16-byte truncated UTF-8)
113    """
114    READING_VALUE_BYTE_LIMIT = 16
115    encoded = sensor_name.encode("utf-8")
116    truncated = encoded[:READING_VALUE_BYTE_LIMIT]
117    padded = truncated.ljust(READING_VALUE_BYTE_LIMIT, b"\x00")
118    return padded.decode("utf-8", errors="ignore")
119
120
121def create_sensor_list_not_having_single_threshold(
122    ipmi_sensor_response, threshold_sensor_list
123):
124    r"""
125    Identify sensors with no valid thresholds.
126
127    Description of arguments:
128    ipmi_sensor_response: Raw IPMI sensor output.
129    threshold_sensor_list: List of expected threshold sensors.
130
131    Example IPMI response line:
132    "fan_1 | 1000 RPM | ok | 200.000 | 500.000 | 600.000 | 700.000 | "
133    "800.000 | 900.000 | na |"
134    - Splitting by "|" gives parts where `parts[4:10]` are thresholds:
135      [500.000, 600.000, 700.000, 800.000, 900.000, na]
136    - If any threshold is not "na", thresholds exist for that sensor.
137    """
138    sensor_ids_missing_threshold = []
139
140    for sensor_id in threshold_sensor_list:
141        thresholds_exist = False
142        for line in ipmi_sensor_response.splitlines():
143            if sensor_id in line:
144                parts = [p.strip() for p in line.split("|")]
145                if len(parts) >= 10:
146                    thresholds = parts[4:10]
147                    if any(t != "na" for t in thresholds):
148                        thresholds_exist = True
149                        break
150        if not thresholds_exist:
151            sensor_ids_missing_threshold.append(sensor_id)
152
153    return sensor_ids_missing_threshold
154