diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch new file mode 100644 index 000000000..f5226fe6e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch @@ -0,0 +1,619 @@ +From 32e557279450226ed9c06312649d90b802f3d4c5 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +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 BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM will be enabled by +default (meson option redfish-new-powersubsystem-thermalsubsystem) 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/Fan_Pwm" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostCpuUtilization" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostMemoryBandwidthUtilization" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostPciBandwidthUtilization" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Inlet_BRD_Temp" + }, + { + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Left_Rear_Board_Temp" + } + ], + "Members@odata.count": 7, + "Name": "Metric Definition Collection" +} + +GET /redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach +{ + "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach", + "@odata.type": "#MetricDefinition.v1_0_3.MetricDefinition", + "Id": "Fan_Tach", + "IsLinear": true, + "MaxReadingRange": 25000.0, + "MetricDataType": "Decimal", + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/1/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/2/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/3/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/4/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/5/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/6/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/7/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/8/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/9/Reading" + ], + "MetricType": "Gauge", + "MinReadingRange": 0.0, + "Name": "Fan_Tach", + "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 <jozef.wludzik@intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +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 | 368 ++++++++++++++++++ + redfish-core/lib/telemetry_service.hpp | 3 +- + 5 files changed, 433 insertions(+), 1 deletion(-) + 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 0a97150..67c5af2 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" +@@ -200,6 +201,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/dbus_singleton.hpp> ++ ++#include <array> ++#include <string> ++#include <vector> ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++template <typename F> ++inline void getChassisNames(F&& cb) ++{ ++ const std::array<const char*, 2> 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<std::string>& chassis) { ++ std::vector<std::string> 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..347c297 +--- /dev/null ++++ b/redfish-core/lib/metric_definition.hpp +@@ -0,0 +1,368 @@ ++#pragma once ++ ++#include "async_resp.hpp" ++#include "sensors.hpp" ++#include "utils/get_chassis_names.hpp" ++#include "utils/telemetry_utils.hpp" ++ ++#include <registries/privilege_registry.hpp> ++ ++namespace redfish ++{ ++ ++namespace telemetry ++{ ++ ++struct ValueVisitor ++{ ++ ValueVisitor(boost::system::error_code& ec) : ec(ec) ++ {} ++ ++ template <class T> ++ double operator()(T value) const ++ { ++ return static_cast<double>(value); ++ } ++ ++ double operator()(std::monostate) const ++ { ++ ec = boost::system::errc::make_error_code( ++ boost::system::errc::invalid_argument); ++ return double{}; ++ } ++ ++ boost::system::error_code& ec; ++}; ++ ++inline void getReadingRange( ++ const std::string& service, const std::string& path, ++ const std::string& property, ++ std::function<void(boost::system::error_code, double)> callback) ++{ ++ crow::connections::systemBus->async_method_call( ++ [callback = std::move(callback)]( ++ boost::system::error_code ec, ++ const std::variant<std::monostate, double, uint64_t, int64_t, ++ uint32_t, int32_t, uint16_t, int16_t>& ++ valueVariant) { ++ if (ec) ++ { ++ callback(ec, double{}); ++ return; ++ } ++ ++ const double value = std::visit(ValueVisitor(ec), valueVariant); ++ ++ callback(ec, value); ++ }, ++ service, path, "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Sensor.Value", property); ++} ++ ++inline void ++ fillMinMaxReadingRange(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& serviceName, ++ const std::string& sensorPath) ++{ ++ asyncResp->res.jsonValue["MetricType"] = "Numeric"; ++ ++ telemetry::getReadingRange( ++ serviceName, sensorPath, "MinValue", ++ [asyncResp](boost::system::error_code ec, double readingRange) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ if (std::isfinite(readingRange)) ++ { ++ asyncResp->res.jsonValue["MetricType"] = "Gauge"; ++ ++ asyncResp->res.jsonValue["MinReadingRange"] = readingRange; ++ } ++ }); ++ ++ telemetry::getReadingRange( ++ serviceName, sensorPath, "MaxValue", ++ [asyncResp](boost::system::error_code ec, double readingRange) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ if (std::isfinite(readingRange)) ++ { ++ asyncResp->res.jsonValue["MetricType"] = "Gauge"; ++ ++ asyncResp->res.jsonValue["MaxReadingRange"] = readingRange; ++ } ++ }); ++} ++ ++inline void getSensorService( ++ const std::string& sensorPath, ++ std::function<void(boost::system::error_code, const std::string&)> callback) ++{ ++ using ResultType = std::pair< ++ std::string, ++ std::vector<std::pair<std::string, std::vector<std::string>>>>; ++ ++ crow::connections::systemBus->async_method_call( ++ [sensorPath, callback = std::move(callback)]( ++ boost::system::error_code ec, ++ const std::vector<ResultType>& result) { ++ if (ec) ++ { ++ callback(ec, std::string{}); ++ return; ++ } ++ ++ for (const auto& [path, serviceToInterfaces] : result) ++ { ++ if (path == sensorPath) ++ { ++ for (const auto& [service, interfaces] : ++ serviceToInterfaces) ++ { ++ callback(boost::system::errc::make_error_code( ++ boost::system::errc::success), ++ service); ++ return; ++ } ++ } ++ } ++ ++ callback(boost::system::errc::make_error_code( ++ boost::system::errc::no_such_file_or_directory), ++ std::string{}); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTree", ++ "/xyz/openbmc_project/sensors", 2, ++ std::array{"xyz.openbmc_project.Sensor.Value"}); ++} ++ ++constexpr auto metricDefinitionMapping = std::array{ ++ std::pair{"fan_pwm", "Fan_Pwm"}, std::pair{"fan_tach", "Fan_Tach"}}; ++ ++std::string mapSensorToMetricDefinition(const std::string& sensorPath) ++{ ++ sdbusplus::message::object_path sensorObjectPath{sensorPath}; ++ ++ const auto it = std::find_if( ++ metricDefinitionMapping.begin(), metricDefinitionMapping.end(), ++ [&sensorObjectPath](const auto& item) { ++ return item.first == sensorObjectPath.parent_path().filename(); ++ }); ++ ++ const char* metricDefinitionPath = ++ "/redfish/v1/TelemetryService/MetricDefinitions/"; ++ ++ if (it != metricDefinitionMapping.end()) ++ { ++ return std::string{metricDefinitionPath} + it->second; ++ } ++ ++ return metricDefinitionPath + sensorObjectPath.filename(); ++} ++ ++template <class Callback> ++inline void mapRedfishUriToDbusPath(Callback&& callback) ++{ ++ utils::getChassisNames([callback = std::move(callback)]( ++ boost::system::error_code ec, ++ const std::vector<std::string>& chassisNames) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getChassisNames error: " << ec.value(); ++ callback(ec, {}); ++ return; ++ } ++ ++ auto counter = std::make_shared<std::pair< ++ boost::container::flat_map<std::string, std::string>, size_t>>(); ++ ++ auto handleRetrieveUriToDbusMap = ++ [counter, callback = std::move(callback)]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map<std::string, std::string>& ++ uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ counter->second = 0u; ++ callback(boost::system::errc::make_error_code( ++ boost::system::errc::io_error), ++ {}); ++ return; ++ } ++ ++ for (const auto& [key, value] : uriToDbus) ++ { ++ counter->first[key] = value; ++ } ++ ++ if (--counter->second == 0u) ++ { ++ callback(boost::system::errc::make_error_code( ++ boost::system::errc::success), ++ counter->first); ++ } ++ }; ++ ++ for (const std::string& chassisName : chassisNames) ++ { ++ for (const auto& [sensorNode, dbusPaths] : sensors::dbus::paths) ++ { ++ ++counter->second; ++ 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<bmcweb::AsyncResp>& 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; ++ } ++ ++ std::set<std::string> members; ++ ++ for (const auto& [uri, dbusPath] : uriToDbus) ++ { ++ members.insert( ++ telemetry::mapSensorToMetricDefinition( ++ dbusPath)); ++ } ++ ++ for (const std::string& odataId : members) ++ { ++ asyncResp->res.jsonValue["Members"].push_back( ++ {{"@odata.id", odataId}}); ++ } ++ ++ asyncResp->res.jsonValue["Members@odata.count"] = ++ asyncResp->res.jsonValue["Members"].size(); ++ }); ++ ++ 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; ++ }); ++} ++ ++inline void requestRoutesMetricDefinition(App& app) ++{ ++ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions/<str>/") ++ .privileges(privileges::getTelemetryService) ++ .methods( ++ boost::beast::http::verb::get)([](const crow::Request&, ++ const std::shared_ptr< ++ bmcweb::AsyncResp>& asyncResp, ++ const std::string& name) { ++ telemetry::mapRedfishUriToDbusPath( ++ [asyncResp, name]( ++ 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; ++ } ++ ++ std::string odataId = telemetry::metricDefinitionUri + name; ++ boost::container::flat_map<std::string, std::string> ++ matchingUris; ++ ++ for (const auto& [uri, dbusPath] : uriToDbus) ++ { ++ if (telemetry::mapSensorToMetricDefinition(dbusPath) == ++ odataId) ++ { ++ matchingUris.emplace(uri, dbusPath); ++ } ++ } ++ ++ if (matchingUris.empty()) ++ { ++ messages::resourceNotFound(asyncResp->res, ++ "MetricDefinition", name); ++ return; ++ } ++ ++ std::string sensorPath = matchingUris.begin()->second; ++ ++ telemetry::getSensorService( ++ sensorPath, ++ [asyncResp, name, odataId = std::move(odataId), ++ sensorPath, matchingUris = std::move(matchingUris)]( ++ boost::system::error_code ec, ++ const std::string& serviceName) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "getServiceSensorFailed: " ++ << ec.value(); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["Id"] = name; ++ asyncResp->res.jsonValue["Name"] = name; ++ asyncResp->res.jsonValue["@odata.id"] = odataId; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#MetricDefinition.v1_0_3.MetricDefinition"; ++ asyncResp->res.jsonValue["MetricDataType"] = ++ "Decimal"; ++ asyncResp->res.jsonValue["IsLinear"] = true; ++ asyncResp->res.jsonValue["Units"] = ++ sensors::toReadingUnits( ++ sdbusplus::message::object_path{sensorPath} ++ .parent_path() ++ .filename()); ++ ++ for (const auto& [uri, dbusPath] : matchingUris) ++ { ++ asyncResp->res.jsonValue["MetricProperties"] ++ .push_back(uri); ++ } ++ ++ telemetry::fillMinMaxReadingRange( ++ asyncResp, serviceName, sensorPath); ++ }); ++ }); ++ }); ++} ++ ++} // namespace redfish +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index 8ecc591..027b51b 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -18,11 +18,12 @@ inline void handleTelemetryServiceGet( + asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService"; + asyncResp->res.jsonValue["Id"] = "TelemetryService"; + asyncResp->res.jsonValue["Name"] = "Telemetry Service"; +- + asyncResp->res.jsonValue["MetricReportDefinitions"]["@odata.id"] = + "/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](const boost::system::error_code ec, +-- +2.25.1 |