From 80608f0d72da62426bb00e03a42fbf5daed931c9 Mon Sep 17 00:00:00 2001 From: Krzysztof Grobelny Date: Tue, 13 Apr 2021 13:00:18 +0000 Subject: [PATCH] Add support for MetricDefinition scheme Added MetricDefinition node to Redfish code. Now user is able to list all available metrics in OpenBMC that are supported by Telemetry service. Metrics are grouped by reading type. MetricDefinitions contains all physical sensors supported by redfish, algorithm iterates through all chassis and collects results for each node available in that chassis (Power, Thermal, Sensors). When https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/40169 will be merge it will be possible to optimize this algorithm to only get sensors from Sensors node. Currently Sensors node doesn't contain all available sensors. Tested: - MetricDefinitions response is filled with existing sensors, it works with and without Telemetry service - Validated a presence of MetricDefinition members and its attributes - Successfully passed RedfishServiceValidator.py using witherspoon image on QEMU - Tested using following GET,POST requests GET /redfish/v1/TelemetryService/MetricDefinitions { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions", "@odata.type": "#MetricDefinitionCollection.MetricDefinitionCollection", "Members": [ { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Rotational" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Percent" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Temperature" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Power" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/AirFlow" } ], "Members@odata.count": 5, "Name": "Metric Definition Collection" } GET /redfish/v1/TelemetryService/MetricDefinitions/Rotational { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Rotational", "@odata.type": "#MetricDefinition.v1_0_3.MetricDefinition", "Id": "Rotational", "Implementation": "PhysicalSensor", "IsLinear": true, "MetricDataType": "Decimal", "MetricProperties": [ "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/0/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/1/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/2/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/3/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/4/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/5/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/6/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/7/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/8/Reading", "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/9/Reading" ], "MetricType": "Numeric", "Name": "Rotational", "Units": "RPM" } POST redfish/v1/TelemetryService/MetricReportDefinitions, body: { "Id": "TestReport", "Metrics": [ { "MetricId": "TestMetric", "MetricProperties": [ "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/3/Reading", ] } ], "MetricReportDefinitionType": "OnRequest", "ReportActions": [ "RedfishEvent", "LogToMetricReportsCollection" ] } { "@Message.ExtendedInfo": [ { "@odata.type": "#Message.v1_1_1.Message", "Message": "The resource has been created successfully", "MessageArgs": [], "MessageId": "Base.1.8.1.Created", "MessageSeverity": "OK", "Resolution": "None" } ] } Signed-off-by: Wludzik, Jozef Signed-off-by: Krzysztof Grobelny Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 --- redfish-core/include/redfish.hpp | 3 + .../include/utils/get_chassis_names.hpp | 58 ++++ .../include/utils/telemetry_utils.hpp | 2 + redfish-core/lib/metric_definition.hpp | 258 ++++++++++++++++++ redfish-core/lib/telemetry_service.hpp | 2 + 5 files changed, 323 insertions(+) create mode 100644 redfish-core/include/utils/get_chassis_names.hpp create mode 100644 redfish-core/lib/metric_definition.hpp diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index 1c7b695..9983b88 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -26,6 +26,7 @@ #include "../lib/managers.hpp" #include "../lib/memory.hpp" #include "../lib/message_registries.hpp" +#include "../lib/metric_definition.hpp" #include "../lib/metric_report.hpp" #include "../lib/metric_report_definition.hpp" #include "../lib/network_protocol.hpp" @@ -199,6 +200,8 @@ class RedfishService requestRoutesMetricReportDefinition(app); requestRoutesMetricReportCollection(app); requestRoutesMetricReport(app); + requestRoutesMetricDefinitionCollection(app); + requestRoutesMetricDefinition(app); } }; diff --git a/redfish-core/include/utils/get_chassis_names.hpp b/redfish-core/include/utils/get_chassis_names.hpp new file mode 100644 index 0000000..0276b6f --- /dev/null +++ b/redfish-core/include/utils/get_chassis_names.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include + +namespace redfish +{ + +namespace utils +{ + +template +inline void getChassisNames(F&& cb) +{ + const std::array interfaces = { + "xyz.openbmc_project.Inventory.Item.Board", + "xyz.openbmc_project.Inventory.Item.Chassis"}; + + crow::connections::systemBus->async_method_call( + [callback = std::move(cb)](const boost::system::error_code ec, + const std::vector& chassis) { + std::vector chassisNames; + + if (ec) + { + callback(ec, chassisNames); + return; + } + + chassisNames.reserve(chassis.size()); + for (const std::string& path : chassis) + { + sdbusplus::message::object_path dbusPath = path; + std::string name = dbusPath.filename(); + if (name.empty()) + { + callback(boost::system::errc::make_error_code( + boost::system::errc::invalid_argument), + chassisNames); + return; + } + chassisNames.emplace_back(std::move(name)); + } + + callback(ec, chassisNames); + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", + "/xyz/openbmc_project/inventory", 0, interfaces); +} + +} // namespace utils + +} // namespace redfish diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp index 5872350..1b4f75d 100644 --- a/redfish-core/include/utils/telemetry_utils.hpp +++ b/redfish-core/include/utils/telemetry_utils.hpp @@ -10,6 +10,8 @@ namespace telemetry constexpr const char* service = "xyz.openbmc_project.Telemetry"; constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; +constexpr const char* metricDefinitionUri = + "/redfish/v1/TelemetryService/MetricDefinitions/"; constexpr const char* metricReportDefinitionUri = "/redfish/v1/TelemetryService/MetricReportDefinitions/"; constexpr const char* metricReportUri = diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp new file mode 100644 index 0000000..019168b --- /dev/null +++ b/redfish-core/lib/metric_definition.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include "async_resp.hpp" +#include "sensors.hpp" +#include "utils/get_chassis_names.hpp" +#include "utils/telemetry_utils.hpp" + +#include + +namespace redfish +{ + +namespace telemetry +{ + +bool containsOdata(const nlohmann::json& json, const std::string& odataId) +{ + const auto it = std::find_if( + json.begin(), json.end(), [&odataId](const nlohmann::json& item) { + auto kt = item.find("@odata.id"); + if (kt == item.end()) + { + return false; + } + const std::string* value = kt->get_ptr(); + if (!value) + { + return false; + } + return *value == odataId; + }); + + return it != json.end(); +} + +void addMembers(crow::Response& res, + const boost::container::flat_map& el) +{ + for (const auto& [_, dbusSensor] : el) + { + sdbusplus::message::object_path path(dbusSensor); + sdbusplus::message::object_path parentPath = path.parent_path(); + const std::string type = parentPath.filename(); + + if (type.empty()) + { + BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " + << dbusSensor; + continue; + } + + nlohmann::json& members = res.jsonValue["Members"]; + + const std::string odataId = + std::string(telemetry::metricDefinitionUri) + + sensors::toReadingType(type); + + if (!containsOdata(members, odataId)) + { + members.push_back({{"@odata.id", odataId}}); + } + + res.jsonValue["Members@odata.count"] = members.size(); + } +} + +template +inline void mapRedfishUriToDbusPath(Callback&& callback) +{ + utils::getChassisNames([callback = std::move(callback)]( + boost::system::error_code ec, + const std::vector& chassisNames) { + if (ec) + { + BMCWEB_LOG_ERROR << "getChassisNames error: " << ec.value(); + callback(ec, {}); + return; + } + + auto handleRetrieveUriToDbusMap = + [callback = std::move(callback)]( + const boost::beast::http::status status, + const boost::container::flat_map& + uriToDbus) { + if (status != boost::beast::http::status::ok) + { + BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " + "sensors map with err " + << static_cast(status); + callback(boost::system::errc::make_error_code( + boost::system::errc::io_error), + {}); + return; + } + + callback(boost::system::errc::make_error_code( + boost::system::errc::success), + uriToDbus); + }; + + for (const std::string& chassisName : chassisNames) + { + for (const auto& [sensorNode, dbusPaths] : sensors::dbus::paths) + { + retrieveUriToDbusMap(chassisName, sensorNode.data(), + handleRetrieveUriToDbusMap); + } + } + }); +} + +} // namespace telemetry + +inline void requestRoutesMetricDefinitionCollection(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions/") + .privileges(privileges::getTelemetryService) + .methods(boost::beast::http::verb::get)( + [](const crow::Request&, + const std::shared_ptr& asyncResp) { + telemetry::mapRedfishUriToDbusPath( + [asyncResp](boost::system::error_code ec, + const boost::container::flat_map< + std::string, std::string>& uriToDbus) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR + << "mapRedfishUriToDbusPath error: " + << ec.value(); + return; + } + + telemetry::addMembers(asyncResp->res, uriToDbus); + }); + + asyncResp->res.jsonValue["@odata.type"] = + "#MetricDefinitionCollection." + "MetricDefinitionCollection"; + asyncResp->res.jsonValue["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; + asyncResp->res.jsonValue["Name"] = + "Metric Definition Collection"; + asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); + asyncResp->res.jsonValue["Members@odata.count"] = 0; + }); +} + +namespace telemetry +{ + +bool isSensorIdSupported(std::string_view readingType) +{ + for (const std::pair>& + typeToPaths : sensors::dbus::paths) + { + for (const char* supportedPath : typeToPaths.second) + { + if (readingType == + sensors::toReadingType( + sdbusplus::message::object_path(supportedPath).filename())) + { + return true; + } + } + } + return false; +} + +void addMetricProperty( + bmcweb::AsyncResp& asyncResp, const std::string& readingType, + const boost::container::flat_map& el) +{ + nlohmann::json& metricProperties = + asyncResp.res.jsonValue["MetricProperties"]; + + for (const auto& [redfishSensor, dbusSensor] : el) + { + std::string sensorId; + if (dbus::utility::getNthStringFromPath(dbusSensor, 3, sensorId)) + { + if (sensors::toReadingType(sensorId) == readingType) + { + metricProperties.push_back(redfishSensor); + } + } + } +} + +inline const char* readingTypeToReadingUnits(const std::string& readingType) +{ + for (const auto& [node, paths] : sensors::dbus::paths) + { + for (const char* path : paths) + { + const sdbusplus::message::object_path sensorPath = + sdbusplus::message::object_path(path); + if (sensors::toReadingType(sensorPath.filename()) == readingType) + { + return sensors::toReadingUnits(sensorPath.filename()); + } + } + } + return ""; +} + +} // namespace telemetry + +inline void requestRoutesMetricDefinition(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions//") + .privileges(privileges::getTelemetryService) + .methods(boost::beast::http::verb::get)( + [](const crow::Request&, + const std::shared_ptr& asyncResp, + const std::string& readingType) { + if (!telemetry::isSensorIdSupported(readingType)) + { + messages::resourceNotFound(asyncResp->res, + "MetricDefinition", readingType); + return; + } + + telemetry::mapRedfishUriToDbusPath( + [asyncResp, + readingType](boost::system::error_code ec, + const boost::container::flat_map< + std::string, std::string>& uriToDbus) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR + << "mapRedfishUriToDbusPath error: " + << ec.value(); + return; + } + + asyncResp->res.jsonValue["Id"] = readingType; + asyncResp->res.jsonValue["Name"] = readingType; + asyncResp->res.jsonValue["@odata.id"] = + telemetry::metricDefinitionUri + readingType; + asyncResp->res.jsonValue["@odata.type"] = + "#MetricDefinition.v1_0_3.MetricDefinition"; + asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; + asyncResp->res.jsonValue["MetricType"] = "Numeric"; + asyncResp->res.jsonValue["IsLinear"] = true; + asyncResp->res.jsonValue["Implementation"] = + "PhysicalSensor"; + asyncResp->res.jsonValue["Units"] = + telemetry::readingTypeToReadingUnits(readingType); + + telemetry::addMetricProperty(*asyncResp, readingType, + uriToDbus); + }); + }); +} + +} // namespace redfish diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp index ad86d5c..c4962e9 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp @@ -29,6 +29,8 @@ inline void requestRoutesTelemetryService(App& app) "/redfish/v1/TelemetryService/MetricReportDefinitions"; asyncResp->res.jsonValue["MetricReports"]["@odata.id"] = "/redfish/v1/TelemetryService/MetricReports"; + asyncResp->res.jsonValue["MetricDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; crow::connections::systemBus->async_method_call( [asyncResp]( -- 2.25.1