diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry')
10 files changed, 1305 insertions, 1541 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 deleted file mode 100644 index f5226fe6e..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-MetricDefinition-scheme.patch +++ /dev/null @@ -1,619 +0,0 @@ -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 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-POST-on-TriggersCollection.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-POST-on-TriggersCollection.patch new file mode 100644 index 000000000..b0cf44cb2 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Add-support-for-POST-on-TriggersCollection.patch @@ -0,0 +1,889 @@ +From 008cc1b35ccb1508d3c71ff5c6cfc6c772f1744c Mon Sep 17 00:00:00 2001 +From: Szymon Dompke <szymon.dompke@intel.com> +Date: Wed, 17 Nov 2021 18:18:16 +0100 +Subject: [PATCH] Add support for POST on TriggersCollection +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Added POST method on /redfish/v1/TelemetryService/Triggers uri, which +creates new trigger in telemetry service, by using dbus call AddTrigger. + +By DMTF, most of the properties are not required, and as such are +treated as optional. Some values can be deduced from others (like +'MetricType', depending on 'DiscreteTriggers' or 'NumericThresholds'). +All properties provided in POST body by user will be verified against +each other, and errors will be raised. Few examples of such situations: +- 'MetricType' is set to 'Discrete' but 'NumericThresholds' was passed. +- 'MetricType' is set to 'Numeric' but "DiscreteTriggers' or + 'DiscreteTriggerCondition' were passed +- 'DiscreteTriggerCondition' is set to 'Specified' but + 'DiscreteTriggers' is an empty array or was not passed. +- 'DiscreteTriggerCondition' is set to 'Changed' but 'DiscreteTriggers' + is passed and is not an empty array. + +Example 1 – Trigger with discrete values: +{ + "Id": "TestTrigger", + "MetricType": "Discrete", + "TriggerActions": [ + "RedfishEvent" + ], + "DiscreteTriggerCondition": "Specified", + "DiscreteTriggers": [ + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Warning" + }, + { + "Name": "My discrete trigger", + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "OK" + }, + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Critical" + } + ], + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading" + ], + "Links": { + "MetricReportDefinitions": [] + } +} + +Example 2 – trigger with numeric threshold: +{ + "Id": "TestTrigger2", + "Name": "My Numeric Trigger", + "MetricType": "Numeric", + "TriggerActions": [ + "RedfishEvent", + "RedfishMetricReport" + ], + "NumericThresholds": { + "UpperCritical": { + "Reading": 50, + "Activation": "Increasing", + "DwellTime": "PT0.001S" + }, + "UpperWarning": { + "Reading": 48.1, + "Activation": "Increasing", + "DwellTime": "PT0.004S" + } + }, + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/17/Reading" + ], + "Links": { + "MetricReportDefinitions": [ + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetrics", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetricStats", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PlatformPowerUsage" + ] + } +} + +Tested: +- Triggers were successfully created with above example message bodies. + This can be checked by calling: + 'busctl tree xyz.openbmc_project.Telemetry'. +- Expected errors were returned for messages with incorrect or mutually + exclusive properties and incorrect values. +- Redfish service validator is passing. + +Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> +Change-Id: Ief8c76de8aa660ae0d2dbe4610c26a28186a290a +--- + redfish-core/include/utils/finalizer.hpp | 35 ++ + .../include/utils/telemetry_utils.hpp | 82 +++ + redfish-core/lib/metric_report_definition.hpp | 31 +- + redfish-core/lib/trigger.hpp | 526 +++++++++++++++++- + 4 files changed, 642 insertions(+), 32 deletions(-) + create mode 100644 redfish-core/include/utils/finalizer.hpp + +diff --git a/redfish-core/include/utils/finalizer.hpp b/redfish-core/include/utils/finalizer.hpp +new file mode 100644 +index 0000000..cb98507 +--- /dev/null ++++ b/redfish-core/include/utils/finalizer.hpp +@@ -0,0 +1,35 @@ ++#pragma once ++ ++#include <functional> ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++class Finalizer ++{ ++ public: ++ Finalizer() = delete; ++ Finalizer(std::function<void()> finalizer) : finalizer(std::move(finalizer)) ++ {} ++ ++ Finalizer(const Finalizer&) = delete; ++ Finalizer(Finalizer&&) = delete; ++ ++ ~Finalizer() ++ { ++ if (finalizer) ++ { ++ finalizer(); ++ } ++ } ++ ++ private: ++ std::function<void()> finalizer; ++}; ++ ++} // namespace utils ++ ++} // namespace redfish +\ No newline at end of file +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 8aeff0d..c68e40c 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -13,6 +13,9 @@ constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + constexpr const char* metricReportUri = + "/redfish/v1/TelemetryService/MetricReports"; ++constexpr const char* triggerInterface = ++ "xyz.openbmc_project.Telemetry.Trigger"; ++constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers"; + + inline std::string getDbusReportPath(const std::string& id) + { +@@ -28,5 +31,84 @@ inline std::string getDbusTriggerPath(const std::string& id) + return {triggersPath / id}; + } + ++inline std::optional<std::string> ++ getReportNameFromReportDefinitionUri(const std::string& uri) ++{ ++ constexpr const char* uriPattern = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; ++ constexpr size_t idx = std::string_view(uriPattern).length(); ++ if (boost::starts_with(uri, uriPattern)) ++ { ++ return uri.substr(idx); ++ } ++ return std::nullopt; ++} ++ ++inline std::optional<std::string> ++ getTriggerIdFromDbusPath(const std::string& dbusPath) ++{ ++ constexpr const char* triggerTree = ++ "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService/"; ++ constexpr size_t idx = std::string_view(triggerTree).length(); ++ if (boost::starts_with(dbusPath, triggerTree)) ++ { ++ return dbusPath.substr(idx); ++ } ++ return std::nullopt; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::vector<std::string>& uris, ++ boost::container::flat_set<std::pair<std::string, std::string>>& matched) ++{ ++ size_t uriIdx = 0; ++ for (const std::string& uri : uris) ++ { ++ std::string chassis; ++ std::string node; ++ ++ if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || ++ !dbus::utility::getNthStringFromPath(uri, 3, chassis) || ++ !dbus::utility::getNthStringFromPath(uri, 4, node)) ++ { ++ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " ++ "from " ++ << uri; ++ messages::propertyValueIncorrect(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(uriIdx)); ++ return false; ++ } ++ ++ if (boost::ends_with(node, "#")) ++ { ++ node.pop_back(); ++ } ++ ++ matched.emplace(std::move(chassis), std::move(node)); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline std::optional<std::string> ++ redfishActionToDbusAction(const std::string& redfishAction) ++{ ++ if (redfishAction == "RedfishMetricReport") ++ { ++ return "UpdateReport"; ++ } ++ if (redfishAction == "RedfishEvent") ++ { ++ return "RedfishEvent"; ++ } ++ if (redfishAction == "LogToLogService") ++ { ++ return "LogToLogService"; ++ } ++ return std::nullopt; ++} ++ + } // namespace telemetry + } // namespace redfish +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index cb38633..4007544 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -217,7 +217,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + return true; + } + +-inline bool getChassisSensorNode( ++inline bool getChassisSensorNodeFromMetrics( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::pair<std::string, std::vector<std::string>>>& + metrics, +@@ -225,30 +225,9 @@ inline bool getChassisSensorNode( + { + for (const auto& [id, uris] : metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ if (!getChassisSensorNode(asyncResp, uris, matched)) + { +- const std::string& uri = uris[i]; +- std::string chassis; +- std::string node; +- +- if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || +- !dbus::utility::getNthStringFromPath(uri, 3, chassis) || +- !dbus::utility::getNthStringFromPath(uri, 4, node)) +- { +- BMCWEB_LOG_ERROR +- << "Failed to get chassis and sensor Node from " << uri; +- messages::propertyValueIncorrect(asyncResp->res, uri, +- "MetricProperties/" + +- std::to_string(i)); +- return false; +- } +- +- if (boost::ends_with(node, "#")) +- { +- node.pop_back(); +- } +- +- matched.emplace(std::move(chassis), std::move(node)); ++ return false; + } + } + return true; +@@ -382,8 +361,8 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) + + boost::container::flat_set<std::pair<std::string, std::string>> + chassisSensors; +- if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, +- chassisSensors)) ++ if (!telemetry::getChassisSensorNodeFromMetrics( ++ asyncResp, args.metrics, chassisSensors)) + { + return; + } +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index 210468c..01c150e 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -1,7 +1,9 @@ + #pragma once + +-#include "utils/collection.hpp" ++#include "sensors.hpp" ++#include "utils/finalizer.hpp" + #include "utils/telemetry_utils.hpp" ++#include "utils/time_utils.hpp" + + #include <app.hpp> + #include <registries/privilege_registry.hpp> +@@ -14,9 +16,10 @@ namespace redfish + { + namespace telemetry + { +-constexpr const char* triggerInterface = +- "xyz.openbmc_project.Telemetry.Trigger"; +-constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers"; ++ ++static constexpr std::array<std::string_view, 4> ++ supportedNumericThresholdNames = {"UpperCritical", "LowerCritical", ++ "UpperWarning", "LowerWarning"}; + + using NumericThresholdParams = + std::tuple<std::string, uint64_t, std::string, double>; +@@ -24,6 +27,10 @@ using NumericThresholdParams = + using DiscreteThresholdParams = + std::tuple<std::string, std::string, uint64_t, std::string>; + ++using TriggerThresholdParams = ++ std::variant<std::vector<NumericThresholdParams>, ++ std::vector<DiscreteThresholdParams>>; ++ + using TriggerThresholdParamsExt = + std::variant<std::monostate, std::vector<NumericThresholdParams>, + std::vector<DiscreteThresholdParams>>; +@@ -35,6 +42,455 @@ using TriggerGetParamsVariant = + std::variant<std::monostate, bool, std::string, TriggerThresholdParamsExt, + TriggerSensorsParams, std::vector<std::string>>; + ++namespace add_trigger ++{ ++ ++enum class MetricType ++{ ++ Discrete, ++ Numeric ++}; ++ ++enum class DiscreteCondition ++{ ++ Specified, ++ Changed ++}; ++ ++struct Context ++{ ++ struct ++ { ++ std::string id; ++ std::string name; ++ std::vector<std::string> actions; ++ std::vector<std::pair<sdbusplus::message::object_path, std::string>> ++ sensors; ++ std::vector<std::string> reportNames; ++ TriggerThresholdParams thresholds; ++ } dbusArgs; ++ ++ struct ++ { ++ std::optional<DiscreteCondition> discreteCondition; ++ std::optional<MetricType> metricType; ++ std::optional<std::vector<std::string>> metricProperties; ++ } parsedInfo; ++ ++ boost::container::flat_map<std::string, std::string> uriToDbusMerged{}; ++}; ++ ++inline std::optional<MetricType> getMetricType(const std::string& metricType) ++{ ++ if (metricType == "Discrete") ++ { ++ return MetricType::Discrete; ++ } ++ if (metricType == "Numeric") ++ { ++ return MetricType::Numeric; ++ } ++ return std::nullopt; ++} ++ ++inline std::optional<DiscreteCondition> ++ getDiscreteCondition(const std::string& discreteTriggerCondition) ++{ ++ if (discreteTriggerCondition == "Specified") ++ { ++ return DiscreteCondition::Specified; ++ } ++ if (discreteTriggerCondition == "Changed") ++ { ++ return DiscreteCondition::Changed; ++ } ++ return std::nullopt; ++} ++ ++inline bool parseNumericThresholds(crow::Response& res, ++ nlohmann::json& numericThresholds, ++ Context& ctx) ++{ ++ if (!numericThresholds.is_object()) ++ { ++ messages::propertyValueTypeError(res, numericThresholds.dump(), ++ "NumericThresholds"); ++ return false; ++ } ++ ++ std::vector<NumericThresholdParams> parsedParams; ++ parsedParams.reserve(numericThresholds.size()); ++ ++ for (auto& [thresholdName, thresholdData] : numericThresholds.items()) ++ { ++ if (std::find(supportedNumericThresholdNames.begin(), ++ supportedNumericThresholdNames.end(), ++ thresholdName) == supportedNumericThresholdNames.end()) ++ { ++ messages::propertyUnknown(res, thresholdName); ++ return false; ++ } ++ ++ double reading = .0; ++ std::string activation; ++ std::string dwellTimeStr; ++ ++ if (!json_util::readJson(thresholdData, res, "Reading", reading, ++ "Activation", activation, "DwellTime", ++ dwellTimeStr)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ parsedParams.emplace_back(thresholdName, ++ static_cast<uint64_t>(dwellTime->count()), ++ activation, reading); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseDiscreteTriggers( ++ crow::Response& res, ++ std::optional<std::vector<nlohmann::json>>& discreteTriggers, Context& ctx) ++{ ++ std::vector<DiscreteThresholdParams> parsedParams; ++ if (!discreteTriggers) ++ { ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++ } ++ ++ parsedParams.reserve(discreteTriggers->size()); ++ for (nlohmann::json& thresholdInfo : *discreteTriggers) ++ { ++ std::optional<std::string> name; ++ std::string value; ++ std::string dwellTimeStr; ++ std::string severity; ++ ++ if (!json_util::readJson(thresholdInfo, res, "Name", name, "Value", ++ value, "DwellTime", dwellTimeStr, "Severity", ++ severity)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ if (!name) ++ { ++ name = ""; ++ } ++ ++ parsedParams.emplace_back( ++ *name, severity, static_cast<uint64_t>(dwellTime->count()), value); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseTriggerThresholds( ++ crow::Response& res, ++ std::optional<std::vector<nlohmann::json>>& discreteTriggers, ++ std::optional<nlohmann::json>& numericThresholds, Context& ctx) ++{ ++ if (discreteTriggers && numericThresholds) ++ { ++ messages::mutualExclusiveProperties(res, "DiscreteTriggers", ++ "NumericThresholds"); ++ return false; ++ } ++ ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (numericThresholds) ++ { ++ messages::mutualExclusiveProperties(res, "DiscreteTriggerCondition", ++ "NumericThresholds"); ++ return false; ++ } ++ } ++ ++ if (ctx.parsedInfo.metricType) ++ { ++ if (*ctx.parsedInfo.metricType == MetricType::Discrete && ++ numericThresholds) ++ { ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ discreteTriggers) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ ctx.parsedInfo.discreteCondition) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ ++ if (discreteTriggers || ctx.parsedInfo.discreteCondition || ++ (ctx.parsedInfo.metricType && ++ *ctx.parsedInfo.metricType == MetricType::Discrete)) ++ { ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ !discreteTriggers) ++ { ++ messages::createFailedMissingReqProperties(res, ++ "DiscreteTriggers"); ++ return false; ++ } ++ if (discreteTriggers && ((*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ discreteTriggers->empty()) || ++ (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Changed && ++ !discreteTriggers->empty()))) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ if (!parseDiscreteTriggers(res, discreteTriggers, ctx)) ++ { ++ return false; ++ } ++ } ++ else if (numericThresholds) ++ { ++ if (!parseNumericThresholds(res, *numericThresholds, ctx)) ++ { ++ return false; ++ } ++ } ++ else ++ { ++ messages::createFailedMissingReqProperties( ++ res, "'DiscreteTriggers', 'NumericThresholds', " ++ "'DiscreteTriggerCondition' or 'MetricType'"); ++ return false; ++ } ++ return true; ++} ++ ++inline bool parseLinks(crow::Response& res, nlohmann::json& links, Context& ctx) ++{ ++ if (links.empty()) ++ { ++ return true; ++ } ++ ++ std::optional<std::vector<std::string>> metricReportDefinitions; ++ if (!json_util::readJson(links, res, "MetricReportDefinitions", ++ metricReportDefinitions)) ++ { ++ return false; ++ } ++ ++ if (metricReportDefinitions) ++ { ++ ctx.dbusArgs.reportNames.reserve(metricReportDefinitions->size()); ++ for (std::string& reportDefinionUri : *metricReportDefinitions) ++ { ++ std::optional<std::string> reportName = ++ getReportNameFromReportDefinitionUri(reportDefinionUri); ++ if (!reportName) ++ { ++ messages::propertyValueIncorrect(res, "MetricReportDefinitions", ++ reportDefinionUri); ++ return false; ++ } ++ ctx.dbusArgs.reportNames.push_back(*reportName); ++ } ++ } ++ return true; ++} ++ ++inline bool parseMetricProperties(crow::Response& res, Context& ctx) ++{ ++ if (!ctx.parsedInfo.metricProperties) ++ { ++ return true; ++ } ++ ++ ctx.dbusArgs.sensors.reserve(ctx.parsedInfo.metricProperties->size()); ++ ++ size_t uriIdx = 0; ++ for (const std::string& uri : *ctx.parsedInfo.metricProperties) ++ { ++ auto el = ctx.uriToDbusMerged.find(uri); ++ if (el == ctx.uriToDbusMerged.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corsresponding to URI " ++ << uri; ++ messages::propertyValueNotInList( ++ res, uri, "MetricProperties/" + std::to_string(uriIdx)); ++ return false; ++ } ++ ++ const std::string& dbusPath = el->second; ++ ctx.dbusArgs.sensors.emplace_back(dbusPath, uri); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline bool parsePostTriggerParams(crow::Response& res, ++ const crow::Request& req, Context& ctx) ++{ ++ std::optional<std::string> id; ++ std::optional<std::string> name; ++ std::optional<std::string> metricType; ++ std::optional<std::vector<std::string>> triggerActions; ++ std::optional<std::string> discreteTriggerCondition; ++ std::optional<std::vector<nlohmann::json>> discreteTriggers; ++ std::optional<nlohmann::json> numericThresholds; ++ std::optional<nlohmann::json> links; ++ if (!json_util::readJson( ++ req, res, "Id", id, "Name", name, "MetricType", metricType, ++ "TriggerActions", triggerActions, "DiscreteTriggerCondition", ++ discreteTriggerCondition, "DiscreteTriggers", discreteTriggers, ++ "NumericThresholds", numericThresholds, "MetricProperties", ++ ctx.parsedInfo.metricProperties, "Links", links)) ++ { ++ return false; ++ } ++ ++ ctx.dbusArgs.id = id.value_or(""); ++ ctx.dbusArgs.name = name.value_or(""); ++ ++ if (metricType) ++ { ++ if (!(ctx.parsedInfo.metricType = getMetricType(*metricType))) ++ { ++ messages::propertyValueIncorrect(res, "MetricType", *metricType); ++ return false; ++ } ++ } ++ ++ if (discreteTriggerCondition) ++ { ++ if (!(ctx.parsedInfo.discreteCondition = ++ getDiscreteCondition(*discreteTriggerCondition))) ++ { ++ messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", ++ *discreteTriggerCondition); ++ return false; ++ } ++ } ++ ++ if (triggerActions) ++ { ++ ctx.dbusArgs.actions.reserve(triggerActions->size()); ++ for (const std::string& action : *triggerActions) ++ { ++ if (const std::optional<std::string>& dbusAction = ++ redfishActionToDbusAction(action)) ++ { ++ ctx.dbusArgs.actions.emplace_back(*dbusAction); ++ } ++ else ++ { ++ messages::propertyValueNotInList(res, action, "TriggerActions"); ++ return false; ++ } ++ } ++ } ++ ++ if (!parseTriggerThresholds(res, discreteTriggers, numericThresholds, ctx)) ++ { ++ return false; ++ } ++ ++ if (links) ++ { ++ if (!parseLinks(res, *links, ctx)) ++ { ++ return false; ++ } ++ } ++ return true; ++} ++ ++inline void createTrigger(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ Context& ctx) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp = asyncResp, id = ctx.dbusArgs.id]( ++ const boost::system::error_code ec, const std::string& dbusPath) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists(aResp->res, "Trigger", "Id", ++ id); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(aResp->res); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ const std::optional<std::string>& triggerId = ++ getTriggerIdFromDbusPath(dbusPath); ++ if (!triggerId) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "Unknown data returned by " ++ "AddTrigger DBus method"; ++ return; ++ } ++ ++ messages::created(aResp->res); ++ aResp->res.addHeader("Location", ++ triggerUri + std::string("/") + *triggerId); ++ }, ++ service, "/xyz/openbmc_project/Telemetry/Triggers", ++ "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", ++ "TelemetryService/" + ctx.dbusArgs.id, ctx.dbusArgs.name, ++ ctx.dbusArgs.actions, ctx.dbusArgs.sensors, ctx.dbusArgs.reportNames, ++ ctx.dbusArgs.thresholds); ++} ++ ++} // namespace add_trigger ++ ++namespace get_trigger ++{ ++ + inline std::optional<std::string> + getRedfishFromDbusAction(const std::string& dbusAction) + { +@@ -270,6 +726,8 @@ inline bool fillTrigger( + return true; + } + ++} // namespace get_trigger ++ + } // namespace telemetry + + inline void requestRoutesTriggerCollection(App& app) +@@ -290,6 +748,62 @@ inline void requestRoutesTriggerCollection(App& app) + asyncResp, telemetry::triggerUri, interfaces, + "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); + }); ++ ++ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") ++ .privileges(redfish::privileges::postTriggersCollection) ++ .methods(boost::beast::http::verb::post)( ++ [](const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { ++ const auto ctx = ++ std::make_shared<telemetry::add_trigger::Context>(); ++ if (!telemetry::add_trigger::parsePostTriggerParams( ++ asyncResp->res, req, *ctx)) ++ { ++ return; ++ } ++ ++ if (!ctx->parsedInfo.metricProperties || ++ ctx->parsedInfo.metricProperties->empty()) ++ { ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ return; ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode( ++ asyncResp, *ctx->parsedInfo.metricProperties, ++ chassisSensors)) ++ { ++ return; ++ } ++ ++ const auto finalizer = ++ std::make_shared<utils::Finalizer>([asyncResp, ctx] { ++ if (!telemetry::add_trigger::parseMetricProperties( ++ asyncResp->res, *ctx)) ++ { ++ return; ++ } ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ }); ++ ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, ctx, ++ finalizer](const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status == boost::beast::http::status::ok) ++ { ++ ctx->uriToDbusMerged.insert(uriToDbus.begin(), ++ uriToDbus.end()); ++ } ++ }); ++ } ++ }); + } + + inline void requestRoutesTrigger(App& app) +@@ -320,8 +834,8 @@ inline void requestRoutesTrigger(App& app) + return; + } + +- if (!telemetry::fillTrigger(asyncResp->res.jsonValue, +- id, ret)) ++ if (!telemetry::get_trigger::fillTrigger( ++ asyncResp->res.jsonValue, id, ret)) + { + messages::internalError(asyncResp->res); + } +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Sync-Telmetry-service-with-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Sync-Telmetry-service-with-EventService.patch index 3088a7f9d..f86f48528 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Sync-Telmetry-service-with-EventService.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Sync-Telmetry-service-with-EventService.patch @@ -1,4 +1,4 @@ -From 541353a4e4b06de42b6a9a400629f5a5fba04e86 Mon Sep 17 00:00:00 2001 +From 383848b9088056371743a28eb1f9a3ed415dc46b Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> Date: Tue, 15 Dec 2020 12:30:31 +0100 Subject: [PATCH] Sync Telmetry service with EventService @@ -18,12 +18,12 @@ Change-Id: I2fc1841a6c9259a8bff30b34bddc0d4aabd41912 Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> --- - .../include/event_service_manager.hpp | 156 ++++++------------ - redfish-core/lib/metric_report.hpp | 28 ++-- - 2 files changed, 69 insertions(+), 115 deletions(-) + .../include/event_service_manager.hpp | 135 ++++++------------ + redfish-core/lib/metric_report.hpp | 29 ++-- + 2 files changed, 59 insertions(+), 105 deletions(-) diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp -index 3f398d7..cf9f658 100644 +index 010c991..38ab879 100644 --- a/redfish-core/include/event_service_manager.hpp +++ b/redfish-core/include/event_service_manager.hpp @@ -14,6 +14,7 @@ @@ -34,7 +34,7 @@ index 3f398d7..cf9f658 100644 #include "registries.hpp" #include "registries/base_message_registry.hpp" #include "registries/openbmc_message_registry.hpp" -@@ -511,47 +512,32 @@ class Subscription : public persistent_data::UserSubscription +@@ -503,47 +504,32 @@ class Subscription : public persistent_data::UserSubscription } #endif @@ -93,28 +93,45 @@ index 3f398d7..cf9f658 100644 this->sendEvent( msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); } -@@ -1317,75 +1303,6 @@ class EventServiceManager +@@ -1309,73 +1295,43 @@ class EventServiceManager } #endif - - void getMetricReading(const std::string& service, - const std::string& objPath, const std::string& intf) -- { ++ void getReadingsForReport(sdbusplus::message::message& msg) + { - std::size_t found = objPath.find_last_of('/'); - if (found == std::string::npos) -- { ++ sdbusplus::message::object_path path(msg.get_path()); ++ std::string id = path.filename(); ++ if (id.empty()) + { - BMCWEB_LOG_DEBUG << "Invalid objPath received"; -- return; -- } -- ++ BMCWEB_LOG_ERROR << "Failed to get Id from path"; + return; + } + - std::string idStr = objPath.substr(found + 1); - if (idStr.empty()) -- { ++ std::string interface; ++ std::vector< ++ std::pair<std::string, std::variant<telemetry::TimestampReadings>>> ++ props; ++ std::vector<std::string> invalidProps; ++ msg.read(interface, props, invalidProps); ++ ++ auto found = ++ std::find_if(props.begin(), props.end(), ++ [](const auto& x) { return x.first == "Readings"; }); ++ if (found == props.end()) + { - BMCWEB_LOG_DEBUG << "Invalid ID in objPath"; -- return; -- } -- ++ BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; + return; + } + - crow::connections::systemBus->async_method_call( - [idStr{std::move(idStr)}]( - const boost::system::error_code ec, @@ -164,12 +181,21 @@ index 3f398d7..cf9f658 100644 - }, - service, objPath, "org.freedesktop.DBus.Properties", "GetAll", - intf); -- } -- ++ const std::variant<telemetry::TimestampReadings>& readings = ++ found->second; ++ for (const auto& it : ++ EventServiceManager::getInstance().subscriptionsMap) ++ { ++ Subscription& entry = *it.second.get(); ++ if (entry.eventFormatType == metricReportFormatType) ++ { ++ entry.filterAndSendReports(id, readings); ++ } ++ } + } + void unregisterMetricReportSignal() - { - if (matchTelemetryMonitor) -@@ -1405,9 +1322,11 @@ class EventServiceManager +@@ -1397,9 +1353,9 @@ class EventServiceManager } BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; @@ -178,13 +204,11 @@ index 3f398d7..cf9f658 100644 - "interface='xyz.openbmc_project.MonitoringService.Report'"); + std::string matchStr = "type='signal',member='PropertiesChanged'," + "interface='org.freedesktop.DBus.Properties'," -+ "path_namespace=/xyz/openbmc_project/Telemetry/" -+ "Reports/TelemetryService," + "arg0=xyz.openbmc_project.Telemetry.Report"; matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( *crow::connections::systemBus, matchStr, -@@ -1418,10 +1337,43 @@ class EventServiceManager +@@ -1410,10 +1366,7 @@ class EventServiceManager return; } @@ -192,51 +216,15 @@ index 3f398d7..cf9f658 100644 - std::string objPath = msg.get_path(); - std::string intf = msg.get_interface(); - getMetricReading(service, objPath, intf); -+ sdbusplus::message::object_path path(msg.get_path()); -+ std::string id = path.filename(); -+ if (id.empty()) -+ { -+ BMCWEB_LOG_ERROR << "Failed to get Id from path"; -+ return; -+ } -+ -+ std::string intf; -+ std::vector<std::pair< -+ std::string, std::variant<telemetry::TimestampReadings>>> -+ props; -+ std::vector<std::string> invalidProps; -+ msg.read(intf, props, invalidProps); -+ -+ auto found = -+ std::find_if(props.begin(), props.end(), [](const auto& x) { -+ return x.first == "Readings"; -+ }); -+ if (found == props.end()) -+ { -+ BMCWEB_LOG_INFO -+ << "Failed to get Readings from Report properties"; -+ return; -+ } -+ -+ const std::variant<telemetry::TimestampReadings>& readings = -+ found->second; -+ for (const auto& it : -+ EventServiceManager::getInstance().subscriptionsMap) -+ { -+ Subscription& entry = *it.second.get(); -+ if (entry.eventFormatType == metricReportFormatType) -+ { -+ entry.filterAndSendReports(id, readings); -+ } -+ } ++ getReadingsForReport(msg); }); } diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 63c8c19..7fe281d 100644 +index 159968a..c959495 100644 --- a/redfish-core/lib/metric_report.hpp +++ b/redfish-core/lib/metric_report.hpp -@@ -33,16 +33,14 @@ inline nlohmann::json toMetricValues(const Readings& readings) +@@ -34,17 +34,14 @@ inline nlohmann::json toMetricValues(const Readings& readings) return metricValues; } @@ -247,19 +235,20 @@ index 63c8c19..7fe281d 100644 { - asyncResp->res.jsonValue["@odata.type"] = - "#MetricReport.v1_3_0.MetricReport"; -- asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; +- asyncResp->res.jsonValue["@odata.id"] = +- telemetry::metricReportUri + std::string("/") + id; - asyncResp->res.jsonValue["Id"] = id; - asyncResp->res.jsonValue["Name"] = id; - asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = + json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; -+ json["@odata.id"] = telemetry::metricReportUri + id; ++ json["@odata.id"] = telemetry::metricReportUri + std::string("/") + id; + json["Id"] = id; + json["Name"] = id; + json["MetricReportDefinition"]["@odata.id"] = - telemetry::metricReportDefinitionUri + id; + telemetry::metricReportDefinitionUri + std::string("/") + id; const TimestampReadings* timestampReadings = -@@ -50,14 +48,14 @@ inline void fillReport(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +@@ -52,14 +49,14 @@ inline void fillReport(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, if (!timestampReadings) { BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; @@ -278,7 +267,7 @@ index 63c8c19..7fe281d 100644 } } // namespace telemetry -@@ -118,7 +116,11 @@ inline void requestRoutesMetricReport(App& app) +@@ -122,7 +119,11 @@ inline void requestRoutesMetricReport(App& app) return; } diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Revert-Remove-LogService-from-TelemetryService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Revert-Remove-LogService-from-TelemetryService.patch index a80ac61c7..6b57dc912 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Revert-Remove-LogService-from-TelemetryService.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Revert-Remove-LogService-from-TelemetryService.patch @@ -1,21 +1,21 @@ -From da575aaf0bdcb15be261d58314cf7bbbcd92dd74 Mon Sep 17 00:00:00 2001 +From 71b55e9773c387d6510650e7cf64f050a853ac77 Mon Sep 17 00:00:00 2001 From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Date: Tue, 12 Oct 2021 08:08:06 +0000 +Date: Tue, 30 Nov 2021 16:29:12 +0100 Subject: [PATCH] Revert "Remove LogService from TelemetryService" This reverts commit 2b3da45876aac57a36d3093379a992d699e7e396. --- - redfish-core/lib/telemetry_service.hpp | 2 ++ - 1 file changed, 2 insertions(+) + redfish-core/lib/telemetry_service.hpp | 2 + + 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp -index 027b51b..49471fe 100644 +index b79a5cd..e3e4a25 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp -@@ -24,6 +24,8 @@ inline void handleTelemetryServiceGet( +@@ -25,6 +25,8 @@ inline void handleTelemetryServiceGet( "/redfish/v1/TelemetryService/MetricReports"; - asyncResp->res.jsonValue["MetricDefinitions"]["@odata.id"] = - "/redfish/v1/TelemetryService/MetricDefinitions"; + asyncResp->res.jsonValue["Triggers"]["@odata.id"] = + "/redfish/v1/TelemetryService/Triggers"; + asyncResp->res.jsonValue["LogService"]["@odata.id"] = + "/redfish/v1/Managers/bmc/LogServices/Journal"; diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Switched-bmcweb-to-use-new-telemetry-service-API.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Switched-bmcweb-to-use-new-telemetry-service-API.patch index 5dd2f51bc..a71511a96 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Switched-bmcweb-to-use-new-telemetry-service-API.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Switched-bmcweb-to-use-new-telemetry-service-API.patch @@ -1,6 +1,6 @@ -From 8ba1bcc3503cafb33b1a06356d4f8f92ae23e39a Mon Sep 17 00:00:00 2001 +From 41ee20f46f2c6d2e9e5418128993cd176d03a927 Mon Sep 17 00:00:00 2001 From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Date: Thu, 17 Jun 2021 13:37:57 +0000 +Date: Wed, 5 Jan 2022 14:54:18 +0100 Subject: [PATCH] Switched bmcweb to use new telemetry service API Added support for multiple MetricProperties. Added support for new @@ -15,51 +15,66 @@ Tested: Change-Id: I2cd17069e3ea015c8f5571c29278f1d50536272a Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> --- - redfish-core/lib/metric_report_definition.hpp | 212 ++++++++++-------- - 1 file changed, 114 insertions(+), 98 deletions(-) + include/dbus_utility.hpp | 5 +- + redfish-core/lib/metric_report_definition.hpp | 310 +++++++++++------- + 2 files changed, 189 insertions(+), 126 deletions(-) +diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp +index a971325..35109c4 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -51,8 +51,9 @@ using DbusVariantType = sdbusplus::utility::dedup_variant_t< + std::vector<std::tuple<std::string, std::string>>, + std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>, + std::vector<std::tuple<uint32_t, size_t>>, +- std::vector<std::tuple<sdbusplus::message::object_path, std::string, +- std::string, std::string>> ++ std::vector<std::tuple< ++ std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, ++ std::string, std::string, std::string, uint64_t>> + >; + + // clang-format on diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index a0c4f1d..7c26787 100644 +index 2584afc..9512381 100644 --- a/redfish-core/lib/metric_report_definition.hpp +++ b/redfish-core/lib/metric_report_definition.hpp -@@ -7,6 +7,8 @@ - #include <app.hpp> +@@ -8,6 +8,8 @@ #include <boost/container/flat_map.hpp> + #include <dbus_utility.hpp> #include <registries/privilege_registry.hpp> +#include <sdbusplus/asio/property.hpp> +#include <sdbusplus/unpack_properties.hpp> #include <tuple> #include <variant> -@@ -17,87 +19,90 @@ namespace redfish +@@ -18,119 +20,156 @@ namespace redfish namespace telemetry { -using ReadingParameters = - std::vector<std::tuple<sdbusplus::message::object_path, std::string, - std::string, std::string>>; -+using ReadingParameters = std::vector< -+ std::tuple<std::vector<sdbusplus::message::object_path>, std::string, -+ std::string, std::string, std::string, uint64_t>>; ++using ReadingParameters = std::vector<std::tuple< ++ std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, ++ std::string, std::string, std::string, uint64_t>>; - inline void fillReportDefinition( - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, - const std::vector< -- std::pair<std::string, std::variant<std::string, bool, uint64_t, -- ReadingParameters>>>& ret) -+ std::pair<std::string, std::variant<std::monostate, std::string, bool, -+ uint64_t, ReadingParameters>>>& -+ properties) +-inline void fillReportDefinition( +- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, +- const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& +- ret) ++std::string toReadfishReportAction(std::string_view action) { - asyncResp->res.jsonValue["@odata.type"] = - "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; - asyncResp->res.jsonValue["@odata.id"] = -- telemetry::metricReportDefinitionUri + id; +- telemetry::metricReportDefinitionUri + std::string("/") + id; - asyncResp->res.jsonValue["Id"] = id; - asyncResp->res.jsonValue["Name"] = id; - asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = -- telemetry::metricReportUri + id; +- telemetry::metricReportUri + std::string("/") + id; - asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; - asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; - @@ -69,34 +84,16 @@ index a0c4f1d..7c26787 100644 - const std::string* reportingType = nullptr; - const uint64_t* interval = nullptr; - for (const auto& [key, var] : ret) -+ try ++ if (action == "EmitsReadingsUpdate") { - if (key == "EmitsReadingsUpdate") -+ bool emitsReadingsUpdate = false; -+ bool logToMetricReportsCollection = false; -+ ReadingParameters readingParams; -+ std::string reportingType; -+ uint64_t interval = 0u; -+ -+ sdbusplus::unpackProperties( -+ properties, "EmitsReadingsUpdate", emitsReadingsUpdate, -+ "LogToMetricReportsCollection", logToMetricReportsCollection, -+ "ReadingParametersFutureVersion", readingParams, "ReportingType", -+ reportingType, "Interval", interval); -+ -+ std::vector<std::string> redfishReportActions; -+ redfishReportActions.reserve(2); -+ if (emitsReadingsUpdate) - { +- { - emitsReadingsUpdate = std::get_if<bool>(&var); -+ redfishReportActions.emplace_back("RedfishEvent"); - } +- } - else if (key == "LogToMetricReportsCollection") -+ if (logToMetricReportsCollection) - { +- { - logToMetricReportsCollection = std::get_if<bool>(&var); -+ redfishReportActions.emplace_back("LogToMetricReportsCollection"); - } +- } - else if (key == "ReadingParameters") - { - readingParams = std::get_if<ReadingParameters>(&var); @@ -106,76 +103,126 @@ index a0c4f1d..7c26787 100644 - reportingType = std::get_if<std::string>(&var); - } - else if (key == "Interval") -+ -+ nlohmann::json metrics = nlohmann::json::array(); -+ for (auto& [sensorPath, operationType, id, metadata, -+ collectionTimeScope, collectionDuration] : readingParams) - { +- { - interval = std::get_if<uint64_t>(&var); -+ std::vector<std::string> metricProperties; -+ -+ nlohmann::json parsedMetadata = nlohmann::json::parse(metadata); -+ if (!json_util::readJson(parsedMetadata, asyncResp->res, -+ "MetricProperties", metricProperties)) -+ { -+ BMCWEB_LOG_ERROR << "Failed to read metadata"; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ metrics.push_back({ -+ {"MetricId", id}, -+ {"MetricProperties", std::move(metricProperties)}, -+ }); - } -- } +- } ++ return "RedfishEvent"; + } - if (!emitsReadingsUpdate || !logToMetricReportsCollection || - !readingParams || !reportingType || !interval) -- { ++ if (action == "LogToMetricReportsCollection") + { - BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; - messages::internalError(asyncResp->res); - return; -- } ++ return "LogToMetricReportsCollection"; + } ++ return ""; ++} - std::vector<std::string> redfishReportActions; - redfishReportActions.reserve(2); - if (*emitsReadingsUpdate) -- { ++std::string toDbusReportAction(std::string_view action) ++{ ++ if (action == "RedfishEvent") + { - redfishReportActions.emplace_back("RedfishEvent"); ++ return "EmitsReadingsUpdate"; + } +- if (*logToMetricReportsCollection) ++ if (action == "LogToMetricReportsCollection") + { +- redfishReportActions.emplace_back("LogToMetricReportsCollection"); ++ return "LogToMetricReportsCollection"; + } ++ return ""; ++} + +- nlohmann::json metrics = nlohmann::json::array(); +- for (auto& [sensorPath, operationType, id, metadata] : *readingParams) ++inline void fillReportDefinition( ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, ++ const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& ++ properties) ++{ ++ try + { +- metrics.push_back({ +- {"MetricId", id}, +- {"MetricProperties", {metadata}}, +- }); ++ std::vector<std::string> reportActions; ++ ReadingParameters readingParams; ++ std::string reportingType; ++ std::string reportUpdates; ++ std::string name; ++ uint64_t appendLimit = 0u; ++ uint64_t interval = 0u; ++ ++ sdbusplus::unpackProperties( ++ properties, "ReportActions", reportActions, "ReportUpdates", ++ reportUpdates, "AppendLimit", appendLimit, ++ "ReadingParametersFutureVersion", readingParams, "ReportingType", ++ reportingType, "Interval", interval, "Name", name); ++ ++ for (std::string& action : reportActions) ++ { ++ action = toReadfishReportAction(action); ++ ++ if (action.empty()) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ } ++ ++ nlohmann::json metrics = nlohmann::json::array(); ++ for (auto& [sensorData, collectionFunction, id, collectionTimeScope, ++ collectionDuration] : readingParams) ++ { ++ std::vector<std::string> metricProperties; ++ ++ for (auto& [sensorPath, sensorMetadata] : sensorData) ++ { ++ metricProperties.emplace_back(std::move(sensorMetadata)); ++ } ++ ++ metrics.push_back( ++ {{"MetricId", std::move(id)}, ++ {"MetricProperties", std::move(metricProperties)}, ++ {"CollectionFunction", std::move(collectionFunction)}, ++ {"CollectionDuration", ++ time_utils::toDurationString( ++ std::chrono::milliseconds(collectionDuration))}, ++ {"CollectionTimeScope", std::move(collectionTimeScope)}}); ++ } ++ + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + asyncResp->res.jsonValue["@odata.id"] = -+ telemetry::metricReportDefinitionUri + id; ++ telemetry::metricReportDefinitionUri + std::string("/") + id; + asyncResp->res.jsonValue["Id"] = id; -+ asyncResp->res.jsonValue["Name"] = id; ++ asyncResp->res.jsonValue["Name"] = name; + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = -+ telemetry::metricReportUri + id; ++ telemetry::metricReportUri + std::string("/") + id; + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; -+ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; ++ asyncResp->res.jsonValue["ReportUpdates"] = reportUpdates; ++ asyncResp->res.jsonValue["AppendLimit"] = appendLimit; + asyncResp->res.jsonValue["Metrics"] = metrics; + asyncResp->res.jsonValue["MetricReportDefinitionType"] = reportingType; -+ asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; ++ asyncResp->res.jsonValue["ReportActions"] = reportActions; + asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = + time_utils::toDurationString(std::chrono::milliseconds(interval)); - } -- if (*logToMetricReportsCollection) ++ } + catch (const sdbusplus::exception::UnpackPropertyError& error) - { -- redfishReportActions.emplace_back("LogToMetricReportsCollection"); ++ { + BMCWEB_LOG_ERROR << error.what() << ", property: " + << error.propertyName + ", reason: " << error.reason; -+ messages::internalError(asyncResp->res); - } -- -- nlohmann::json metrics = nlohmann::json::array(); -- for (auto& [sensorPath, operationType, id, metadata] : *readingParams) -+ catch (const nlohmann::json::parse_error& e) - { -- metrics.push_back({ -- {"MetricId", id}, -- {"MetricProperties", {metadata}}, -- }); -+ BMCWEB_LOG_ERROR << "Failed to parse metadata: " << e.what(); ++ messages::queryParameterValueFormatError( ++ asyncResp->res, ++ std::string(error.propertyName) + " " + std::string(error.reason), ++ error.what()); + messages::internalError(asyncResp->res); } - asyncResp->res.jsonValue["Metrics"] = metrics; @@ -186,33 +233,196 @@ index a0c4f1d..7c26787 100644 } struct AddReportArgs -@@ -275,6 +280,11 @@ class AddReport + { +- std::string name; ++ struct MetricArgs ++ { ++ std::string id; ++ std::vector<std::string> uris; ++ std::optional<std::string> collectionFunction; ++ std::optional<std::string> collectionTimeScope; ++ std::optional<uint64_t> collectionDuration; ++ }; ++ ++ std::optional<std::string> id; ++ std::optional<std::string> name; + std::string reportingType; +- bool emitsReadingsUpdate = false; +- bool logToMetricReportsCollection = false; ++ std::optional<std::string> reportUpdates; ++ std::optional<uint64_t> appendLimit; ++ std::vector<std::string> reportActions; + uint64_t interval = 0; +- std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++ std::vector<MetricArgs> metrics; + }; - for (const auto& [id, uris] : args.metrics) + inline bool toDbusReportActions(crow::Response& res, +- std::vector<std::string>& actions, ++ const std::vector<std::string>& actions, + AddReportArgs& args) + { + size_t index = 0; +- for (auto& action : actions) ++ for (const auto& action : actions) + { +- if (action == "RedfishEvent") +- { +- args.emitsReadingsUpdate = true; +- } +- else if (action == "LogToMetricReportsCollection") +- { +- args.logToMetricReportsCollection = true; +- } +- else ++ std::string dbusReportAction = toDbusReportAction(action); ++ ++ if (dbusReportAction.empty()) { -+ std::vector<sdbusplus::message::object_path> dbusPaths; -+ dbusPaths.reserve(uris.size()); -+ nlohmann::json metadata; -+ metadata["MetricProperties"] = nlohmann::json::array(); + messages::propertyValueNotInList( + res, action, "ReportActions/" + std::to_string(index)); + return false; + } + - for (size_t i = 0; i < uris.size(); i++) ++ args.reportActions.emplace_back(std::move(dbusReportAction)); + index++; + } + return true; +@@ -142,23 +181,12 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + std::vector<nlohmann::json> metrics; + std::vector<std::string> reportActions; + std::optional<nlohmann::json> schedule; +- if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, +- "MetricReportDefinitionType", args.reportingType, +- "ReportActions", reportActions, "Schedule", +- schedule)) +- { +- return false; +- } +- +- constexpr const char* allowedCharactersInName = +- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; +- if (args.name.empty() || args.name.find_first_not_of( +- allowedCharactersInName) != std::string::npos) ++ if (!json_util::readJson( ++ req, res, "Id", args.id, "Name", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", args.reportingType, "ReportUpdates", ++ args.reportUpdates, "AppendLimit", args.appendLimit, ++ "ReportActions", reportActions, "Schedule", schedule)) + { +- BMCWEB_LOG_ERROR << "Failed to match " << args.name +- << " with allowed character " +- << allowedCharactersInName; +- messages::propertyValueIncorrect(res, "Id", args.name); + return false; + } + +@@ -203,15 +231,35 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + args.metrics.reserve(metrics.size()); + for (auto& m : metrics) + { +- std::string id; +- std::vector<std::string> uris; +- if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", +- uris)) ++ std::optional<std::string> collectionDurationStr; ++ AddReportArgs::MetricArgs metricArgs; ++ if (!json_util::readJson( ++ m, res, "MetricId", metricArgs.id, "MetricProperties", ++ metricArgs.uris, "CollectionFunction", ++ metricArgs.collectionFunction, "CollectionTimeScope", ++ metricArgs.collectionTimeScope, "CollectionDuration", ++ collectionDurationStr)) + { + return false; + } + +- args.metrics.emplace_back(std::move(id), std::move(uris)); ++ if (collectionDurationStr) ++ { ++ std::optional<std::chrono::milliseconds> duration = ++ time_utils::fromDurationString(*collectionDurationStr); ++ ++ if (!duration || duration->count() < 0) ++ { ++ messages::propertyValueIncorrect(res, "CollectionDuration", ++ *collectionDurationStr); ++ return false; ++ } ++ ++ metricArgs.collectionDuration = ++ static_cast<uint64_t>(duration->count()); ++ } ++ ++ args.metrics.emplace_back(std::move(metricArgs)); + } + + return true; +@@ -219,13 +267,12 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + + inline bool getChassisSensorNodeFromMetrics( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +- const std::vector<std::pair<std::string, std::vector<std::string>>>& +- metrics, ++ const std::vector<AddReportArgs::MetricArgs>& metrics, + boost::container::flat_set<std::pair<std::string, std::string>>& matched) + { +- for (const auto& [id, uris] : metrics) ++ for (const auto& metric : metrics) + { +- if (!getChassisSensorNode(asyncResp, uris, matched)) ++ if (!getChassisSensorNode(asyncResp, metric.uris, matched)) + { + return false; + } +@@ -251,11 +298,16 @@ class AddReport + telemetry::ReadingParameters readingParams; + readingParams.reserve(args.metrics.size()); + +- for (const auto& [id, uris] : args.metrics) ++ for (auto& metric : args.metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ std::vector< ++ std::tuple<sdbusplus::message::object_path, std::string>> ++ sensorParams; ++ sensorParams.reserve(metric.uris.size()); ++ ++ for (size_t i = 0; i < metric.uris.size(); i++) { - const std::string& uri = uris[i]; -@@ -291,8 +301,12 @@ class AddReport +- const std::string& uri = uris[i]; ++ const std::string& uri = metric.uris[i]; + auto el = uriToDbus.find(uri); + if (el == uriToDbus.end()) + { +@@ -269,17 +321,23 @@ class AddReport } const std::string& dbusPath = el->second; - readingParams.emplace_back(dbusPath, "SINGLE", id, uri); -+ dbusPaths.emplace_back(dbusPath); -+ metadata["MetricProperties"].emplace_back(uri); ++ sensorParams.emplace_back(dbusPath, uri); } + -+ readingParams.emplace_back(dbusPaths, "SINGLE", id, metadata.dump(), -+ "Point", 0u); ++ readingParams.emplace_back( ++ std::move(sensorParams), metric.collectionFunction.value_or(""), ++ std::move(metric.id), metric.collectionTimeScope.value_or(""), ++ metric.collectionDuration.value_or(0u)); } const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp; crow::connections::systemBus->async_method_call( -@@ -330,10 +344,10 @@ class AddReport +- [aResp, name = args.name, uriToDbus = std::move(uriToDbus)]( ++ [aResp, id = args.id.value_or(""), ++ uriToDbus = std::move(uriToDbus)]( + const boost::system::error_code ec, const std::string&) { + if (ec == boost::system::errc::file_exists) + { + messages::resourceAlreadyExists( +- aResp->res, "MetricReportDefinition", "Id", name); ++ aResp->res, "MetricReportDefinition", "Id", id); + return; + } + if (ec == boost::system::errc::too_many_files_open) +@@ -308,10 +366,12 @@ class AddReport messages::created(aResp->res); }, telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", @@ -221,81 +431,50 @@ index a0c4f1d..7c26787 100644 - args.emitsReadingsUpdate, args.logToMetricReportsCollection, - args.interval, readingParams); + "xyz.openbmc_project.Telemetry.ReportManager", -+ "AddReportFutureVersion", "TelemetryService/" + args.name, -+ args.reportingType, args.emitsReadingsUpdate, -+ args.logToMetricReportsCollection, args.interval, readingParams); ++ "AddReportFutureVersion", ++ "TelemetryService/" + args.id.value_or(""), args.name.value_or(""), ++ args.reportingType, args.reportUpdates.value_or("Overwrite"), ++ args.appendLimit.value_or(0), args.reportActions, args.interval, ++ readingParams); } void insert(const boost::container::flat_map<std::string, std::string>& el) -@@ -415,37 +429,39 @@ inline void requestRoutesMetricReportDefinition(App& app) - BMCWEB_ROUTE(app, - "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") - .privileges(redfish::privileges::getMetricReportDefinition) -- .methods(boost::beast::http::verb::get)( -- [](const crow::Request&, -- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, -- const std::string& id) { +@@ -399,12 +459,15 @@ inline void requestRoutesMetricReportDefinition(App& app) + [](const crow::Request&, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& id) { - crow::connections::systemBus->async_method_call( -- [asyncResp, id]( -- const boost::system::error_code ec, -- const std::vector<std::pair< -- std::string, -- std::variant<std::string, bool, uint64_t, -- telemetry::ReadingParameters>>>& ret) { -- if (ec.value() == EBADR || -- ec == boost::system::errc::host_unreachable) -- { -- messages::resourceNotFound( -- asyncResp->res, "MetricReportDefinition", id); -- return; -- } -- if (ec) -- { -- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -- messages::internalError(asyncResp->res); -- return; -- } -+ .methods( -+ boost::beast::http::verb::get)([](const crow::Request&, -+ const std::shared_ptr< -+ bmcweb::AsyncResp>& asyncResp, -+ const std::string& id) { -+ sdbusplus::asio::getAllProperties( -+ *crow::connections::systemBus, telemetry::service, -+ telemetry::getDbusReportPath(id), telemetry::reportInterface, -+ [asyncResp, -+ id](boost::system::error_code ec, -+ const std::vector<std::pair< -+ std::string, -+ std::variant<std::monostate, std::string, bool, -+ uint64_t, telemetry::ReadingParameters>>>& -+ properties) { -+ if (ec.value() == EBADR || -+ ec == boost::system::errc::host_unreachable) -+ { -+ messages::resourceNotFound( -+ asyncResp->res, "MetricReportDefinition", id); -+ return; -+ } -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ telemetry::fillReportDefinition(asyncResp, id, properties); -+ }); -+ }); ++ sdbusplus::asio::getAllProperties( ++ *crow::connections::systemBus, telemetry::service, ++ telemetry::getDbusReportPath(id), ++ telemetry::reportInterface, + [asyncResp, +- id](const boost::system::error_code ec, ++ id](boost::system::error_code ec, + const std::vector<std::pair< + std::string, dbus::utility::DbusVariantType>>& +- ret) { ++ properties) { + if (ec.value() == EBADR || + ec == boost::system::errc::host_unreachable) + { +@@ -419,12 +482,11 @@ inline void requestRoutesMetricReportDefinition(App& app) + return; + } - telemetry::fillReportDefinition(asyncResp, id, ret); - }, - telemetry::service, telemetry::getDbusReportPath(id), - "org.freedesktop.DBus.Properties", "GetAll", - telemetry::reportInterface); -- }); ++ telemetry::fillReportDefinition(asyncResp, id, ++ properties); ++ }); + }); ++ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) -- 2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-MetricDefinition-property-in-MetricReport.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-MetricDefinition-property-in-MetricReport.patch deleted file mode 100644 index bf5a09d9d..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-MetricDefinition-property-in-MetricReport.patch +++ /dev/null @@ -1,268 +0,0 @@ -From dab3c96f9e39a89d7c359e22655650c7c16952ec Mon Sep 17 00:00:00 2001 -From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Date: Tue, 12 Oct 2021 08:06:13 +0000 -Subject: [PATCH] Add support for MetricDefinition property in MetricReport - -Added MetricDefinition as part of MetricValues array returned by -MetricReport. It contains single @odata.id with URI to proper -MetricDefinition resource - depending on MetricProperty. - -Testing done: -- GET request on redfish/v1/TelemetryService/MetricReports - got response with MetricDefinition and proper id inside - MetricValues array. - -Testing steps: -1. POST on redfish/v1/TelemetryService/MetricReportDefinitions - with body: -{ - "Id": "PeriodicReport_1", - "MetricReportDefinitionType": "Periodic", - "ReportActions": [ - "LogToMetricReportsCollection", - "RedfishEvent" - ], - "Metrics": [ - { - "MetricId": "sensor_1", - "MetricProperties": [ - "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/1/Reading" - ] - } - ], - "Schedule": { - "RecurrenceInterval": "PT10S" - } -} - -2. GET on redfish/v1/TelemetryService/MetricReports/PeriodicReport_1 - should return: -{ - "@odata.id": - "/redfish/v1/TelemetryService/MetricReports/PeriodicReport_1", - "@odata.type": "#MetricReport.v1_3_0.MetricReport", - "Id": "PeriodicReport_1", - "MetricReportDefinition": { - "@odata.id": - "/redfish/v1/TelemetryService/MetricReportDefinitions/PeriodicReport_1" - }, - "MetricValues": [ - { - "MetricDefinition": { - "@odata.id": - "/redfish/v1/TelemetryService/MetricDefinitions/Rotational" - }, - "MetricId": "sensor_1", - "MetricProperty": - "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/1/Reading", - "MetricValue": "nan", - "Timestamp": "1970-01-01T00:03:21+00:00" - } - ], - "Name": "PeriodicReport_1", - "Timestamp": "1970-01-01T00:03:21+00:00" -} - -Change-Id: I7181c612f9b443015d551259bae25303aa436822 -Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> ---- - meson.build | 4 +- - .../include/utils/telemetry_utils.hpp | 40 ++++++++++++ - redfish-core/lib/metric_report.hpp | 64 +++++++++++++++---- - redfish-core/lib/sensors.hpp | 2 + - 4 files changed, 95 insertions(+), 15 deletions(-) - -diff --git a/meson.build b/meson.build -index 6b6a8ab..218ea49 100644 ---- a/meson.build -+++ b/meson.build -@@ -377,6 +377,8 @@ srcfiles_unittest = [ - 'http/ut/utility_test.cpp' - ] - -+srcfiles_unittest_dependencies = ['redfish-core/src/error_messages.cpp', 'src/boost_url.cpp'] -+ - # Gather the Configuration data - - conf_data = configuration_data() -@@ -434,7 +436,7 @@ executable('bmcweb',srcfiles_bmcweb, - if(get_option('tests').enabled()) - foreach src_test : srcfiles_unittest - testname = src_test.split('/')[-1].split('.')[0] -- test(testname,executable(testname,src_test, -+ test(testname,executable(testname,[src_test] + srcfiles_unittest_dependencies, - include_directories : incdir, - install_dir: bindir, - dependencies: [ -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index 1b4f75d..c0c5ba3 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -17,6 +17,46 @@ constexpr const char* metricReportDefinitionUri = - constexpr const char* metricReportUri = - "/redfish/v1/TelemetryService/MetricReports/"; - -+inline std::optional<nlohmann::json> -+ getMetadataJson(const std::string& metadataStr) -+{ -+ std::optional<nlohmann::json> res = -+ nlohmann::json::parse(metadataStr, nullptr, false); -+ if (res->is_discarded()) -+ { -+ BMCWEB_LOG_ERROR << "Malformed reading metatadata JSON provided by " -+ "telemetry service."; -+ return std::nullopt; -+ } -+ return res; -+} -+ -+inline std::optional<std::string> -+ readStringFromMetadata(const nlohmann::json& metadataJson, const char* key) -+{ -+ std::optional<std::string> res; -+ if (auto it = metadataJson.find(key); it != metadataJson.end()) -+ { -+ if (const std::string* value = it->get_ptr<const std::string*>()) -+ { -+ res = *value; -+ } -+ else -+ { -+ BMCWEB_LOG_ERROR << "Incorrect reading metatadata JSON provided by " -+ "telemetry service. Missing key '" -+ << key << "'."; -+ } -+ } -+ else -+ { -+ BMCWEB_LOG_ERROR << "Incorrect reading metatadata JSON provided by " -+ "telemetry service. Key '" -+ << key << "' has a wrong type."; -+ } -+ return res; -+} -+ - inline void - getReportCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& uri) -diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 7fe281d..13bf792 100644 ---- a/redfish-core/lib/metric_report.hpp -+++ b/redfish-core/lib/metric_report.hpp -@@ -1,5 +1,6 @@ - #pragma once - -+#include "sensors.hpp" - #include "utils/telemetry_utils.hpp" - - #include <app.hpp> -@@ -15,34 +16,56 @@ using Readings = - std::vector<std::tuple<std::string, std::string, double, uint64_t>>; - using TimestampReadings = std::tuple<uint64_t, Readings>; - --inline nlohmann::json toMetricValues(const Readings& readings) -+inline bool fillMetricValues(nlohmann::json& metricValues, -+ const Readings& readings) - { -- nlohmann::json metricValues = nlohmann::json::array_t(); -- -- for (auto& [id, metadata, sensorValue, timestamp] : readings) -+ for (auto& [id, metadataStr, sensorValue, timestamp] : readings) - { -+ std::optional<nlohmann::json> readingMetadataJson = -+ getMetadataJson(metadataStr); -+ if (!readingMetadataJson) -+ { -+ return false; -+ } -+ -+ std::optional<std::string> sensorDbusPath = -+ readStringFromMetadata(*readingMetadataJson, "SensorDbusPath"); -+ if (!sensorDbusPath) -+ { -+ return false; -+ } -+ -+ std::optional<std::string> sensorRedfishUri = -+ readStringFromMetadata(*readingMetadataJson, "SensorRedfishUri"); -+ if (!sensorRedfishUri) -+ { -+ return false; -+ } -+ -+ std::string metricDefinition = -+ std::string(metricDefinitionUri) + -+ sensors::toReadingType( -+ sdbusplus::message::object_path(*sensorDbusPath) -+ .parent_path() -+ .filename()); -+ - metricValues.push_back({ -+ {"MetricDefinition", -+ nlohmann::json{{"@odata.id", metricDefinition}}}, - {"MetricId", id}, -- {"MetricProperty", metadata}, -+ {"MetricProperty", *sensorRedfishUri}, - {"MetricValue", std::to_string(sensorValue)}, - {"Timestamp", - crow::utility::getDateTime(static_cast<time_t>(timestamp))}, - }); - } - -- return metricValues; -+ return true; - } - - inline bool fillReport(nlohmann::json& json, const std::string& id, - const std::variant<TimestampReadings>& var) - { -- json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; -- json["@odata.id"] = telemetry::metricReportUri + id; -- json["Id"] = id; -- json["Name"] = id; -- json["MetricReportDefinition"]["@odata.id"] = -- telemetry::metricReportDefinitionUri + id; -- - const TimestampReadings* timestampReadings = - std::get_if<TimestampReadings>(&var); - if (!timestampReadings) -@@ -52,9 +75,22 @@ inline bool fillReport(nlohmann::json& json, const std::string& id, - } - - const auto& [timestamp, readings] = *timestampReadings; -+ nlohmann::json metricValues = nlohmann::json::array(); -+ if (!fillMetricValues(metricValues, readings)) -+ { -+ return false; -+ } -+ -+ json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; -+ json["@odata.id"] = telemetry::metricReportUri + id; -+ json["Id"] = id; -+ json["Name"] = id; -+ json["MetricReportDefinition"]["@odata.id"] = -+ telemetry::metricReportDefinitionUri + id; - json["Timestamp"] = - crow::utility::getDateTime(static_cast<time_t>(timestamp)); -- json["MetricValues"] = toMetricValues(readings); -+ json["MetricValues"] = metricValues; -+ - return true; - } - } // namespace telemetry -diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp -index 7405e5a..9850b24 100644 ---- a/redfish-core/lib/sensors.hpp -+++ b/redfish-core/lib/sensors.hpp -@@ -21,6 +21,8 @@ - #include <boost/container/flat_map.hpp> - #include <boost/range/algorithm/replace_copy_if.hpp> - #include <dbus_singleton.hpp> -+#include <dbus_utility.hpp> -+#include <error_messages.hpp> - #include <registries/privilege_registry.hpp> - #include <utils/json_utils.hpp> - --- -2.25.1 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-GET-method-for-TriggerCollection.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-GET-method-for-TriggerCollection.patch deleted file mode 100644 index 0646aba5c..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-GET-method-for-TriggerCollection.patch +++ /dev/null @@ -1,313 +0,0 @@ -From a1e89d356ba5ed594a1494efe8257946e1062396 Mon Sep 17 00:00:00 2001 -From: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> -Date: Tue, 31 Aug 2021 14:35:31 +0200 -Subject: [PATCH] Add GET method for TriggerCollection - -Added GET method for retrieving list of Triggers from Telemetry service - -Tested: -- Added single Trigger and requested result from bmcweb via - /redfish/v1/TelemetryService/Triggers -- Added multiple Triggers numeric and discrete, and requested results - from bmcweb via /redfish/v1/TelemetryService/Triggers -- Verified uri /redfish/v1/TelemetryService/Triggers by using - Redfish-Service-Validator (all passed) - -Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> -Change-Id: Ide00eb44901ea1b97b80fc5c5ddfd97e393d4a04 ---- - redfish-core/include/redfish.hpp | 2 + - .../include/utils/telemetry_utils.hpp | 40 ++++++++--- - redfish-core/lib/metric_report.hpp | 6 +- - redfish-core/lib/metric_report_definition.hpp | 6 +- - redfish-core/lib/trigger.hpp | 31 ++++++++ - scripts/update_schemas.py | 1 + - static/redfish/v1/$metadata/index.xml | 3 + - .../v1/schema/TriggersCollection_v1.xml | 70 +++++++++++++++++++ - 8 files changed, 144 insertions(+), 15 deletions(-) - create mode 100644 redfish-core/lib/trigger.hpp - create mode 100644 static/redfish/v1/schema/TriggersCollection_v1.xml - -diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp -index 9fb0ffe..99b3fe6 100644 ---- a/redfish-core/include/redfish.hpp -+++ b/redfish-core/include/redfish.hpp -@@ -42,6 +42,7 @@ - #include "../lib/task.hpp" - #include "../lib/telemetry_service.hpp" - #include "../lib/thermal.hpp" -+#include "../lib/trigger.hpp" - #include "../lib/update_service.hpp" - #include "../lib/virtual_media.hpp" - -@@ -197,6 +198,7 @@ class RedfishService - - hypervisor::requestRoutesHypervisorSystems(app); - -+ requestRoutesTriggerCollection(app); - requestRoutesTelemetryService(app); - requestRoutesMetricReportDefinitionCollection(app); - requestRoutesMetricReportDefinition(app); -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index c0c5ba3..df1aa68 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -9,6 +9,8 @@ namespace telemetry - { - - constexpr const char* service = "xyz.openbmc_project.Telemetry"; -+constexpr const char* reportSubtree = -+ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"; - constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; - constexpr const char* metricDefinitionUri = - "/redfish/v1/TelemetryService/MetricDefinitions/"; -@@ -16,6 +18,11 @@ constexpr const char* metricReportDefinitionUri = - "/redfish/v1/TelemetryService/MetricReportDefinitions/"; - constexpr const char* metricReportUri = - "/redfish/v1/TelemetryService/MetricReports/"; -+constexpr const char* triggerSubtree = -+ "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"; -+constexpr const char* triggerInterface = -+ "xyz.openbmc_project.Telemetry.Trigger"; -+constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers/"; - - inline std::optional<nlohmann::json> - getMetadataJson(const std::string& metadataStr) -@@ -57,15 +64,27 @@ inline std::optional<std::string> - return res; - } - --inline void -- getReportCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, -- const std::string& uri) -+struct CollectionParams - { -- const std::array<const char*, 1> interfaces = {reportInterface}; -+ const char* subtree; -+ int depth; -+ std::array<const char*, 1> interfaces; - -+ CollectionParams() = delete; -+ CollectionParams(const char* st, int dp, -+ const std::array<const char*, 1>& ifaces) : -+ subtree{st}, -+ depth{dp}, interfaces{ifaces} -+ {} -+}; -+ -+inline void getCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, -+ const std::string& uri, -+ const CollectionParams& params) -+{ - crow::connections::systemBus->async_method_call( - [asyncResp, uri](const boost::system::error_code ec, -- const std::vector<std::string>& reports) { -+ const std::vector<std::string>& items) { - if (ec == boost::system::errc::io_error) - { - asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); -@@ -82,13 +101,13 @@ inline void - nlohmann::json& members = asyncResp->res.jsonValue["Members"]; - members = nlohmann::json::array(); - -- for (const std::string& report : reports) -+ for (const std::string& item : items) - { -- sdbusplus::message::object_path path(report); -+ sdbusplus::message::object_path path(item); - std::string name = path.filename(); - if (name.empty()) - { -- BMCWEB_LOG_ERROR << "Received invalid path: " << report; -+ BMCWEB_LOG_ERROR << "Received invalid path: " << item; - messages::internalError(asyncResp->res); - return; - } -@@ -99,9 +118,8 @@ inline void - }, - "xyz.openbmc_project.ObjectMapper", - "/xyz/openbmc_project/object_mapper", -- "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", -- "/xyz/openbmc_project/Telemetry/Reports/TelemetryService", 1, -- interfaces); -+ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", params.subtree, -+ params.depth, params.interfaces); - } - - inline std::string getDbusReportPath(const std::string& id) -diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 13bf792..ea4cd62 100644 ---- a/redfish-core/lib/metric_report.hpp -+++ b/redfish-core/lib/metric_report.hpp -@@ -108,8 +108,10 @@ inline void requestRoutesMetricReportCollection(App& app) - "/redfish/v1/TelemetryService/MetricReports"; - asyncResp->res.jsonValue["Name"] = "Metric Report Collection"; - -- telemetry::getReportCollection(asyncResp, -- telemetry::metricReportUri); -+ telemetry::getCollection( -+ asyncResp, telemetry::metricReportUri, -+ telemetry::CollectionParams(telemetry::reportSubtree, 1, -+ {telemetry::reportInterface})); - }); - } - -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index 7c26787..c97a1df 100644 ---- a/redfish-core/lib/metric_report_definition.hpp -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -377,8 +377,10 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) - asyncResp->res.jsonValue["Name"] = - "Metric Definition Collection"; - -- telemetry::getReportCollection( -- asyncResp, telemetry::metricReportDefinitionUri); -+ telemetry::getCollection( -+ asyncResp, telemetry::metricReportDefinitionUri, -+ telemetry::CollectionParams(telemetry::reportSubtree, 1, -+ {telemetry::reportInterface})); - }); - - BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") -diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp -new file mode 100644 -index 0000000..681b3b4 ---- /dev/null -+++ b/redfish-core/lib/trigger.hpp -@@ -0,0 +1,31 @@ -+#pragma once -+ -+#include "utils/telemetry_utils.hpp" -+ -+#include <app.hpp> -+#include <registries/privilege_registry.hpp> -+ -+namespace redfish -+{ -+ -+inline void requestRoutesTriggerCollection(App& app) -+{ -+ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") -+ .privileges(redfish::privileges::getTriggersCollection) -+ .methods(boost::beast::http::verb::get)( -+ [](const crow::Request&, -+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { -+ asyncResp->res.jsonValue["@odata.type"] = -+ "#TriggersCollection.TriggersCollection"; -+ asyncResp->res.jsonValue["@odata.id"] = -+ "/redfish/v1/TelemetryService/Triggers"; -+ asyncResp->res.jsonValue["Name"] = "Triggers Collection"; -+ -+ telemetry::getCollection( -+ asyncResp, telemetry::triggerUri, -+ telemetry::CollectionParams(telemetry::triggerSubtree, 1, -+ {telemetry::triggerInterface})); -+ }); -+} -+ -+} // namespace redfish -diff --git a/scripts/update_schemas.py b/scripts/update_schemas.py -index dd39278..d66a59a 100755 ---- a/scripts/update_schemas.py -+++ b/scripts/update_schemas.py -@@ -93,6 +93,7 @@ include_list = [ - 'TaskService', - 'TelemetryService', - 'Thermal', -+ 'TriggersCollection', - 'UpdateService', - 'VLanNetworkInterfaceCollection', - 'VLanNetworkInterface', -diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml -index 876ebfb..75e3dd4 100644 ---- a/static/redfish/v1/$metadata/index.xml -+++ b/static/redfish/v1/$metadata/index.xml -@@ -2215,6 +2215,9 @@ - <edmx:Include Namespace="Thermal.v1_7_0"/> - <edmx:Include Namespace="Thermal.v1_7_1"/> - </edmx:Reference> -+ <edmx:Reference Uri="/redfish/v1/schema/TriggersCollection_v1.xml"> -+ <edmx:Include Namespace="TriggersCollection"/> -+ </edmx:Reference> - <edmx:Reference Uri="/redfish/v1/schema/UpdateService_v1.xml"> - <edmx:Include Namespace="UpdateService"/> - <edmx:Include Namespace="UpdateService.v1_0_0"/> -diff --git a/static/redfish/v1/schema/TriggersCollection_v1.xml b/static/redfish/v1/schema/TriggersCollection_v1.xml -new file mode 100644 -index 0000000..399bebd ---- /dev/null -+++ b/static/redfish/v1/schema/TriggersCollection_v1.xml -@@ -0,0 +1,70 @@ -+<?xml version="1.0" encoding="UTF-8"?> -+<!----> -+<!--################################################################################ --> -+<!--# Redfish Schema: TriggerSetCollection --> -+<!--# --> -+<!--# For a detailed change log, see the README file contained in the DSP8010 bundle, --> -+<!--# available at http://www.dmtf.org/standards/redfish --> -+<!--# Copyright 2014-2021 DMTF. --> -+<!--# For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright --> -+<!--################################################################################ --> -+<!----> -+<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> -+ -+ <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/csd01/complete/vocabularies/Org.OData.Core.V1.xml"> -+ <edmx:Include Namespace="Org.OData.Core.V1" Alias="OData"/> -+ </edmx:Reference> -+ <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/csd01/complete/vocabularies/Org.OData.Capabilities.V1.xml"> -+ <edmx:Include Namespace="Org.OData.Capabilities.V1" Alias="Capabilities"/> -+ </edmx:Reference> -+ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/Resource_v1.xml"> -+ <edmx:Include Namespace="Resource.v1_0_0"/> -+ </edmx:Reference> -+ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/RedfishExtensions_v1.xml"> -+ <edmx:Include Namespace="RedfishExtensions.v1_0_0" Alias="Redfish"/> -+ </edmx:Reference> -+ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/Triggers_v1.xml"> -+ <edmx:Include Namespace="Triggers"/> -+ </edmx:Reference> -+ -+ <edmx:DataServices> -+ -+ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="TriggersCollection"> -+ <Annotation Term="Redfish.OwningEntity" String="DMTF"/> -+ -+ <EntityType Name="TriggersCollection" BaseType="Resource.v1_0_0.ResourceCollection"> -+ <Annotation Term="OData.Description" String="The collection of Triggers resource instances."/> -+ <Annotation Term="OData.LongDescription" String="This resource shall represent a resource collection of Triggers instances for a Redfish implementation."/> -+ <Annotation Term="Capabilities.InsertRestrictions"> -+ <Record> -+ <PropertyValue Property="Insertable" Bool="true"/> -+ <Annotation Term="OData.Description" String="Create triggers through a POST to the trigger collection."/> -+ </Record> -+ </Annotation> -+ <Annotation Term="Capabilities.UpdateRestrictions"> -+ <Record> -+ <PropertyValue Property="Updatable" Bool="false"/> -+ </Record> -+ </Annotation> -+ <Annotation Term="Capabilities.DeleteRestrictions"> -+ <Record> -+ <PropertyValue Property="Deletable" Bool="false"/> -+ </Record> -+ </Annotation> -+ <Annotation Term="Redfish.Uris"> -+ <Collection> -+ <String>/redfish/v1/TelemetryService/Triggers</String> -+ </Collection> -+ </Annotation> -+ <NavigationProperty Name="Members" Type="Collection(Triggers.Triggers)"> -+ <Annotation Term="OData.Permissions" EnumMember="OData.Permission/Read"/> -+ <Annotation Term="OData.Description" String="The members of this collection."/> -+ <Annotation Term="OData.LongDescription" String="This property shall contain an array of links to the members of this collection."/> -+ <Annotation Term="OData.AutoExpandReferences"/> -+ <Annotation Term="Redfish.Required"/> -+ </NavigationProperty> -+ </EntityType> -+ -+ </Schema> -+ </edmx:DataServices> -+</edmx:Edmx> --- -2.25.1 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-event-service-fix-added-Context-field-to-response.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-event-service-fix-added-Context-field-to-response.patch deleted file mode 100644 index ffab743f6..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-event-service-fix-added-Context-field-to-response.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0ca8c383db8c9afbce63380955a20ada0acc20b7 Mon Sep 17 00:00:00 2001 -From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Date: Wed, 2 Jun 2021 12:44:43 +0000 -Subject: [PATCH] event service fix, added Context field to response - -Tested: - - Context field is present - - No regression detected - -Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> ---- - redfish-core/include/event_service_manager.hpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp -index 2b957ea..289886b 100644 ---- a/redfish-core/include/event_service_manager.hpp -+++ b/redfish-core/include/event_service_manager.hpp -@@ -556,6 +556,7 @@ class Subscription - << id; - return; - } -+ msg["Context"] = customText; - - this->sendEvent( - msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); --- -2.25.1 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0009-Add-support-for-deleting-terminated-subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0009-Add-support-for-deleting-terminated-subscriptions.patch deleted file mode 100644 index 548e3d9c6..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0009-Add-support-for-deleting-terminated-subscriptions.patch +++ /dev/null @@ -1,46 +0,0 @@ -From ef83a4fb14648edc6c8370363ff88fb6f060a43b Mon Sep 17 00:00:00 2001 -From: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> -Date: Mon, 20 Sep 2021 21:55:57 +0530 -Subject: [PATCH] Add support for deleting terminated subscriptions - -Added functionality to delete/remove event subscription(s) which are -configured to Terminate after retries. - -Currently, when an Event is subscribed with Retry Policy as -"TerminateAfterRetries", the state of the connection is set to -"Terminated" after retrying, but the Subscription is not removed. -This commit adds the functionality to detect terminated connection and -remove the respective subscription. - -This commit adds this check for metric reports. - -Tested: - - Created a Subscription with - DeliveryRetryPolicy: "TerminateAfterRetries" - - Received Events successfully on Event listener - - Once the Event listener was stopped, the Subscription was - removed/deleted after retries. - -Change-Id: I3cb0af5bc24411cddcdb3d1d9de25e8e9144106c -Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> ---- - redfish-core/include/event_service_manager.hpp | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp -index c9e2812..c2fefb3 100644 ---- a/redfish-core/include/event_service_manager.hpp -+++ b/redfish-core/include/event_service_manager.hpp -@@ -1535,6 +1535,9 @@ class EventServiceManager - - std::variant<telemetry::TimestampReadings>& readings = - found->second; -+ -+ this->deleteTerminatedSubcriptions(); -+ - for (const auto& it : - EventServiceManager::getInstance().subscriptionsMap) - { --- -2.17.1 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README index 90916ecec..541fa6aba 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -2,29 +2,11 @@ These patches are mirror of upstream TelemetryService implementation. Until change is integrated they will be manually merged here to enable feature in Intel builds. Current revisions: -- Add support for MetricDefinition scheme - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/102 - -- Sync Telmetry service with EventService - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/38798/53 - -- Switched bmcweb to use new telemetry service API - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44270/19 - -- Add support for MetricDefinition property in MetricReport - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44512/24 - -- Add GET method for TriggerCollection - file://telemetry/0005-Add-GET-method-for-TriggerCollection.patch +- Add support for POST on TriggersCollection + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44935/19 - LogService field, actual implementation will be upstreamed with triggers feature - file://telemetry/0006-Revert-Remove-LogService-from-TelemetryService.patch - -- Event service fix for Context field - file://telemetry/0007-event-service-fix-added-Context-field-to-response.patch + file://telemetry/0002-Revert-Remove-LogService-from-TelemetryService.patch -- Generalize ReadingType in MetricDefinition - file://telemetry/0008-Generalize-ReadingType-in-MetricDefinition.patch - -- Add support for deleting terminated subscriptions - file://telemetry/0009-Add-support-for-deleting-terminated-subscriptions.patch +- Switched bmcweb to use new telemetry service API + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44270/28 |