diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry')
5 files changed, 609 insertions, 1502 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch deleted file mode 100644 index 208831338..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch +++ /dev/null @@ -1,717 +0,0 @@ -From d50e4ce193703c008d3293acd03e1c0542c0c215 Mon Sep 17 00:00:00 2001 -From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Mon, 27 Apr 2020 17:24:15 +0200 -Subject: [PATCH] Redfish TelemetryService schema implementation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Added TelemetryService, MetricReports, MetricReportCollection, -MetricReportDefinition and MetricReportDefinitionCollection schemas -with GET method support. Added TelemetryService URI to root service. -Implemented communication with backend - Telemetry. -Added schemes attributes that are supported by Telemetry service -design. User is able to fetch basic information about reports if -Telemetry service is present in OpenBMC. -Added util function that converts decimal value into duration format -that is described by ISO 8601 and Redfish specification. - -Tested: - - Succesfully passed RedfishServiceValidator.py - - Verified DBus method calls to Telemetry service - - Verified all possible pages that are displayed to user when: - - Reports are fully defined in Telemetry - - Reports are partially available in Telemetry - - Telemetry is disabled - - Verified time_utils::toDurationString() output - -Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> -Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com> -Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Change-Id: Ie6b0b49f4ef5eeaef07d1209b6c349270c04d570 ---- - redfish-core/include/redfish.hpp | 10 + - .../include/utils/telemetry_utils.hpp | 71 +++++++ - redfish-core/include/utils/time_utils.hpp | 78 ++++++++ - redfish-core/lib/metric_report.hpp | 162 +++++++++++++++ - redfish-core/lib/metric_report_definition.hpp | 186 ++++++++++++++++++ - redfish-core/lib/service_root.hpp | 2 + - redfish-core/lib/telemetry_service.hpp | 93 +++++++++ - 7 files changed, 602 insertions(+) - create mode 100644 redfish-core/include/utils/telemetry_utils.hpp - create mode 100644 redfish-core/include/utils/time_utils.hpp - create mode 100644 redfish-core/lib/metric_report.hpp - create mode 100644 redfish-core/lib/metric_report_definition.hpp - create mode 100644 redfish-core/lib/telemetry_service.hpp - -diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp -index dabf78e..b366e24 100644 ---- a/redfish-core/include/redfish.hpp -+++ b/redfish-core/include/redfish.hpp -@@ -25,6 +25,8 @@ - #include "../lib/managers.hpp" - #include "../lib/memory.hpp" - #include "../lib/message_registries.hpp" -+#include "../lib/metric_report.hpp" -+#include "../lib/metric_report_definition.hpp" - #include "../lib/network_protocol.hpp" - #include "../lib/pcie.hpp" - #include "../lib/power.hpp" -@@ -36,6 +38,7 @@ - #include "../lib/storage.hpp" - #include "../lib/systems.hpp" - #include "../lib/task.hpp" -+#include "../lib/telemetry_service.hpp" - #include "../lib/thermal.hpp" - #include "../lib/update_service.hpp" - #ifdef BMCWEB_ENABLE_VM_NBDPROXY -@@ -212,6 +215,13 @@ class RedfishService - nodes.emplace_back(std::make_unique<HypervisorInterface>(app)); - nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); - -+ nodes.emplace_back(std::make_unique<TelemetryService>(app)); -+ nodes.emplace_back( -+ std::make_unique<MetricReportDefinitionCollection>(app)); -+ nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); -+ nodes.emplace_back(std::make_unique<MetricReportCollection>(app)); -+ nodes.emplace_back(std::make_unique<MetricReport>(app)); -+ - for (const auto& node : nodes) - { - node->initPrivileges(); -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -new file mode 100644 -index 0000000..8caee2d ---- /dev/null -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -0,0 +1,71 @@ -+#pragma once -+ -+namespace redfish -+{ -+ -+namespace telemetry -+{ -+ -+constexpr const char* service = "xyz.openbmc_project.Telemetry"; -+constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; -+constexpr const char* metricReportDefinitionUri = -+ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; -+constexpr const char* metricReportUri = -+ "/redfish/v1/TelemetryService/MetricReports/"; -+ -+inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& uri) -+{ -+ const std::array<const char*, 1> interfaces = {reportInterface}; -+ -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, uri](const boost::system::error_code ec, -+ const std::vector<std::string>& reportPaths) { -+ if (ec) -+ { -+ asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); -+ asyncResp->res.jsonValue["Members@odata.count"] = 0; -+ return; -+ } -+ -+ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; -+ members = nlohmann::json::array(); -+ -+ for (const std::string& path : reportPaths) -+ { -+ std::size_t pos = path.rfind('/'); -+ if (pos == std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ if (path.size() <= (pos + 1)) -+ { -+ BMCWEB_LOG_ERROR << "Failed to parse path " << path; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); -+ } -+ -+ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); -+ }, -+ "xyz.openbmc_project.ObjectMapper", -+ "/xyz/openbmc_project/object_mapper", -+ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", -+ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService", 1, -+ interfaces); -+} -+ -+inline std::string getDbusReportPath(const std::string& id) -+{ -+ std::string path = -+ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id; -+ dbus::utility::escapePathForDbus(path); -+ return path; -+} -+ -+} // namespace telemetry -+} // namespace redfish -diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp -new file mode 100644 -index 0000000..dd4ea75 ---- /dev/null -+++ b/redfish-core/include/utils/time_utils.hpp -@@ -0,0 +1,78 @@ -+#pragma once -+ -+#include <chrono> -+#include <string> -+ -+namespace redfish -+{ -+ -+namespace time_utils -+{ -+ -+namespace details -+{ -+ -+inline void leftZeroPadding(std::string& str, const std::size_t padding) -+{ -+ if (str.size() < padding) -+ { -+ str.insert(0, padding - str.size(), '0'); -+ } -+} -+} // namespace details -+ -+/** -+ * @brief Convert time value into duration format that is based on ISO 8601. -+ * Example output: "P12DT1M5.5S" -+ * Ref: Redfish Specification, Section 9.4.4. Duration values -+ */ -+std::string toDurationString(std::chrono::milliseconds ms) -+{ -+ if (ms < std::chrono::milliseconds::zero()) -+ { -+ return ""; -+ } -+ -+ std::string fmt; -+ fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); -+ -+ using Days = std::chrono::duration<long, std::ratio<24 * 60 * 60>>; -+ Days days = std::chrono::floor<Days>(ms); -+ ms -= days; -+ -+ std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); -+ ms -= hours; -+ -+ std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms); -+ ms -= minutes; -+ -+ std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms); -+ ms -= seconds; -+ -+ fmt = "P"; -+ if (days.count() > 0) -+ { -+ fmt += std::to_string(days.count()) + "D"; -+ } -+ fmt += "T"; -+ if (hours.count() > 0) -+ { -+ fmt += std::to_string(hours.count()) + "H"; -+ } -+ if (minutes.count() > 0) -+ { -+ fmt += std::to_string(minutes.count()) + "M"; -+ } -+ if (seconds.count() != 0 || ms.count() != 0) -+ { -+ fmt += std::to_string(seconds.count()) + "."; -+ std::string msStr = std::to_string(ms.count()); -+ details::leftZeroPadding(msStr, 3); -+ fmt += msStr + "S"; -+ } -+ -+ return fmt; -+} -+ -+} // namespace time_utils -+} // namespace redfish -diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -new file mode 100644 -index 0000000..050304c ---- /dev/null -+++ b/redfish-core/lib/metric_report.hpp -@@ -0,0 +1,162 @@ -+#pragma once -+ -+#include "node.hpp" -+#include "utils/telemetry_utils.hpp" -+ -+namespace redfish -+{ -+ -+class MetricReportCollection : public Node -+{ -+ public: -+ MetricReportCollection(App& app) : -+ Node(app, "/redfish/v1/TelemetryService/MetricReports/") -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>&) override -+ { -+ res.jsonValue["@odata.type"] = -+ "#MetricReportCollection.MetricReportCollection"; -+ res.jsonValue["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricReports"; -+ res.jsonValue["Name"] = "Metric Report Collection"; -+ -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ telemetry::getReportCollection(asyncResp, telemetry::metricReportUri); -+ } -+}; -+ -+class MetricReport : public Node -+{ -+ public: -+ MetricReport(App& app) : -+ Node(app, "/redfish/v1/TelemetryService/MetricReports/<str>/", -+ std::string()) -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>& params) override -+ { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ -+ if (params.size() != 1) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ const std::string& id = params[0]; -+ const std::string reportPath = telemetry::getDbusReportPath(id); -+ -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, id, reportPath](const boost::system::error_code& ec) { -+ if (ec.value() == EBADR) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, -+ id](const boost::system::error_code ec, -+ const std::variant<TimestampReadings>& ret) { -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ fillReport(asyncResp, id, ret); -+ }, -+ telemetry::service, reportPath, -+ "org.freedesktop.DBus.Properties", "Get", -+ telemetry::reportInterface, "Readings"); -+ }, -+ telemetry::service, reportPath, telemetry::reportInterface, -+ "Update"); -+ } -+ -+ using Readings = -+ std::vector<std::tuple<std::string, std::string, double, uint64_t>>; -+ using TimestampReadings = std::tuple<uint64_t, Readings>; -+ -+ static nlohmann::json toMetricValues(const Readings& readings) -+ { -+ nlohmann::json metricValues = nlohmann::json::array_t(); -+ -+ for (auto& [id, metadata, sensorValue, timestamp] : readings) -+ { -+ nlohmann::json metadataJson = nlohmann::json::parse(metadata); -+ metricValues.push_back({ -+ {"MetricId", id}, -+ {"MetricDefinition", metadataJson.contains("MetricDefinition") -+ ? metadataJson["MetricDefinition"] -+ : nlohmann::json()}, -+ {"MetricProperty", metadataJson.contains("MetricProperty") -+ ? metadataJson["MetricProperty"] -+ : nlohmann::json()}, -+ {"MetricValue", std::to_string(sensorValue)}, -+ {"Timestamp", -+ crow::utility::getDateTime(static_cast<time_t>(timestamp))}, -+ }); -+ } -+ -+ return metricValues; -+ } -+ -+ static void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& id, -+ const std::variant<TimestampReadings>& var) -+ { -+ asyncResp->res.jsonValue["@odata.type"] = schemaType; -+ asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; -+ asyncResp->res.jsonValue["Id"] = id; -+ asyncResp->res.jsonValue["Name"] = id; -+ asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = -+ telemetry::metricReportDefinitionUri + id; -+ -+ const TimestampReadings* timestampReadings = -+ std::get_if<TimestampReadings>(&var); -+ if (!timestampReadings) -+ { -+ BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ const auto& [timestamp, readings] = *timestampReadings; -+ asyncResp->res.jsonValue["Timestamp"] = -+ crow::utility::getDateTime(static_cast<time_t>(timestamp)); -+ asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); -+ } -+ -+ static constexpr const char* schemaType = -+ "#MetricReport.v1_3_0.MetricReport"; -+}; -+} // namespace redfish -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -new file mode 100644 -index 0000000..48c56e6 ---- /dev/null -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -0,0 +1,186 @@ -+#pragma once -+ -+#include "node.hpp" -+#include "utils/telemetry_utils.hpp" -+#include "utils/time_utils.hpp" -+ -+#include <tuple> -+#include <variant> -+ -+namespace redfish -+{ -+ -+class MetricReportDefinitionCollection : public Node -+{ -+ public: -+ MetricReportDefinitionCollection(App& app) : -+ Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>&) override -+ { -+ res.jsonValue["@odata.type"] = "#MetricReportDefinitionCollection." -+ "MetricReportDefinitionCollection"; -+ res.jsonValue["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricReportDefinitions"; -+ res.jsonValue["Name"] = "Metric Definition Collection"; -+ -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ telemetry::getReportCollection(asyncResp, -+ telemetry::metricReportDefinitionUri); -+ } -+}; -+ -+class MetricReportDefinition : public Node -+{ -+ public: -+ MetricReportDefinition(App& app) : -+ Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/", -+ std::string()) -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>& params) override -+ { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ -+ if (params.size() != 1) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ const std::string& id = params[0]; -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, -+ id](const boost::system::error_code ec, -+ const std::vector<std::pair< -+ std::string, std::variant<bool, ReadingParameters, -+ std::string, uint64_t>>>& ret) { -+ if (ec.value() == EBADR) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ fillReportDefinition(asyncResp, id, ret); -+ }, -+ telemetry::service, telemetry::getDbusReportPath(id), -+ "org.freedesktop.DBus.Properties", "GetAll", -+ telemetry::reportInterface); -+ } -+ -+ using ReadingParameters = -+ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -+ std::string, std::string, std::string>>; -+ -+ static void fillReportDefinition( -+ const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id, -+ const std::vector< -+ std::pair<std::string, std::variant<bool, ReadingParameters, -+ std::string, uint64_t>>>& ret) -+ { -+ asyncResp->res.jsonValue["@odata.type"] = schemaType; -+ asyncResp->res.jsonValue["@odata.id"] = -+ telemetry::metricReportDefinitionUri + id; -+ asyncResp->res.jsonValue["Id"] = id; -+ asyncResp->res.jsonValue["Name"] = id; -+ asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = -+ telemetry::metricReportUri + id; -+ asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; -+ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; -+ -+ const bool* emitsReadingsUpdate = nullptr; -+ const bool* logToMetricReportsCollection = nullptr; -+ const ReadingParameters* readingParams = nullptr; -+ const std::string* reportingType = nullptr; -+ const uint64_t* interval = nullptr; -+ for (const auto& [key, var] : ret) -+ { -+ if (key == "EmitsReadingsUpdate") -+ { -+ emitsReadingsUpdate = std::get_if<bool>(&var); -+ } -+ else if (key == "LogToMetricReportsCollection") -+ { -+ logToMetricReportsCollection = std::get_if<bool>(&var); -+ } -+ else if (key == "ReadingParameters") -+ { -+ readingParams = std::get_if<ReadingParameters>(&var); -+ } -+ else if (key == "ReportingType") -+ { -+ reportingType = std::get_if<std::string>(&var); -+ } -+ else if (key == "Interval") -+ { -+ interval = std::get_if<uint64_t>(&var); -+ } -+ } -+ if (!emitsReadingsUpdate || !logToMetricReportsCollection || -+ !readingParams || !reportingType || !interval) -+ { -+ BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ std::vector<std::string> redfishReportActions; -+ redfishReportActions.reserve(2); -+ if (*emitsReadingsUpdate) -+ { -+ redfishReportActions.emplace_back("RedfishEvent"); -+ } -+ if (*logToMetricReportsCollection) -+ { -+ redfishReportActions.emplace_back("LogToMetricReportsCollection"); -+ } -+ -+ nlohmann::json metrics = nlohmann::json::array(); -+ for (auto& [sensorPaths, operationType, id, metadata] : *readingParams) -+ { -+ nlohmann::json metadataJson = nlohmann::json::parse(metadata); -+ metrics.push_back({ -+ {"MetricId", id}, -+ {"MetricProperties", metadataJson.contains("MetricProperties") -+ ? metadataJson["MetricProperties"] -+ : nlohmann::json()}, -+ }); -+ } -+ asyncResp->res.jsonValue["Metrics"] = metrics; -+ asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; -+ asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; -+ asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = -+ time_utils::toDurationString(std::chrono::milliseconds(*interval)); -+ } -+ -+ static constexpr const char* schemaType = -+ "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; -+}; -+} // namespace redfish -diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp -index 629280c..3df5ec5 100644 ---- a/redfish-core/lib/service_root.hpp -+++ b/redfish-core/lib/service_root.hpp -@@ -68,6 +68,8 @@ class ServiceRoot : public Node - res.jsonValue["Tasks"] = {{"@odata.id", "/redfish/v1/TaskService"}}; - res.jsonValue["EventService"] = { - {"@odata.id", "/redfish/v1/EventService"}}; -+ res.jsonValue["TelemetryService"] = { -+ {"@odata.id", "/redfish/v1/TelemetryService"}}; - res.end(); - } - -diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp -new file mode 100644 -index 0000000..a6acc34 ---- /dev/null -+++ b/redfish-core/lib/telemetry_service.hpp -@@ -0,0 +1,93 @@ -+#pragma once -+ -+#include "node.hpp" -+#include "utils/telemetry_utils.hpp" -+ -+#include <variant> -+ -+namespace redfish -+{ -+ -+class TelemetryService : public Node -+{ -+ public: -+ TelemetryService(App& app) : Node(app, "/redfish/v1/TelemetryService/") -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>&) override -+ { -+ res.jsonValue["@odata.type"] = -+ "#TelemetryService.v1_2_1.TelemetryService"; -+ res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService"; -+ res.jsonValue["Id"] = "TelemetryService"; -+ res.jsonValue["Name"] = "Telemetry Service"; -+ -+ res.jsonValue["LogService"]["@odata.id"] = -+ "/redfish/v1/Managers/bmc/LogServices/Journal"; -+ res.jsonValue["MetricReportDefinitions"]["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricReportDefinitions"; -+ res.jsonValue["MetricReports"]["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricReports"; -+ -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ crow::connections::systemBus->async_method_call( -+ [asyncResp]( -+ const boost::system::error_code ec, -+ const std::vector<std::pair< -+ std::string, std::variant<uint32_t, uint64_t>>>& ret) { -+ if (ec == boost::system::errc::host_unreachable) -+ { -+ asyncResp->res.jsonValue["Status"]["State"] = "Absent"; -+ return; -+ } -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; -+ -+ const size_t* maxReports = nullptr; -+ const uint64_t* minInterval = nullptr; -+ for (const auto& [key, var] : ret) -+ { -+ if (key == "MaxReports") -+ { -+ maxReports = std::get_if<size_t>(&var); -+ } -+ else if (key == "MinInterval") -+ { -+ minInterval = std::get_if<uint64_t>(&var); -+ } -+ } -+ if (!maxReports || !minInterval) -+ { -+ BMCWEB_LOG_ERROR -+ << "Property type mismatch or property is missing"; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ asyncResp->res.jsonValue["MaxReports"] = *maxReports; -+ asyncResp->res.jsonValue["MinCollectionInterval"] = -+ time_utils::toDurationString(std::chrono::milliseconds( -+ static_cast<time_t>(*minInterval))); -+ }, -+ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", -+ "org.freedesktop.DBus.Properties", "GetAll", -+ "xyz.openbmc_project.Telemetry.ReportManager"); -+ } -+}; -+} // namespace redfish --- -2.17.1 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch index f40058ad8..fd7e8a445 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch @@ -1,7 +1,7 @@ -From 433358330c7f7d2fba99f6e488d67b314224317f Mon Sep 17 00:00:00 2001 +From bc1635622e122f307fb3b8eb9bbd66ea576a8f0c Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> Date: Mon, 18 May 2020 11:56:57 +0200 -Subject: [PATCH] Add POST and DELETE in MetricReportDefinitions +Subject: [PATCH 2/4] Add POST and DELETE in MetricReportDefinitions Added POST action in MetricReportDefinitions node to allow user to add new MetricReportDefinition. Using minimal set of @@ -12,64 +12,62 @@ Added DELETE request in MetricReportDefinitions node to allow user to remove report from Telemetry. Added conversion from string that represents duration format into its numeric equivalent. +Added unit tests for conversion from and to Duration format. Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated good cases with different parameters for POST action - - Validated bad cases with different parameters for POST action - - Verified time_utils::fromDurationString() + - Tested using witherspoon image on QEMU + - Verified POST action in different cases: + - all parameters are provided, new report is added to collection + - some parameters are missing or invalid, user gets response with + description of the issue - Verified that reports are removed on DELETE request + - Verified that on invalid DELETE request user receives response + with error + - Verified time_utils::fromDurationString() + - Succesfully passed RedfishServiceValidator.py Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a --- - .../include/utils/telemetry_utils.hpp | 5 +- - redfish-core/include/utils/time_utils.hpp | 145 ++++++- - redfish-core/lib/metric_report_definition.hpp | 382 +++++++++++++++++- - 3 files changed, 516 insertions(+), 16 deletions(-) + meson.build | 1 + + redfish-core/include/utils/time_utils.hpp | 138 ++++++++++- + redfish-core/lib/metric_report_definition.hpp | 328 ++++++++++++++++++++++++++ + redfish-core/ut/time_utils_test.cpp | 63 +++++ + 4 files changed, 528 insertions(+), 2 deletions(-) + create mode 100644 redfish-core/ut/time_utils_test.cpp -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index 8caee2d..acb739d 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -12,6 +12,8 @@ constexpr const char* metricReportDefinitionUri = - "/redfish/v1/TelemetryService/MetricReportDefinitions/"; - constexpr const char* metricReportUri = - "/redfish/v1/TelemetryService/MetricReports/"; -+constexpr const char* reportDir = -+ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/"; +diff --git a/meson.build b/meson.build +index 7a16e91..3d65b01 100644 +--- a/meson.build ++++ b/meson.build +@@ -330,6 +330,7 @@ srcfiles_bmcweb = ['src/webserver_main.cpp','redfish-core/src/error_messages.cpp + srcfiles_unittest = ['include/ut/dbus_utility_test.cpp', + 'redfish-core/ut/privileges_test.cpp', + 'redfish-core/ut/lock_test.cpp', ++ 'redfish-core/ut/time_utils_test.cpp', + 'http/ut/utility_test.cpp'] - inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - const std::string& uri) -@@ -61,8 +63,7 @@ inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - - inline std::string getDbusReportPath(const std::string& id) - { -- std::string path = -- "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id; -+ std::string path = reportDir + id; - dbus::utility::escapePathForDbus(path); - return path; - } + # Gather the Configuration data diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp -index dd4ea75..d8985ab 100644 +index dd4ea75..e94801b 100644 --- a/redfish-core/include/utils/time_utils.hpp +++ b/redfish-core/include/utils/time_utils.hpp -@@ -1,7 +1,12 @@ +@@ -1,7 +1,13 @@ #pragma once +#include "logging.hpp" + +#include <charconv> #include <chrono> ++#include <cmath> +#include <optional> #include <string> +#include <system_error> namespace redfish { -@@ -12,6 +17,8 @@ namespace time_utils +@@ -12,6 +18,8 @@ namespace time_utils namespace details { @@ -78,32 +76,12 @@ index dd4ea75..d8985ab 100644 inline void leftZeroPadding(std::string& str, const std::size_t padding) { if (str.size() < padding) -@@ -19,8 +26,143 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) +@@ -19,8 +27,135 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) str.insert(0, padding - str.size(), '0'); } } + -+inline bool fromChars(const char* start, const char* end, -+ std::chrono::milliseconds::rep& val) -+{ -+ auto [ptr, ec] = std::from_chars(start, end, val); -+ if (ptr != end) -+ { -+ BMCWEB_LOG_ERROR -+ << "Failed to convert string to decimal because of unexpected sign"; -+ return false; -+ } -+ if (ec != std::errc()) -+ { -+ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " -+ << static_cast<int>(ec) << "(" -+ << std::make_error_code(ec).message() << ")"; -+ return false; -+ } -+ return true; -+} -+ -+template <typename T> ++template <typename FromTime> +bool fromDurationItem(std::string_view& fmt, const char postfix, + std::chrono::milliseconds& out) +{ @@ -117,31 +95,43 @@ index dd4ea75..d8985ab 100644 + return false; + } + -+ std::chrono::milliseconds::rep v = 0; -+ if constexpr (std::is_same_v<T, std::chrono::milliseconds>) ++ const char* end; ++ std::chrono::milliseconds::rep ticks = 0; ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) + { -+ std::string str(fmt.data(), std::min<size_t>(pos, 3U)); -+ while (str.size() < 3U) -+ { -+ str += '0'; -+ } -+ if (!fromChars(str.data(), str.data() + str.size(), v)) -+ { -+ return false; -+ } ++ end = fmt.data() + std::min<size_t>(pos, 3U); + } + else + { -+ if (!fromChars(fmt.data(), fmt.data() + pos, v)) -+ { -+ return false; -+ } ++ end = fmt.data() + pos; + } + -+ out += T(v); -+ if (out < T(v) || -+ std::chrono::duration_cast<T>(std::chrono::milliseconds::max()) -+ .count() < v) ++ auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks); ++ if (ptr != end || ec != std::errc()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " ++ << static_cast<int>(ec) << "(" ++ << std::make_error_code(ec).message() << "), ptr{" ++ << static_cast<const void*>(ptr) << "} != end{" ++ << static_cast<const void*>(end) << "})"; ++ return false; ++ } ++ ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) ++ { ++ ticks *= static_cast<std::chrono::milliseconds::rep>( ++ std::pow(10, 3 - std::min<size_t>(pos, 3U))); ++ } ++ if (ticks < 0) ++ { ++ return false; ++ } ++ ++ out += FromTime(ticks); ++ const auto maxConversionRange = ++ std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max()) ++ .count(); ++ if (out < FromTime(ticks) || maxConversionRange < ticks) + { + return false; + } @@ -222,7 +212,7 @@ index dd4ea75..d8985ab 100644 /** * @brief Convert time value into duration format that is based on ISO 8601. * Example output: "P12DT1M5.5S" -@@ -36,8 +178,7 @@ std::string toDurationString(std::chrono::milliseconds ms) +@@ -36,8 +171,7 @@ std::string toDurationString(std::chrono::milliseconds ms) std::string fmt; fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); @@ -233,10 +223,10 @@ index dd4ea75..d8985ab 100644 std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index 48c56e6..d5a540d 100644 +index 59025d9..fcbc99c 100644 --- a/redfish-core/lib/metric_report_definition.hpp +++ b/redfish-core/lib/metric_report_definition.hpp -@@ -1,15 +1,26 @@ +@@ -1,9 +1,12 @@ #pragma once #include "node.hpp" @@ -249,394 +239,311 @@ index 48c56e6..d5a540d 100644 #include <tuple> #include <variant> - namespace redfish - { - -+namespace telemetry -+{ -+ -+using ReadingParameters = -+ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -+ std::string, std::string, std::string>>; -+} // namespace telemetry -+ - class MetricReportDefinitionCollection : public Node - { - public: -@@ -39,6 +50,318 @@ class MetricReportDefinitionCollection : public Node - telemetry::getReportCollection(asyncResp, - telemetry::metricReportDefinitionUri); - } -+ -+ struct AddReportArgs -+ { -+ std::string name; -+ std::string reportingType; -+ bool emitsReadingsUpdate = false; -+ bool logToMetricReportsCollection = false; -+ uint64_t interval = 0; -+ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; -+ }; +@@ -95,6 +98,252 @@ inline void fillReportDefinition( + asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = + time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } + -+ void doPost(crow::Response& res, const crow::Request& req, -+ const std::vector<std::string>&) override ++struct AddReportArgs ++{ ++ std::string name; ++ std::string reportingType; ++ bool emitsReadingsUpdate = false; ++ bool logToMetricReportsCollection = false; ++ uint64_t interval = 0; ++ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++}; ++ ++inline bool toDbusReportActions(crow::Response& res, ++ std::vector<std::string>& actions, ++ AddReportArgs& args) ++{ ++ size_t index = 0; ++ for (auto& action : actions) + { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ AddReportArgs args; -+ if (!getUserParameters(res, req, args)) ++ if (action == "RedfishEvent") + { -+ return; ++ args.emitsReadingsUpdate = true; + } -+ -+ boost::container::flat_set<std::pair<std::string, std::string>> -+ chassisSensors; -+ if (!getChassisSensorNode(asyncResp, args.metrics, chassisSensors)) ++ else if (action == "LogToMetricReportsCollection") + { -+ return; ++ args.logToMetricReportsCollection = true; + } -+ -+ auto addReportReq = -+ std::make_shared<AddReport>(std::move(args), asyncResp); -+ for (const auto& [chassis, sensorType] : chassisSensors) ++ else + { -+ retrieveUriToDbusMap( -+ chassis, sensorType, -+ [asyncResp, addReportReq]( -+ 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); -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ addReportReq->insert(uriToDbus); -+ }); ++ messages::propertyValueNotInList( ++ res, action, "ReportActions/" + std::to_string(index)); ++ return false; + } ++ index++; + } ++ return true; ++} + -+ static bool toDbusReportActions(crow::Response& res, -+ std::vector<std::string>& actions, -+ AddReportArgs& args) ++inline bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& args) ++{ ++ 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)) + { -+ size_t index = 0; -+ for (auto& action : actions) -+ { -+ if (action == "RedfishEvent") -+ { -+ args.emitsReadingsUpdate = true; -+ } -+ else if (action == "LogToMetricReportsCollection") -+ { -+ args.logToMetricReportsCollection = true; -+ } -+ else -+ { -+ messages::propertyValueNotInList( -+ res, action, "ReportActions/" + std::to_string(index)); -+ return false; -+ } -+ index++; -+ } -+ return true; ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; ++ if (args.name.empty() || args.name.find_first_not_of( ++ allowedCharactersInName) != std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << args.name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueIncorrect(res, "Id", args.name); ++ return false; ++ } ++ ++ if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") ++ { ++ messages::propertyValueNotInList(res, args.reportingType, ++ "MetricReportDefinitionType"); ++ return false; + } + -+ static bool getUserParameters(crow::Response& res, const crow::Request& req, -+ AddReportArgs& args) ++ if (!toDbusReportActions(res, reportActions, args)) + { -+ 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; ++ } ++ ++ if (args.reportingType == "Periodic") ++ { ++ if (!schedule) + { ++ messages::createFailedMissingReqProperties(res, "Schedule"); + return false; + } + -+ constexpr const char* allowedCharactersInName = -+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; -+ if (args.name.empty() || -+ args.name.find_first_not_of(allowedCharactersInName) != -+ std::string::npos) ++ std::string durationStr; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ durationStr)) + { -+ BMCWEB_LOG_ERROR << "Failed to match " << args.name -+ << " with allowed character " -+ << allowedCharactersInName; -+ messages::propertyValueIncorrect(res, "Id", args.name); + return false; + } + -+ if (args.reportingType != "Periodic" && -+ args.reportingType != "OnRequest") ++ std::optional<std::chrono::milliseconds> durationNum = ++ time_utils::fromDurationString(durationStr); ++ if (!durationNum) + { -+ messages::propertyValueNotInList(res, args.reportingType, -+ "MetricReportDefinitionType"); ++ messages::propertyValueIncorrect(res, "RecurrenceInterval", ++ durationStr); + return false; + } ++ args.interval = static_cast<uint64_t>(durationNum->count()); ++ } + -+ if (!toDbusReportActions(res, reportActions, args)) ++ 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)) + { + return false; + } + -+ if (args.reportingType == "Periodic") ++ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ } ++ ++ return true; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::pair<std::string, std::vector<std::string>>>& ++ metrics, ++ boost::container::flat_set<std::pair<std::string, std::string>>& matched) ++{ ++ for (const auto& [id, uris] : metrics) ++ { ++ for (size_t i = 0; i < uris.size(); i++) + { -+ if (!schedule) -+ { -+ messages::createFailedMissingReqProperties(res, "Schedule"); -+ return false; -+ } ++ const std::string& uri = uris[i]; ++ std::string chassis; ++ std::string node; + -+ std::string durationStr; -+ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", -+ durationStr)) ++ 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; + } + -+ std::optional<std::chrono::milliseconds> durationNum = -+ time_utils::fromDurationString(durationStr); -+ if (!durationNum) ++ if (boost::ends_with(node, "#")) + { -+ messages::propertyValueIncorrect(res, "RecurrenceInterval", -+ durationStr); -+ return false; ++ node.pop_back(); + } -+ args.interval = static_cast<uint64_t>(durationNum->count()); ++ ++ matched.emplace(std::move(chassis), std::move(node)); + } ++ } ++ return true; ++} + -+ args.metrics.reserve(metrics.size()); -+ for (auto& m : metrics) ++class AddReport ++{ ++ public: ++ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : ++ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} ++ {} ++ ~AddReport() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) + { -+ std::string id; -+ std::vector<std::string> uris; -+ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", -+ uris)) -+ { -+ return false; -+ } -+ -+ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ return; + } + -+ return true; -+ } ++ telemetry::ReadingParameters readingParams; ++ readingParams.reserve(args.metrics.size()); + -+ static bool getChassisSensorNode( -+ const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::vector<std::pair<std::string, std::vector<std::string>>>& -+ metrics, -+ boost::container::flat_set<std::pair<std::string, std::string>>& -+ matched) -+ { -+ for (const auto& [id, uris] : metrics) ++ for (const auto& [id, uris] : args.metrics) + { + for (size_t i = 0; i < uris.size(); i++) + { + 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)) ++ auto el = uriToDbus.find(uri); ++ if (el == uriToDbus.end()) + { -+ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " -+ "from " ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " + << uri; -+ messages::propertyValueIncorrect(asyncResp->res, uri, ++ messages::propertyValueNotInList(asyncResp->res, uri, + "MetricProperties/" + + std::to_string(i)); -+ return false; -+ } -+ -+ if (boost::ends_with(node, "#")) -+ { -+ node.pop_back(); ++ return; + } + -+ matched.emplace(std::move(chassis), std::move(node)); ++ const std::string& dbusPath = el->second; ++ readingParams.emplace_back(dbusPath, "SINGLE", id, uri); + } + } -+ return true; -+ } -+ -+ class AddReport -+ { -+ public: -+ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : -+ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} -+ {} -+ ~AddReport() -+ { -+ if (asyncResp->res.result() != boost::beast::http::status::ok) -+ { -+ return; -+ } -+ -+ telemetry::ReadingParameters readingParams; -+ readingParams.reserve(args.metrics.size()); + -+ for (const auto& [id, uris] : args.metrics) -+ { -+ std::vector<sdbusplus::message::object_path> dbusPaths; -+ dbusPaths.reserve(uris.size()); -+ -+ for (size_t i = 0; i < uris.size(); i++) ++ crow::connections::systemBus->async_method_call( ++ [asyncResp = std::move(asyncResp), name = args.name, ++ uriToDbus = std::move(uriToDbus)]( ++ const boost::system::error_code ec, const std::string&) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists( ++ asyncResp->res, "MetricReportDefinition", "Id", name); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) + { -+ const std::string& uri = uris[i]; -+ auto el = uriToDbus.find(uri); -+ if (el == uriToDbus.end()) ++ messages::createLimitReachedForResource(asyncResp->res); ++ return; ++ } ++ if (ec == boost::system::errc::argument_list_too_long) ++ { ++ nlohmann::json metricProperties = nlohmann::json::array(); ++ for (const auto& [uri, _] : uriToDbus) + { -+ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " -+ "corresponding to URI " -+ << uri; -+ messages::propertyValueNotInList(asyncResp->res, uri, -+ "MetricProperties/" + -+ std::to_string(i)); -+ return; ++ metricProperties.emplace_back(uri); + } -+ -+ dbusPaths.emplace_back(el->second); ++ messages::propertyValueIncorrect( ++ asyncResp->res, metricProperties, "MetricProperties"); ++ return; + } -+ -+ nlohmann::json metadata; -+ metadata["MetricProperties"] = uris; -+ if (uris.size() == 1) ++ if (ec) + { -+ metadata["MetricProperty"] = uris[0]; ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; + } -+ readingParams.emplace_back(std::move(dbusPaths), "SINGLE", id, -+ metadata.dump()); -+ } + -+ crow::connections::systemBus->async_method_call( -+ [asyncResp = asyncResp, name = args.name]( -+ const boost::system::error_code ec, const std::string&) { -+ if (ec == boost::system::errc::file_exists) -+ { -+ messages::resourceAlreadyExists( -+ asyncResp->res, "MetricReportDefinition", "Id", -+ name); -+ return; -+ } -+ if (ec == boost::system::errc::too_many_files_open) -+ { -+ messages::createLimitReachedForResource(asyncResp->res); -+ return; -+ } -+ if (ec == boost::system::errc::argument_list_too_long) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, "/Exceeds supported size/", -+ "Metrics"); -+ return; -+ } -+ if (ec == boost::system::errc::not_supported) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, -+ "/Only single property per metric is supported/", -+ "MetricProperties"); -+ return; -+ } -+ if (ec == boost::system::errc::invalid_argument) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, "/Less then MinInterval/", -+ "RecurrenceInterval"); -+ return; -+ } -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } ++ messages::created(asyncResp->res); ++ }, ++ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", ++ "TelemetryService/" + args.name, args.reportingType, ++ args.emitsReadingsUpdate, args.logToMetricReportsCollection, ++ args.interval, readingParams); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } + -+ messages::created(asyncResp->res); -+ }, -+ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", -+ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", -+ "TelemetryService/" + args.name, args.reportingType, -+ args.emitsReadingsUpdate, args.logToMetricReportsCollection, -+ args.interval, readingParams); ++ private: ++ std::shared_ptr<AsyncResp> asyncResp; ++ AddReportArgs args; ++ boost::container::flat_map<std::string, std::string> uriToDbus{}; ++}; + } // namespace telemetry + + class MetricReportDefinitionCollection : public Node +@@ -126,6 +375,46 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ telemetry::AddReportArgs args; ++ if (!telemetry::getUserParameters(res, req, args)) ++ { ++ return; + } + -+ void insert( -+ const boost::container::flat_map<std::string, std::string>& el) ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, ++ chassisSensors)) + { -+ uriToDbus.insert(el.begin(), el.end()); ++ return; + } + -+ private: -+ std::shared_ptr<AsyncResp> asyncResp; -+ AddReportArgs args; -+ boost::container::flat_map<std::string, std::string> uriToDbus{}; -+ }; ++ auto addReportReq = ++ std::make_shared<telemetry::AddReport>(std::move(args), asyncResp); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ 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); ++ return; ++ } ++ addReportReq->insert(uriToDbus); ++ }); ++ } ++ } }; class MetricReportDefinition : public Node -@@ -73,9 +396,10 @@ class MetricReportDefinition : public Node - crow::connections::systemBus->async_method_call( - [asyncResp, - id](const boost::system::error_code ec, -- const std::vector<std::pair< -- std::string, std::variant<bool, ReadingParameters, -- std::string, uint64_t>>>& ret) { -+ const std::vector< -+ std::pair<std::string, -+ std::variant<bool, telemetry::ReadingParameters, -+ std::string, uint64_t>>>& ret) { - if (ec.value() == EBADR) - { - messages::resourceNotFound(asyncResp->res, schemaType, id); -@@ -95,15 +419,11 @@ class MetricReportDefinition : public Node +@@ -184,5 +473,44 @@ class MetricReportDefinition : public Node + "org.freedesktop.DBus.Properties", "GetAll", telemetry::reportInterface); } - -- using ReadingParameters = -- std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -- std::string, std::string, std::string>>; -- - static void fillReportDefinition( - const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id, -- const std::vector< -- std::pair<std::string, std::variant<bool, ReadingParameters, -- std::string, uint64_t>>>& ret) -+ const std::vector<std::pair< -+ std::string, std::variant<bool, telemetry::ReadingParameters, -+ std::string, uint64_t>>>& ret) - { - asyncResp->res.jsonValue["@odata.type"] = schemaType; - asyncResp->res.jsonValue["@odata.id"] = -@@ -117,7 +437,7 @@ class MetricReportDefinition : public Node - - const bool* emitsReadingsUpdate = nullptr; - const bool* logToMetricReportsCollection = nullptr; -- const ReadingParameters* readingParams = nullptr; -+ const telemetry::ReadingParameters* readingParams = nullptr; - const std::string* reportingType = nullptr; - const uint64_t* interval = nullptr; - for (const auto& [key, var] : ret) -@@ -132,7 +452,7 @@ class MetricReportDefinition : public Node - } - else if (key == "ReadingParameters") - { -- readingParams = std::get_if<ReadingParameters>(&var); -+ readingParams = std::get_if<telemetry::ReadingParameters>(&var); - } - else if (key == "ReportingType") - { -@@ -180,6 +500,44 @@ class MetricReportDefinition : public Node - time_utils::toDurationString(std::chrono::milliseconds(*interval)); - } - ++ + void doDelete(crow::Response& res, const crow::Request&, + const std::vector<std::string>& params) override + { @@ -658,7 +565,8 @@ index 48c56e6..d5a540d 100644 + */ + if (ec.value() == EBADR) + { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ messages::resourceNotFound(asyncResp->res, ++ "MetricReportDefinition", id); + return; + } + @@ -674,10 +582,77 @@ index 48c56e6..d5a540d 100644 + telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", + "Delete"); + } -+ - static constexpr const char* schemaType = - "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; }; + } // namespace redfish +diff --git a/redfish-core/ut/time_utils_test.cpp b/redfish-core/ut/time_utils_test.cpp +new file mode 100644 +index 0000000..70999ce +--- /dev/null ++++ b/redfish-core/ut/time_utils_test.cpp +@@ -0,0 +1,63 @@ ++#include "utils/time_utils.hpp" ++ ++#include <gmock/gmock.h> ++ ++using namespace testing; ++ ++class FromDurationTest : ++ public Test, ++ public WithParamInterface< ++ std::pair<std::string, std::optional<std::chrono::milliseconds>>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, FromDurationTest, ++ Values(std::make_pair("PT12S", std::chrono::milliseconds(12000)), ++ std::make_pair("PT0.204S", std::chrono::milliseconds(204)), ++ std::make_pair("PT0.2S", std::chrono::milliseconds(200)), ++ std::make_pair("PT50M", std::chrono::milliseconds(3000000)), ++ std::make_pair("PT23H", std::chrono::milliseconds(82800000)), ++ std::make_pair("P51D", std::chrono::milliseconds(4406400000)), ++ std::make_pair("PT2H40M10.1S", std::chrono::milliseconds(9610100)), ++ std::make_pair("P20DT2H40M10.1S", ++ std::chrono::milliseconds(1737610100)), ++ std::make_pair("", std::chrono::milliseconds(0)), ++ std::make_pair("PTS", std::nullopt), ++ std::make_pair("P1T", std::nullopt), ++ std::make_pair("PT100M1000S100", std::nullopt), ++ std::make_pair("PDTHMS", std::nullopt), ++ std::make_pair("P99999999999999999DT", std::nullopt), ++ std::make_pair("PD222T222H222M222.222S", std::nullopt), ++ std::make_pair("PT99999H9999999999999999999999M99999999999S", ++ std::nullopt), ++ std::make_pair("PT-9H", std::nullopt))); ++ ++TEST_P(FromDurationTest, convertToMilliseconds) ++{ ++ const auto& [str, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::fromDurationString(str), Eq(expected)); ++} ++ ++class ToDurationTest : ++ public Test, ++ public WithParamInterface<std::pair<std::chrono::milliseconds, std::string>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, ToDurationTest, ++ Values(std::make_pair(std::chrono::milliseconds(12000), "PT12.000S"), ++ std::make_pair(std::chrono::milliseconds(204), "PT0.204S"), ++ std::make_pair(std::chrono::milliseconds(200), "PT0.200S"), ++ std::make_pair(std::chrono::milliseconds(3000000), "PT50M"), ++ std::make_pair(std::chrono::milliseconds(82800000), "PT23H"), ++ std::make_pair(std::chrono::milliseconds(4406400000), "P51DT"), ++ std::make_pair(std::chrono::milliseconds(9610100), "PT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(1737610100), ++ "P20DT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(-250), ""))); ++ ++TEST_P(ToDurationTest, convertToDuration) ++{ ++ const auto& [ms, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::toDurationString(ms), Eq(expected)); ++} -- -2.17.1 +2.16.6 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch index 7c3e4c804..99af0ab86 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch @@ -1,35 +1,41 @@ -From d9016c8064f5732fb6d24d07a990ddfa294a8a9d Mon Sep 17 00:00:00 2001 +From 462b2e814698e12a18b4956eb3c6421c34a3a4ba Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Mon, 8 Jun 2020 17:15:54 +0200 -Subject: [PATCH] Add support for MetricDefinition scheme +Date: Tue, 15 Dec 2020 12:28:17 +0100 +Subject: [PATCH 3/4] 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 following +categories: temperature, power, voltage, current, fan_tach, +fan_pwm, utilization. -Added MetricDefinition node to redfish core. Now user is able to -get all possible metrics that are present in system and are -supported by TelemetryService. Added generic function to fill ReadingUnits and ReadingType -in Sensor scheme. +in Sensor node. Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated a presence of MetricDefinition members + - MetricDefinitions response is filled with existing sensors, + it works with and without Telemetry service + - Validated a presence of MetricDefinition members and it + attributes + - Succesfully passed RedfishServiceValidator.py using + witherspoon image on QEMU 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/telemetry_utils.hpp | 56 ++-- - redfish-core/lib/metric_definition.hpp | 269 ++++++++++++++++++ - redfish-core/lib/metric_report_definition.hpp | 22 ++ - redfish-core/lib/power.hpp | 4 +- - redfish-core/lib/sensors.hpp | 96 +++++-- - redfish-core/lib/telemetry_service.hpp | 2 + - redfish-core/lib/thermal.hpp | 4 +- - 8 files changed, 406 insertions(+), 50 deletions(-) + redfish-core/include/redfish.hpp | 3 + + redfish-core/include/utils/telemetry_utils.hpp | 2 + + redfish-core/lib/metric_definition.hpp | 283 +++++++++++++++++++++++++ + redfish-core/lib/power.hpp | 4 +- + redfish-core/lib/sensors.hpp | 85 ++++++-- + redfish-core/lib/telemetry_service.hpp | 2 + + redfish-core/lib/thermal.hpp | 4 +- + 7 files changed, 361 insertions(+), 22 deletions(-) create mode 100644 redfish-core/lib/metric_definition.hpp diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp -index b366e24..a938d43 100644 +index e94c0f3..83f2300 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -25,6 +25,7 @@ @@ -40,7 +46,7 @@ index b366e24..a938d43 100644 #include "../lib/metric_report.hpp" #include "../lib/metric_report_definition.hpp" #include "../lib/network_protocol.hpp" -@@ -216,6 +217,8 @@ class RedfishService +@@ -213,6 +214,8 @@ class RedfishService nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); nodes.emplace_back(std::make_unique<TelemetryService>(app)); @@ -50,7 +56,7 @@ index b366e24..a938d43 100644 std::make_unique<MetricReportDefinitionCollection>(app)); nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index acb739d..c13a79b 100644 +index a3a8156..c1b7639 100644 --- a/redfish-core/include/utils/telemetry_utils.hpp +++ b/redfish-core/include/utils/telemetry_utils.hpp @@ -8,6 +8,8 @@ namespace telemetry @@ -62,80 +68,12 @@ index acb739d..c13a79b 100644 constexpr const char* metricReportDefinitionUri = "/redfish/v1/TelemetryService/MetricReportDefinitions/"; constexpr const char* metricReportUri = -@@ -15,6 +17,36 @@ constexpr const char* metricReportUri = - constexpr const char* reportDir = - "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/"; - -+inline void dbusPathsToMembers(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::vector<std::string>& paths, -+ const std::string& uri) -+{ -+ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; -+ members = nlohmann::json::array(); -+ -+ for (const std::string& path : paths) -+ { -+ std::size_t pos = path.rfind('/'); -+ if (pos == std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ if (path.size() <= (pos + 1)) -+ { -+ BMCWEB_LOG_ERROR << "Failed to parse path " << path; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); -+ } -+ -+ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); -+} -+ - inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - const std::string& uri) - { -@@ -30,29 +62,7 @@ inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - return; - } - -- nlohmann::json& members = asyncResp->res.jsonValue["Members"]; -- members = nlohmann::json::array(); -- -- for (const std::string& path : reportPaths) -- { -- std::size_t pos = path.rfind('/'); -- if (pos == std::string::npos) -- { -- BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; -- messages::internalError(asyncResp->res); -- return; -- } -- if (path.size() <= (pos + 1)) -- { -- BMCWEB_LOG_ERROR << "Failed to parse path " << path; -- messages::internalError(asyncResp->res); -- return; -- } -- -- members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); -- } -- -- asyncResp->res.jsonValue["Members@odata.count"] = members.size(); -+ dbusPathsToMembers(asyncResp, reportPaths, uri); - }, - "xyz.openbmc_project.ObjectMapper", - "/xyz/openbmc_project/object_mapper", diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp new file mode 100644 -index 0000000..f037ed2 +index 0000000..4a40af5 --- /dev/null +++ b/redfish-core/lib/metric_definition.hpp -@@ -0,0 +1,269 @@ +@@ -0,0 +1,283 @@ +#pragma once + +#include "node.hpp" @@ -149,31 +87,36 @@ index 0000000..f037ed2 +{ + +template <typename F> -+inline void getChassisNames(F&& cb) ++inline void getChassisNames(F&& cb, const std::shared_ptr<AsyncResp>& asyncResp) +{ + 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, -+ std::vector<std::string>& chassisList) { ++ [asyncResp, ++ callback = std::move(cb)](const boost::system::error_code ec, ++ std::vector<std::string>& chassises) { + if (ec) + { ++ messages::internalError(asyncResp->res); + BMCWEB_LOG_DEBUG << "DBus call error: " << ec.value(); + return; + } + + std::vector<std::string> chassisNames; -+ chassisNames.reserve(chassisList.size()); -+ for (const std::string& chassisPath : chassisList) ++ chassisNames.reserve(chassises.size()); ++ for (const std::string& chassis : chassises) + { -+ size_t pos = chassisPath.rfind('/'); -+ if (pos == std::string::npos) ++ sdbusplus::message::object_path path(chassis); ++ std::string name = path.filename(); ++ if (name.empty()) + { -+ continue; ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "Invalid chassis: " << chassis; ++ return; + } -+ chassisNames.push_back(chassisPath.substr(pos + 1)); ++ chassisNames.push_back(name); + } + + callback(chassisNames); @@ -185,6 +128,109 @@ index 0000000..f037ed2 +} +} // namespace utils + ++namespace telemetry ++{ ++ ++class DefinitionCollectionReduce ++{ ++ public: ++ DefinitionCollectionReduce(const std::shared_ptr<AsyncResp>& asyncResp) : ++ asyncResp{asyncResp} ++ {} ++ ++ ~DefinitionCollectionReduce() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; ++ members = nlohmann::json::array(); ++ ++ for (const std::string& type : dbusTypes) ++ { ++ members.push_back( ++ {{"@odata.id", telemetry::metricDefinitionUri + type}}); ++ } ++ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [_, dbusSensor] : el) ++ { ++ sdbusplus::message::object_path path(dbusSensor); ++ sdbusplus::message::object_path parentPath = path.parent_path(); ++ std::string type = parentPath.filename(); ++ if (type.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " ++ << dbusSensor; ++ continue; ++ } ++ ++ dbusTypes.insert(std::move(type)); ++ } ++ } ++ ++ private: ++ const std::shared_ptr<AsyncResp> asyncResp; ++ boost::container::flat_set<std::string> dbusTypes; ++}; ++ ++class DefinitionReduce ++{ ++ public: ++ DefinitionReduce(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& id) : ++ id(id), ++ pattern{'/' + id + '/'}, asyncResp{asyncResp} ++ {} ++ ~DefinitionReduce() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ if (redfishSensors.empty()) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricDefinition", id); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["MetricProperties"] = redfishSensors; ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["Name"] = id; ++ asyncResp->res.jsonValue["@odata.id"] = ++ telemetry::metricDefinitionUri + id; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#MetricDefinition.v1_0_3.MetricDefinition"; ++ asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; ++ asyncResp->res.jsonValue["MetricType"] = "Numeric"; ++ asyncResp->res.jsonValue["IsLinear"] = true; ++ asyncResp->res.jsonValue["Units"] = sensors::toReadingUnits(id); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [redfishSensor, dbusSensor] : el) ++ { ++ if (dbusSensor.find(pattern) != std::string::npos) ++ { ++ redfishSensors.push_back(redfishSensor); ++ } ++ } ++ } ++ ++ private: ++ const std::string id; ++ const std::string pattern; ++ const std::shared_ptr<AsyncResp> asyncResp; ++ std::vector<std::string> redfishSensors; ++}; ++} // namespace telemetry ++ +class MetricDefinitionCollection : public Node +{ + public: @@ -213,7 +259,8 @@ index 0000000..f037ed2 + res.jsonValue["Members@odata.count"] = 0; + + auto asyncResp = std::make_shared<AsyncResp>(res); -+ auto collectionReduce = std::make_shared<CollectionGather>(asyncResp); ++ auto collectionReduce = ++ std::make_shared<telemetry::DefinitionCollectionReduce>(asyncResp); + utils::getChassisNames( + [asyncResp, + collectionReduce](const std::vector<std::string>& chassisNames) { @@ -242,50 +289,9 @@ index 0000000..f037ed2 + }); + } + } -+ }); ++ }, ++ asyncResp); + } -+ -+ class CollectionGather -+ { -+ public: -+ CollectionGather(const std::shared_ptr<AsyncResp>& asyncResp) : -+ asyncResp{asyncResp} -+ {} -+ -+ ~CollectionGather() -+ { -+ if (asyncResp->res.result() != boost::beast::http::status::ok) -+ { -+ return; -+ } -+ -+ telemetry::dbusPathsToMembers( -+ asyncResp, -+ std::vector<std::string>(dbusTypes.begin(), dbusTypes.end()), -+ telemetry::metricDefinitionUri); -+ } -+ -+ void insert( -+ const boost::container::flat_map<std::string, std::string>& el) -+ { -+ for (const auto& [_, dbusSensor] : el) -+ { -+ size_t pos = dbusSensor.rfind('/'); -+ if (pos == std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " -+ << dbusSensor; -+ continue; -+ } -+ -+ dbusTypes.insert(dbusSensor.substr(0, pos)); -+ } -+ } -+ -+ private: -+ const std::shared_ptr<AsyncResp> asyncResp; -+ boost::container::flat_set<std::string> dbusTypes; -+ }; +}; + +class MetricDefinition : public Node @@ -317,7 +323,7 @@ index 0000000..f037ed2 + + const std::string& id = params[0]; + auto definitionGather = -+ std::make_shared<DefinitionGather>(asyncResp, id); ++ std::make_shared<telemetry::DefinitionReduce>(asyncResp, id); + utils::getChassisNames( + [asyncResp, + definitionGather](const std::vector<std::string>& chassisNames) { @@ -345,113 +351,12 @@ index 0000000..f037ed2 + }); + } + } -+ }); ++ }, ++ asyncResp); + } -+ -+ class DefinitionGather -+ { -+ public: -+ DefinitionGather(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& id) : -+ id(id), -+ pattern{'/' + id + '/'}, asyncResp{asyncResp} -+ {} -+ ~DefinitionGather() -+ { -+ if (asyncResp->res.result() != boost::beast::http::status::ok) -+ { -+ return; -+ } -+ if (redfishSensors.empty()) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ -+ asyncResp->res.jsonValue["MetricProperties"] = redfishSensors; -+ asyncResp->res.jsonValue["Id"] = id; -+ asyncResp->res.jsonValue["Name"] = id; -+ asyncResp->res.jsonValue["@odata.id"] = -+ telemetry::metricDefinitionUri + id; -+ asyncResp->res.jsonValue["@odata.type"] = schemaType; -+ asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; -+ asyncResp->res.jsonValue["MetricType"] = "Numeric"; -+ asyncResp->res.jsonValue["IsLinear"] = true; -+ asyncResp->res.jsonValue["Units"] = sensors::toReadingUnits(id); -+ } -+ -+ void insert( -+ const boost::container::flat_map<std::string, std::string>& el) -+ { -+ for (const auto& [redfishSensor, dbusSensor] : el) -+ { -+ if (dbusSensor.find(pattern) != std::string::npos) -+ { -+ redfishSensors.push_back(redfishSensor); -+ } -+ } -+ } -+ -+ const std::string id; -+ const std::string pattern; -+ -+ private: -+ const std::shared_ptr<AsyncResp> asyncResp; -+ std::vector<std::string> redfishSensors; -+ }; -+ -+ static constexpr const char* schemaType = -+ "#MetricDefinition.v1_0_3.MetricDefinition"; +}; + +} // namespace redfish -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index d5a540d..03f0b82 100644 ---- a/redfish-core/lib/metric_report_definition.hpp -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -269,6 +269,8 @@ class MetricReportDefinitionCollection : public Node - { - std::vector<sdbusplus::message::object_path> dbusPaths; - dbusPaths.reserve(uris.size()); -+ std::string sensorType; -+ bool invalidType = false; - - for (size_t i = 0; i < uris.size(); i++) - { -@@ -286,6 +288,21 @@ class MetricReportDefinitionCollection : public Node - } - - dbusPaths.emplace_back(el->second); -+ -+ if (invalidType) -+ { -+ continue; -+ } -+ std::string tmp; -+ dbus::utility::getNthStringFromPath(el->second, 3, tmp); -+ if (sensorType.empty()) -+ { -+ sensorType = std::move(tmp); -+ } -+ else if (sensorType != tmp) -+ { -+ invalidType = true; -+ } - } - - nlohmann::json metadata; -@@ -294,6 +311,11 @@ class MetricReportDefinitionCollection : public Node - { - metadata["MetricProperty"] = uris[0]; - } -+ if (!sensorType.empty() && !invalidType) -+ { -+ metadata["MetricDefinition"]["@odata.id"] = -+ telemetry::metricDefinitionUri + sensorType; -+ } - readingParams.emplace_back(std::move(dbusPaths), "SINGLE", id, - metadata.dump()); - } diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp index 1c7a009..99c45ef 100644 --- a/redfish-core/lib/power.hpp @@ -475,7 +380,7 @@ index 1c7a009..99c45ef 100644 std::optional<std::vector<nlohmann::json>> voltageCollections; diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp -index 14c9593..bf97540 100644 +index 14c9593..5080f77 100644 --- a/redfish-core/lib/sensors.hpp +++ b/redfish-core/lib/sensors.hpp @@ -54,9 +54,10 @@ static constexpr std::string_view thermal = "Thermal"; @@ -555,46 +460,12 @@ index 14c9593..bf97540 100644 } } // namespace sensors -@@ -90,19 +149,20 @@ class SensorsAsyncResp - }; - - SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn, -- const std::vector<const char*>& typesIn, -+ const std::vector<const char*>& matchPathsIn, - const std::string_view& subNode) : - res(response), -- chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode) -+ chassisId(chassisIdIn), matchPaths(matchPathsIn), -+ chassisSubNode(subNode) - {} - - // Store extra data about sensor mapping and return it in callback - SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn, -- const std::vector<const char*>& typesIn, -+ const std::vector<const char*>& matchPathsIn, - const std::string_view& subNode, - DataCompleteCb&& creationComplete) : - res(response), -- chassisId(chassisIdIn), types(typesIn), -+ chassisId(chassisIdIn), matchPaths(matchPathsIn), - chassisSubNode(subNode), metadata{std::vector<SensorData>()}, - dataComplete{std::move(creationComplete)} - {} -@@ -161,7 +221,7 @@ class SensorsAsyncResp - - crow::Response& res; - const std::string chassisId; -- const std::vector<const char*> types; -+ const std::vector<const char*> matchPaths; - const std::string chassisSubNode; - - private: -@@ -345,11 +405,11 @@ inline void reduceSensorList( +@@ -345,11 +404,11 @@ inline void reduceSensorList( return; } - for (const char* type : sensorsAsyncResp->types) -+ for (const char* path : sensorsAsyncResp->matchPaths) ++ for (const char* path : sensorsAsyncResp->types) { for (const std::string& sensor : *allSensors) { @@ -603,7 +474,7 @@ index 14c9593..bf97540 100644 { activeSensors->emplace(sensor); } -@@ -853,18 +913,8 @@ inline void objectInterfacesToJson( +@@ -853,18 +912,8 @@ inline void objectInterfacesToJson( if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) { sensorJson["@odata.type"] = "#Sensor.v1_0_0.Sensor"; @@ -624,7 +495,7 @@ index 14c9593..bf97540 100644 } else if (sensorType == "temperature") { -@@ -2976,8 +3026,8 @@ inline void retrieveUriToDbusMap(const std::string& chassis, +@@ -2976,8 +3025,8 @@ inline void retrieveUriToDbusMap(const std::string& chassis, const std::string& node, SensorsAsyncResp::DataCompleteCb&& mapComplete) { @@ -635,7 +506,7 @@ index 14c9593..bf97540 100644 { BMCWEB_LOG_ERROR << "Wrong node provided : " << node; mapComplete(boost::beast::http::status::bad_request, {}); -@@ -3027,7 +3077,7 @@ class SensorCollection : public Node +@@ -3027,7 +3076,7 @@ class SensorCollection : public Node const std::string& chassisId = params[0]; std::shared_ptr<SensorsAsyncResp> asyncResp = std::make_shared<SensorsAsyncResp>( @@ -645,13 +516,13 @@ index 14c9593..bf97540 100644 auto getChassisCb = diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp -index a6acc34..8105d86 100644 +index 61ca891..a8c8b03 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp -@@ -34,6 +34,8 @@ class TelemetryService : public Node +@@ -32,6 +32,8 @@ class TelemetryService : public Node + res.jsonValue["Id"] = "TelemetryService"; + res.jsonValue["Name"] = "Telemetry Service"; - res.jsonValue["LogService"]["@odata.id"] = - "/redfish/v1/Managers/bmc/LogServices/Journal"; + res.jsonValue["MetricDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; res.jsonValue["MetricReportDefinitions"]["@odata.id"] = @@ -680,5 +551,5 @@ index 8e01bee..00acdf9 100644 if (!json_util::readJson(req, asyncResp->res, "Temperatures", -- -2.17.1 +2.16.6 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch index 06c50b31f..3df9fe5ae 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch @@ -1,27 +1,28 @@ -From b6286fe6800ca402b013e57429025fd9e4d65cab Mon Sep 17 00:00:00 2001 +From 14a429586fd8ccb82c72611fe3310860db2e643b Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Fri, 4 Dec 2020 14:48:41 +0100 -Subject: [PATCH] Sync Telmetry service with EventService +Date: Tue, 15 Dec 2020 12:30:31 +0100 +Subject: [PATCH 4/4] Sync Telmetry service with EventService -Now assembling MetricReport is done properly and is -covered in one place - MetricReport node. -Updated method of fetching Readings from Telemetry by -EventService. Using ReportUpdate signal is no longer -supported. +Synced the latest changes in Telemetry service with Event Service +code. Now assembling MetricReport is covered in single place in +code. Updated method of fetching Readings from Telemetry by +Event Service. Using ReportUpdate signal is no longer +supported. Now Event Service monitors for PropertiesChanged signal +from /xyz/openbmc_project/Telemetry/Reports path. Tested: - - Received MetricReport in EventListener server after - adding subscription to EventService. + - Verified that EventListener received MetricReport response from + Event Service in insecure http push style eventing mode Change-Id: I2fc1841a6c9259a8bff30b34bddc0d4aabd41912 Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> --- - .../include/event_service_manager.hpp | 156 ++++++------------ - redfish-core/lib/metric_report.hpp | 35 ++-- - 2 files changed, 74 insertions(+), 117 deletions(-) + redfish-core/include/event_service_manager.hpp | 157 +++++++++---------------- + redfish-core/lib/metric_report.hpp | 28 +++-- + 2 files changed, 71 insertions(+), 114 deletions(-) diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp -index f68ae1d..893a813 100644 +index 3db9f0c..5c5a6c1 100644 --- a/redfish-core/include/event_service_manager.hpp +++ b/redfish-core/include/event_service_manager.hpp @@ -14,6 +14,7 @@ @@ -41,7 +42,7 @@ index f68ae1d..893a813 100644 - const ReadingsObjType& readings) + void filterAndSendReports( + const std::string& id, -+ const std::variant<MetricReport::TimestampReadings>& var) ++ const std::variant<telemetry::TimestampReadings>& var) { - std::string metricReportDef = - "/redfish/v1/TelemetryService/MetricReportDefinitions/" + id2; @@ -62,7 +63,7 @@ index f68ae1d..893a813 100644 - nlohmann::json metricValuesArray = nlohmann::json::array(); - for (const auto& it : readings) + nlohmann::json json; -+ if (!MetricReport::fillReport(json, id, var)) ++ if (!telemetry::fillReport(json, id, var)) { - metricValuesArray.push_back({}); - nlohmann::json& entry = metricValuesArray.back(); @@ -90,7 +91,7 @@ index f68ae1d..893a813 100644 } void updateRetryConfig(const uint32_t retryAttempts, -@@ -1342,56 +1324,71 @@ class EventServiceManager +@@ -1342,56 +1324,72 @@ class EventServiceManager } #endif @@ -152,7 +153,7 @@ index f68ae1d..893a813 100644 - if (!timestampPtr) + std::string intf; + std::vector<std::pair< -+ std::string, std::variant<MetricReport::TimestampReadings>>> ++ std::string, std::variant<telemetry::TimestampReadings>>> + props; + std::vector<std::string> invalidProp; + @@ -166,7 +167,7 @@ index f68ae1d..893a813 100644 - ReadingsObjType* readingsPtr = - std::get_if<ReadingsObjType>(&resp["Readings"]); - if (!readingsPtr) -+ const std::variant<MetricReport::TimestampReadings>* varPtr = ++ const std::variant<telemetry::TimestampReadings>* varPtr = + nullptr; + for (const auto& [key, var] : props) + { @@ -183,15 +184,16 @@ index f68ae1d..893a813 100644 } - if (!readingsPtr->size()) -+ std::string id; -+ if (!dbus::utility::getNthStringFromPath(msg.get_path(), 5, id)) ++ sdbusplus::message::object_path path(msg.get_path()); ++ std::string id = path.filename(); ++ if (id.empty()) { - BMCWEB_LOG_DEBUG << "No metrics report to be transferred"; + BMCWEB_LOG_ERROR << "Failed to get Id from path"; return; } -@@ -1401,52 +1398,9 @@ class EventServiceManager +@@ -1401,52 +1399,9 @@ class EventServiceManager std::shared_ptr<Subscription> entry = it.second; if (entry->eventFormatType == metricReportFormatType) { @@ -246,85 +248,64 @@ index f68ae1d..893a813 100644 } diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 050304c..c2013cc 100644 +index 9caf4a3..e79a41c 100644 --- a/redfish-core/lib/metric_report.hpp +++ b/redfish-core/lib/metric_report.hpp -@@ -52,6 +52,10 @@ class MetricReport : public Node - {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; +@@ -31,16 +31,14 @@ inline nlohmann::json toMetricValues(const Readings& readings) + return metricValues; + } + +-inline void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, +- const std::string& id, ++inline bool fillReport(nlohmann::json& json, const std::string& id, + const std::variant<TimestampReadings>& var) + { +- asyncResp->res.jsonValue["@odata.type"] = +- "#MetricReport.v1_3_0.MetricReport"; +- asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + 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["Id"] = id; ++ json["Name"] = id; ++ json["MetricReportDefinition"]["@odata.id"] = + telemetry::metricReportDefinitionUri + id; + + const TimestampReadings* timestampReadings = +@@ -48,14 +46,14 @@ inline void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, + if (!timestampReadings) + { + BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; +- messages::internalError(asyncResp->res); +- return; ++ return false; } -+ using Readings = -+ std::vector<std::tuple<std::string, std::string, double, uint64_t>>; -+ using TimestampReadings = std::tuple<uint64_t, Readings>; -+ - private: - void doGet(crow::Response& res, const crow::Request&, - const std::vector<std::string>& params) override -@@ -92,7 +96,10 @@ class MetricReport : public Node + const auto& [timestamp, readings] = *timestampReadings; +- asyncResp->res.jsonValue["Timestamp"] = ++ json["Timestamp"] = + crow::utility::getDateTime(static_cast<time_t>(timestamp)); +- asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); ++ json["MetricValues"] = toMetricValues(readings); ++ return true; + } + } // namespace telemetry + +@@ -146,7 +144,11 @@ class MetricReport : public Node return; } -- fillReport(asyncResp, id, ret); -+ if (!fillReport(asyncResp->res.jsonValue, id, ret)) +- telemetry::fillReport(asyncResp, id, ret); ++ if (!telemetry::fillReport(asyncResp->res.jsonValue, id, ++ ret)) + { + messages::internalError(asyncResp->res); + } }, telemetry::service, reportPath, "org.freedesktop.DBus.Properties", "Get", -@@ -102,10 +109,6 @@ class MetricReport : public Node - "Update"); - } - -- using Readings = -- std::vector<std::tuple<std::string, std::string, double, uint64_t>>; -- using TimestampReadings = std::tuple<uint64_t, Readings>; -- - static nlohmann::json toMetricValues(const Readings& readings) - { - nlohmann::json metricValues = nlohmann::json::array_t(); -@@ -130,15 +133,15 @@ class MetricReport : public Node - return metricValues; - } - -- static void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, -- const std::string& id, -+ public: -+ static bool fillReport(nlohmann::json& json, const std::string& id, - const std::variant<TimestampReadings>& var) - { -- asyncResp->res.jsonValue["@odata.type"] = schemaType; -- asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; -- asyncResp->res.jsonValue["Id"] = id; -- asyncResp->res.jsonValue["Name"] = id; -- asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = -+ json["@odata.type"] = schemaType; -+ json["@odata.id"] = telemetry::metricReportUri + id; -+ json["Id"] = id; -+ json["Name"] = id; -+ json["MetricReportDefinition"]["@odata.id"] = - telemetry::metricReportDefinitionUri + id; - - const TimestampReadings* timestampReadings = -@@ -146,14 +149,14 @@ class MetricReport : public Node - if (!timestampReadings) - { - BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; -- messages::internalError(asyncResp->res); -- return; -+ return false; - } - - const auto& [timestamp, readings] = *timestampReadings; -- asyncResp->res.jsonValue["Timestamp"] = -+ json["Timestamp"] = - crow::utility::getDateTime(static_cast<time_t>(timestamp)); -- asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); -+ json["MetricValues"] = toMetricValues(readings); -+ return true; - } - - static constexpr const char* schemaType = -- -2.17.1 +2.16.6 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 833fabfec..35c6e90bc 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,15 +2,12 @@ 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: -- Redfish TelemetryService schema implementation - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31692/54 - - Add POST and DELETE in MetricReportDefinitions - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/46 + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/58 - Add support for MetricDefinition scheme - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/42 + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/54 - Sync Telmetry service with EventService - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/38798/9 + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/38798/21 |