common/recipes-rest/rest-api/files/redfish_sensors.py (210 lines of code) (raw):
import typing as t
from functools import lru_cache
import aggregate_sensor
import pal
import redfish_chassis_helper
import rest_pal_legacy
from aiohttp import web
from common_utils import dumps_bytestr, common_force_async, parse_expand_level
from redfish_base import validate_keys
try:
fru_name_map = pal.pal_fru_name_map()
except Exception:
fru_name_map = {}
# controller for /redfish/v1/Chassis/{fru}/Sensors
async def get_redfish_sensors_handler(request: web.Request) -> web.Response:
expand_level = parse_expand_level(request)
server_name = request.match_info["fru_name"]
body = await get_redfish_sensors_for_server_name(server_name, expand_level)
return web.json_response(body, dumps=dumps_bytestr)
async def get_redfish_sensors_for_server_name(
server_name: str, expand_level: int
) -> t.Dict[str, t.Any]:
try:
fru_names = _get_fru_names(server_name)
except ValueError:
return web.json_response(
status=404,
)
if len(fru_names) == 0:
return web.json_response(
{"Error": "Invalid FRU {server_name}".format(server_name=server_name)},
dumps=dumps_bytestr,
status=400,
)
members_json = await _get_sensor_members(server_name, fru_names, expand_level > 0)
body = {
"@odata.type": "#SensorCollection.SensorCollection",
"Name": "Chassis sensors",
"Members@odata.count": len(members_json),
"Members": members_json,
"@odata.id": "/redfish/v1/Chassis/{server_name}/Sensors".format(
server_name=server_name
),
}
await validate_keys(body)
return body
# controller for /redfish/v1/Chassis/{fru}/Sensors/{fru_name}_{sensor_id}
async def get_redfish_sensor_handler(request: web.Request) -> web.Response:
server_name = request.match_info["fru_name"]
if redfish_chassis_helper.is_libpal_supported():
sensor_id_and_fru = request.match_info["sensor_id"]
target_fru_name = "_".join(sensor_id_and_fru.split("_")[:-1])
sensor_id = int(sensor_id_and_fru.split("_")[-1])
else:
target_fru_name = "BMC"
sensor_id = request.match_info["sensor_id"]
# This call could block the event loop too, but the risk of that is really low.
# This call takes ~0.2 seconds to run on average
# also this endpoint is not exercised in prod, as we use Sensors?$expand=1
sensor = _get_sensor(target_fru_name, sensor_id)
if sensor:
body = _render_sensor_body(server_name, sensor)
else:
return web.json_response(
{
"Error": "sensor not found fru_name={fru_name}, sensor_id={sensor_id}".format( # noqa: B950
fru_name=server_name, sensor_id=sensor_id
)
},
dumps=dumps_bytestr,
status=404,
)
await validate_keys(body)
return web.json_response(body, dumps=dumps_bytestr)
@common_force_async
def _get_sensor_members(parent_resource: str, fru_names: t.List[str], expand: bool):
members_json = []
for fru in fru_names:
if redfish_chassis_helper.is_libpal_supported():
fru_id = fru_name_map[fru]
for sensor_id in pal.pal_get_fru_sensor_list(fru_id):
sensor = _get_sensor(fru_name=fru, sensor_id=sensor_id)
if sensor:
child = _render_sensor(parent_resource, sensor, expand)
members_json.append(child)
else:
sensors = redfish_chassis_helper.get_older_fboss_sensor_details(fru)
for sensor in sensors:
child = _render_sensor(parent_resource, sensor, expand)
members_json.append(child)
if parent_resource == "1":
# we expose aggregate sensors under the Chassis, so only do this
# if parent_resource is 1
for sensor_id in range(aggregate_sensor.aggregate_sensor_count()):
sensor = _get_sensor("aggregate", sensor_id)
if sensor:
child = _render_sensor(parent_resource, sensor, expand)
members_json.append(child)
return members_json
def _get_sensor(
fru_name: str, sensor_id: t.Union[int, str]
) -> t.Optional[redfish_chassis_helper.SensorDetails]:
if fru_name == "aggregate":
sensor = redfish_chassis_helper.get_aggregate_sensor(sensor_id) # type: ignore
return sensor
else:
if redfish_chassis_helper.is_libpal_supported():
fru_id = fru_name_map[fru_name]
sensor = redfish_chassis_helper.get_pal_sensor(
fru_name, fru_id, int(sensor_id)
)
return sensor
else:
# i know this is ugly,
# sensors.py does not support accessing individual sensors by id.
sensors = redfish_chassis_helper.get_older_fboss_sensor_details(fru_name)
for candidate in sensors:
if (
candidate.sensor_name.replace("/", "_").replace(" ", "")
== sensor_id
):
return candidate
return None
def _render_sensor(
parent_resource: str, sensor: redfish_chassis_helper.SensorDetails, expand: bool
) -> t.Dict[str, t.Any]:
if expand:
child = _render_sensor_body(parent_resource, sensor)
else:
child = {"@odata.id": _render_sensor_uri(parent_resource, sensor)}
return child
def _render_sensor_body(
parent_resource: str, sensor: redfish_chassis_helper.SensorDetails
) -> t.Dict[str, t.Any]:
if sensor.reading == redfish_chassis_helper.SAD_SENSOR:
status_val = {"State": "UnavailableOffline", "Health": "Critical"}
else:
status_val = {"State": "Enabled", "Health": "OK"}
if sensor.sensor_thresh:
threshold_json = {
"UpperCaution": {"Reading": int(sensor.sensor_thresh.unc_thresh)},
"UpperCritical": {"Reading": int(sensor.sensor_thresh.ucr_thresh)},
"UpperFatal": {"Reading": int(sensor.sensor_thresh.unr_thresh)},
"LowerCaution": {"Reading": int(sensor.sensor_thresh.lnc_thresh)},
"LowerCritical": {"Reading": int(sensor.sensor_thresh.lcr_thresh)},
"LowerFatal": {"Reading": int(sensor.sensor_thresh.lnr_thresh)},
}
else:
threshold_json = {}
body = {
"@odata.type": "#Sensor.v1_2_0.Sensor",
"Id": str(sensor.sensor_number),
"Name": sensor.sensor_name,
"Oem": {},
"PhysicalContext": _get_phy_context(sensor.sensor_name),
"Status": status_val,
"Reading": sensor.reading,
"ReadingUnits": sensor.sensor_unit,
"ReadingRangeMin": -99999,
"ReadingRangeMax": 99999,
"Thresholds": threshold_json,
"@odata.id": _render_sensor_uri(parent_resource, sensor),
}
return body
def _render_sensor_uri(
parent_resource: str, sensor: redfish_chassis_helper.SensorDetails
) -> str:
if redfish_chassis_helper.is_libpal_supported():
return "/redfish/v1/Chassis/{parent_resource}/Sensors/{fru_name}_{sensor_id}".format( # noqa: B950
parent_resource=parent_resource,
fru_name=sensor.fru_name,
sensor_id=sensor.sensor_number,
)
else:
return "/redfish/v1/Chassis/{parent_resource}/Sensors/{sensor_name}".format(
parent_resource=parent_resource,
sensor_name=sensor.sensor_name.replace("/", "_").replace(" ", ""),
)
@lru_cache()
def _get_phy_context(sensor_name: str) -> str:
# valid physical contexts:
# ['Room', 'Intake', 'Exhaust', 'LiquidInlet', 'LiquidOutlet', 'Front', 'Back', # noqa: B950
# 'Upper', 'Lower', 'CPU', 'CPUSubsystem', 'GPU', 'GPUSubsystem', 'FPGA', 'Accelerator', # noqa: B950
# 'ASIC', 'Backplane', 'SystemBoard', 'PowerSupply', 'PowerSubsystem', 'VoltageRegulator', # noqa: B950
# 'Rectifier', 'StorageDevice', 'NetworkingDevice', 'ComputeBay', 'StorageBay', 'NetworkBay', # noqa: B950
# 'ExpansionBay', 'PowerSupplyBay', 'Memory', 'MemorySubsystem', 'Chassis', 'Fan', # noqa: B950
# 'CoolingSubsystem', 'Motor', 'Transformer', 'ACUtilityInput', 'ACStaticBypassInput', # noqa: B950
# 'ACMaintenanceBypassInput', 'DCBus', 'ACOutput', 'ACInput', 'TrustedModule', 'Board', 'Transceiver'], # noqa: B950
if "INLET" in sensor_name:
return "Intake"
if "OUTLET" in sensor_name:
return "Exhaust"
if "FAN" in sensor_name:
return "Fan"
if "DIMM" in sensor_name:
return "Memory"
if "SOC" in sensor_name:
return "CPU"
if "NVMe" in sensor_name:
return "StorageDevice"
return "Chassis"
@lru_cache()
def _get_fru_names(server_name: str) -> t.List[str]:
fru_names = []
if server_name == "1": # Chassis/1 represents the Chassis on all platforms
if redfish_chassis_helper.is_libpal_supported():
return redfish_chassis_helper.get_single_sled_frus()
else:
return ["BMC"]
elif "server" in server_name:
if rest_pal_legacy.pal_get_num_slots() > 1:
slot_name = server_name.replace("server", "slot")
fru_names = [slot_name] # we expose slot1-4 as server 1-4 in our routes.
for fru_name in fru_name_map.keys():
if slot_name in fru_name and "-exp" in fru_name:
fru_names.append(fru_name)
else:
raise ValueError(
"{server_name} server_name is invalid for slingle slot servers".format(
server_name=server_name
)
)
elif "accelerator" in server_name:
asic_idx = int(server_name.replace("accelerator", ""))
accelerators = redfish_chassis_helper._get_accelerator_list()
return [accelerators[asic_idx]]
return fru_names