| #include "tlbmc/redfish/routes/sensor.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/bind_front.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/substitute.h" |
| #include "http_request.hpp" |
| #include "async_resp.hpp" |
| #include <nlohmann/json.hpp> |
| #include "json_utils.h" |
| #include "tlbmc/central_config/config.h" |
| #include "entity_common_config.pb.h" |
| #include "reading_range_config.pb.h" |
| #include "threshold_config.pb.h" |
| #include "topology_config.pb.h" |
| #include "tlbmc/redfish/app.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/url.h" |
| #include "resource.pb.h" |
| #include "sensor.pb.h" |
| #include "tlbmc/sensors/sensor.h" |
| #include "tlbmc/store/store.h" |
| #include "smart_router.h" |
| |
| namespace milotic_tlbmc::sensor { |
| |
| namespace { |
| constexpr std::string_view kHealthOk = "OK"; |
| constexpr std::string_view kHealthWarning = "Warning"; |
| constexpr std::string_view kHealthCritical = "Critical"; |
| |
| void HandleSensorCollection(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, |
| const std::string& chassis_id) { |
| std::string sensor_collection_url = |
| absl::Substitute("/redfish/v1/Chassis/$0/Sensors", chassis_id); |
| resp.SetKeyInJsonBody("/@odata.id", sensor_collection_url); |
| resp.SetKeyInJsonBody("/@odata.type", "#SensorCollection.SensorCollection"); |
| resp.SetKeyInJsonBody("/Name", "Sensors"); |
| resp.SetKeyInJsonBody("/Description", |
| "Collection of Sensors for this Chassis"); |
| |
| const Store& store = *app.GetStore(); |
| |
| std::vector<std::string> sensor_keys = |
| store.GetAllSensorKeysByConfigKey(chassis_id); |
| std::sort(sensor_keys.begin(), sensor_keys.end()); |
| nlohmann::json::array_t members; |
| for (const auto& key : sensor_keys) { |
| std::shared_ptr<const Sensor> sensor_ptr = |
| store.GetSensorByConfigKeyAndSensorKey(chassis_id, key); |
| if (sensor_ptr->GetSensorAttributesStatic().redfish_hidden()) { |
| continue; |
| } |
| nlohmann::json::object_t member; |
| member["@odata.id"] = absl::StrCat(sensor_collection_url, "/", key); |
| members.push_back(std::move(member)); |
| } |
| |
| resp.SetKeyInJsonBody("/Members@odata.count", members.size()); |
| resp.SetKeyInJsonBody("/Members", members); |
| } |
| |
| void HandleSensorCollectionAfterGbmcweb( |
| const RedfishApp& app, const ::crow::Request& request, |
| const std::shared_ptr<bmcweb::AsyncResp>& async_resp, |
| const std::string& chassis_id) { |
| nlohmann::json& json = async_resp->res.jsonValue; |
| nlohmann::json::array_t* members = |
| milotic::authz::GetValueAsMutableArray(json, "Members"); |
| if (members == nullptr) { |
| return; |
| } |
| std::size_t members_count = members->size(); |
| const Store& store = *app.GetStore(); |
| std::vector<std::string> sensor_keys = |
| store.GetAllSensorKeysByConfigKey(chassis_id); |
| int valid_sensors_count = 0; |
| std::string sensor_collection_url = |
| absl::Substitute("/redfish/v1/Chassis/$0/Sensors", chassis_id); |
| for (const auto& key : sensor_keys) { |
| std::shared_ptr<const Sensor> sensor_ptr = |
| store.GetSensorByConfigKeyAndSensorKey(chassis_id, key); |
| if (sensor_ptr == nullptr) { |
| continue; |
| } |
| if (sensor_ptr->GetSensorAttributesStatic().redfish_hidden()) { |
| continue; |
| } |
| if (GetTlbmcConfig() |
| .sensor_collector_module() |
| .sensor_collection_append_sub_module() |
| .per_sensor_append_mode() && |
| !sensor_ptr->GetSensorAttributesStatic() |
| .append_to_sensor_collection()) { |
| continue; |
| } |
| valid_sensors_count++; |
| nlohmann::json::object_t member; |
| member["@odata.id"] = absl::StrCat(sensor_collection_url, "/", key); |
| members->push_back(std::move(member)); |
| } |
| json["Members@odata.count"] = members_count + valid_sensors_count; |
| } |
| |
| void HandleSensor(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, const std::string& chassis_id, |
| const std::string& sensor_id) { |
| const Store& store = *app.GetStore(); |
| |
| std::shared_ptr<const Sensor> sensor = |
| store.GetSensorByConfigKeyAndSensorKey(chassis_id, sensor_id); |
| if (sensor == nullptr || |
| sensor->GetSensorAttributesStatic().redfish_hidden()) { |
| resp.SetToNotFound( |
| absl::StrFormat("Chassis %s's sensor %s not found in tlBMC Store", |
| chassis_id, sensor_id)); |
| return; |
| } |
| |
| nlohmann::json::json_pointer sensor_pointer(""); |
| FillResponseWithSensorData(sensor, sensor_pointer, resp, store); |
| } |
| |
| void HandleSensorPatch(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, const std::string& chassis_id, |
| const std::string& sensor_id) { |
| Store& store = *app.GetStore(); |
| std::shared_ptr<const Sensor> sensor = |
| store.GetSensorByConfigKeyAndSensorKey(chassis_id, sensor_id); |
| if (sensor == nullptr || |
| sensor->GetSensorAttributesStatic().redfish_hidden()) { |
| resp.SetToNotFound( |
| absl::StrFormat("Chassis %s's sensor %s not found in tlBMC Store", |
| chassis_id, sensor_id)); |
| return; |
| } |
| nlohmann::json body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (body.is_discarded()) { |
| resp.SetToBadRequest("Request body is not valid JSON"); |
| return; |
| } |
| |
| if (!body.contains("Reading")) { |
| resp.SetToBadRequest("Request does not contain 'Reading' field"); |
| return; |
| } |
| |
| SensorValue val; |
| if (sensor->GetSensorAttributesStatic().unit() == UNIT_COUNT) { |
| if (!body["Reading"].is_number_integer()) { |
| resp.SetToBadRequest("Reading must be an integer for count unit"); |
| return; |
| } |
| val.set_count(body["Reading"].get<int64_t>()); |
| } else { |
| if (!body["Reading"].is_number()) { |
| resp.SetToBadRequest("Reading must be a number"); |
| return; |
| } |
| val.set_reading(body["Reading"].get<double>()); |
| } |
| |
| absl::Status status = store.WriteToSensor(sensor_id, val); |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| |
| nlohmann::json success = {{{"@odata.type", "#Message.v1_1_1.Message"}, |
| {"Message", "The request completed successfully."}, |
| {"MessageArgs", nlohmann::json::array_t()}, |
| {"MessageId", "Base.1.13.0.Success"}, |
| {"MessageSeverity", "OK"}, |
| {"Resolution", "None"}}}; |
| resp.SetKeyInJsonBody("/@Message.ExtendedInfo", success); |
| resp.SetKeyInJsonBody("/@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, |
| "Sensors", sensor_id})); |
| } |
| |
| std::string_view GetOemSensorStateString(Status status) { |
| switch (status) { |
| case STATUS_STALE: |
| return "Stale"; |
| case STATUS_CREATION_FAILED: |
| return "CreationFailed"; |
| case STATUS_DRIVER_READ_ERROR: |
| return "DriverReadError"; |
| case STATUS_INVALID_DATA: |
| return "InvalidData"; |
| case STATUS_OTHER_ERROR: |
| return "OtherError"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| } // namespace |
| |
| void FillResponseWithSensorData( |
| const std::shared_ptr<const Sensor>& sensor, |
| const nlohmann::json::json_pointer& sensor_pointer, RedfishResponse& resp, |
| const Store& store) { |
| const SensorAttributesDynamic attributes_dynamic = |
| sensor->GetSensorAttributesDynamic(); |
| const Status status = attributes_dynamic.state().status(); |
| switch (status) { |
| case STATUS_READY: { |
| std::string_view health = kHealthOk; |
| double reading = sensor->GetSensorData()->reading(); |
| |
| for (const auto& config : |
| attributes_dynamic.thresholds().threshold_configs()) { |
| if (!config.has_value()) { |
| continue; |
| } |
| const auto type = config.type(); |
| const double value = config.value(); |
| |
| if ((type == THRESHOLD_TYPE_UPPER_CRITICAL && reading > value) || |
| (type == THRESHOLD_TYPE_LOWER_CRITICAL && reading < value)) { |
| health = kHealthCritical; |
| break; |
| } |
| |
| if ((type == THRESHOLD_TYPE_UPPER_NON_CRITICAL && reading > value) || |
| (type == THRESHOLD_TYPE_LOWER_NON_CRITICAL && reading < value)) { |
| health = kHealthWarning; |
| } |
| } |
| |
| resp.SetKeyInJsonBody(sensor_pointer / "Status" / "Health", health); |
| resp.SetKeyInJsonBody(sensor_pointer / "Status" / "State", "Enabled"); |
| |
| resp.SetKeyInJsonBody(sensor_pointer / "Reading", reading); |
| break; |
| } |
| case STATUS_CREATION_PENDING: |
| resp.SetKeyInJsonBody(sensor_pointer / "Status" / "State", "Absent"); |
| resp.SetKeyInJsonBody(sensor_pointer / "Reading", nullptr); |
| break; |
| default: { |
| // We will always return a sensor regardless of status. For unhealthy |
| // sensors, we will return a Disabled status and a detailed error message. |
| nlohmann::json error_message_json; |
| error_message_json["@odata.type"] = "#Message.v1_1_1.Message"; |
| error_message_json["MessageId"] = "Base.1.13.0.InternalError"; |
| error_message_json["Message"] = absl::StrFormat( |
| "Sensor %s is not ready in tlBMC Store: %s", sensor->GetKey(), |
| attributes_dynamic.state().status_message()); |
| resp.SetErrorMessage(error_message_json); |
| resp.SetKeyInJsonBody(sensor_pointer / "Status" / "State", "Disabled"); |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Status" / "Oem" / "Google" / "SensorState", |
| GetOemSensorStateString(status)); |
| resp.SetKeyInJsonBody(sensor_pointer / "Reading", nullptr); |
| break; |
| } |
| } |
| const SensorAttributesStatic& attributes_static = |
| sensor->GetSensorAttributesStatic(); |
| std::string chassis_id = |
| attributes_static.entity_common_config().board_config_key(); |
| std::string sensor_url = |
| absl::Substitute("/redfish/v1/Chassis/$0/Sensors/$1", chassis_id, |
| attributes_static.attributes().key()); |
| resp.SetKeyInJsonBody(sensor_pointer / "@odata.id", sensor_url); |
| resp.SetKeyInJsonBody(sensor_pointer / "@odata.type", |
| "#Sensor.v1_2_0.Sensor"); |
| std::string sensor_name = |
| GetTrimmedSensorName(attributes_static.attributes().key()); |
| resp.SetKeyInJsonBody(sensor_pointer / "Name", sensor_name); |
| resp.SetKeyInJsonBody(sensor_pointer / "Id", |
| attributes_static.attributes().key()); |
| resp.SetKeyInJsonBody(sensor_pointer / "Description", "Sensor"); |
| |
| if (!attributes_static.related_item().id().empty()) { |
| nlohmann::json::array_t related_items; |
| nlohmann::json::object_t related_item; |
| |
| switch (attributes_static.related_item().type()) { |
| case RESOURCE_TYPE_FAN: |
| related_item["@odata.id"] = CreateUrl( |
| {"redfish", "v1", "Chassis", chassis_id, "ThermalSubsystem", "Fans", |
| attributes_static.related_item().id()}); |
| break; |
| case RESOURCE_TYPE_BOARD: |
| related_item["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Chassis", |
| attributes_static.related_item().id()}); |
| break; |
| case RESOURCE_TYPE_PROCESSOR: { |
| std::string processor_redfish_id(attributes_static.related_item().id()); |
| // Resolve redfish_id from the parent board's topology if available. |
| absl::StatusOr<const TopologyConfigNode*> parent_board = |
| store.GetFruTopology(chassis_id); |
| if (parent_board.ok()) { |
| // Iterator type is google::protobuf::Map<std::string, |
| // std::string>::const_iterator. |
| auto redfish_it = (*parent_board) |
| ->redfish_processor_ids() |
| .find(attributes_static.related_item().id()); |
| if (redfish_it != (*parent_board)->redfish_processor_ids().end()) { |
| processor_redfish_id = redfish_it->second; |
| } |
| } |
| related_item["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Systems", |
| attributes_static.related_item().system_id(), |
| "Processors", processor_redfish_id}); |
| break; |
| } |
| case RESOURCE_TYPE_DIMM: { |
| related_item["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Systems", |
| attributes_static.related_item().system_id(), "Memory", |
| attributes_static.related_item().id()}); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| related_items.emplace_back(std::move(related_item)); |
| resp.SetKeyInJsonBody(sensor_pointer / "RelatedItem", related_items); |
| } |
| |
| std::string reading_units; |
| std::string reading_type; |
| |
| // Reference: https://redfish.dmtf.org/schemas/v1/Sensor.v1_10_0.json |
| switch (attributes_static.unit()) { |
| case UNIT_UNKNOWN: |
| break; |
| case UNIT_DEGREE_CELSIUS: |
| reading_type = "Temperature"; |
| reading_units = "Cel"; |
| break; |
| case UNIT_VOLT: |
| reading_type = "Voltage"; |
| reading_units = "V"; |
| break; |
| case UNIT_WATT: |
| reading_type = "Power"; |
| reading_units = "W"; |
| break; |
| case UNIT_AMPERE: |
| reading_type = "Current"; |
| reading_units = "A"; |
| break; |
| case UNIT_REVOLUTION_PER_MINUTE: |
| reading_type = "Rotational"; |
| reading_units = "RPM"; |
| break; |
| case UNIT_PERCENT: |
| reading_type = "Percent"; |
| reading_units = "%"; |
| break; |
| case UNIT_COUNT: |
| reading_type = "Count"; |
| reading_units = "Count"; |
| break; |
| default: |
| break; |
| } |
| if (!reading_type.empty()) { |
| resp.SetKeyInJsonBody(sensor_pointer / "ReadingType", reading_type); |
| } |
| if (!reading_units.empty()) { |
| resp.SetKeyInJsonBody(sensor_pointer / "ReadingUnits", reading_units); |
| } |
| resp.SetBodyToJson(); |
| if (attributes_static.has_reading_ranges()) { |
| for (const auto& reading_range : |
| attributes_static.reading_ranges().reading_range_configs()) { |
| if (reading_range.type() == READING_RANGE_TYPE_MAX) { |
| resp.SetKeyInJsonBody(sensor_pointer / "ReadingRangeMax", |
| reading_range.reading()); |
| } else if (reading_range.type() == READING_RANGE_TYPE_MIN) { |
| resp.SetKeyInJsonBody(sensor_pointer / "ReadingRangeMin", |
| reading_range.reading()); |
| } |
| } |
| } |
| |
| if (attributes_dynamic.has_thresholds()) { |
| for (const auto& threshold : |
| attributes_dynamic.thresholds().threshold_configs()) { |
| switch (threshold.type()) { |
| case THRESHOLD_TYPE_LOWER_CRITICAL: |
| if (threshold.has_value()) { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "LowerCritical" / "Reading", |
| threshold.value()); |
| } else { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "LowerCritical" / "Reading", |
| nullptr); |
| } |
| break; |
| case THRESHOLD_TYPE_LOWER_NON_CRITICAL: |
| if (threshold.has_value()) { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "LowerCaution" / "Reading", |
| threshold.value()); |
| } else { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "LowerCaution" / "Reading", |
| nullptr); |
| } |
| break; |
| case THRESHOLD_TYPE_UPPER_NON_CRITICAL: |
| if (threshold.has_value()) { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "UpperCaution" / "Reading", |
| threshold.value()); |
| } else { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "UpperCaution" / "Reading", |
| nullptr); |
| } |
| break; |
| case THRESHOLD_TYPE_UPPER_CRITICAL: |
| if (threshold.has_value()) { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "UpperCritical" / "Reading", |
| threshold.value()); |
| } else { |
| resp.SetKeyInJsonBody( |
| sensor_pointer / "Thresholds" / "UpperCritical" / "Reading", |
| nullptr); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| void RegisterRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleSensorCollection, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleSensor, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") |
| .methods(boost::beast::http::verb::patch)( |
| absl::bind_front(HandleSensorPatch, std::cref(app))); |
| } |
| |
| void RegisterRoutesAppendMode(RedfishApp& app) { |
| // Appends to the sensor collection returned by gBMCWeb |
| TLBMC_APPEND_GBMCWEB( |
| app.GetSmartRouter(), "/redfish/v1/Chassis/<str>/Sensors/", |
| boost::beast::http::verb::get, |
| absl::bind_front(HandleSensorCollectionAfterGbmcweb, std::cref(app))); |
| // Still register collection route for RedfishRoute-owning chassis |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleSensorCollection, std::cref(app))); |
| // Owns the sensor resource that tlBMC models. |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleSensor, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") |
| .methods(boost::beast::http::verb::patch)( |
| absl::bind_front(HandleSensorPatch, std::cref(app))); |
| } |
| |
| } // namespace milotic_tlbmc::sensor |