blob: a46d9e361b2cc442e716937b369c43a77db3f766 [file]
#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