From f99301c1a626951ee7feee081a1494e795d0e243 Mon Sep 17 00:00:00 2001 From: "Jason M. Bills" Date: Mon, 31 Aug 2020 13:56:28 -0700 Subject: Update to internal 0.74 Signed-off-by: Jason M. Bills --- ...sh-TelemetryService-schema-implementation.patch | 913 +++++++++++++++++++++ ...pport-for-POST-in-MetricReportDefinitions.patch | 594 ++++++++++++++ ...-for-DELETE-in-MetricReportDefinitions-st.patch | 68 ++ ...t-for-OnRequest-in-MetricReportDefinition.patch | 169 ++++ ...5-Add-support-for-MetricDefinition-scheme.patch | 535 ++++++++++++ ...x-MetricReport-timestamp-for-EventService.patch | 78 ++ .../interfaces/bmcweb/telemetry/README | 21 + 7 files changed, 2378 insertions(+) create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry') 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 new file mode 100644 index 000000000..3850c8fa8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch @@ -0,0 +1,913 @@ +From 7820421433349df28bd393e8d610d1848af0f1c8 Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Mon, 27 Apr 2020 17:24:15 +0200 +Subject: [PATCH 1/5] 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 - MonitoringService. +Added schemes attributes that are supported by MonitoringService +design. User is able to fetch basic information about reports if +MonitoringService is present in OpenBMC. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated conversion to duration format using whole + range of uint32_t type + - Validated assigning value to JSON response using different + closures and std::functions types + +Signed-off-by: Wludzik, Jozef +Signed-off-by: Adrian Ambrożewicz +Change-Id: Ie6b0b49f4ef5eeaef07d1209b6c349270c04d570 +--- + include/dbus_utility.hpp | 21 +++ + redfish-core/include/redfish.hpp | 10 ++ + redfish-core/include/utils/json_utils.hpp | 101 +++++++++++++ + redfish-core/include/utils/telemetry_utils.hpp | 100 +++++++++++++ + redfish-core/include/utils/time_utils.hpp | 97 +++++++++++++ + redfish-core/lib/metric_report.hpp | 149 +++++++++++++++++++ + redfish-core/lib/metric_report_definition.hpp | 193 +++++++++++++++++++++++++ + redfish-core/lib/service_root.hpp | 2 + + redfish-core/lib/telemetry_service.hpp | 92 ++++++++++++ + 9 files changed, 765 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/include/dbus_utility.hpp b/include/dbus_utility.hpp +index e1360f7..3df88d8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -109,5 +109,26 @@ inline void checkDbusPathExists(const std::string& path, Callback&& callback) + std::array()); + } + ++template ++inline void getSubTreePaths(Callback&& callback, const std::string& path, ++ int depth, Array& interfaces) ++{ ++ crow::connections::systemBus->async_method_call( ++ callback, "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", path, depth, ++ interfaces); ++} ++ ++template ++inline void getAllProperties(Callback&& callback, const std::string& service, ++ const std::string& path, ++ const std::string& interface) ++{ ++ crow::connections::systemBus->async_method_call( ++ callback, service, path, "org.freedesktop.DBus.Properties", "GetAll", ++ interface); ++} ++ + } // namespace utility + } // namespace dbus +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index cc98e1a..3d4c117 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,8 @@ + #include "../lib/log_services.hpp" + #include "../lib/managers.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" +@@ -35,6 +37,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 +@@ -202,6 +205,13 @@ class RedfishService + nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back(std::make_unique(app)); + ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back( ++ std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ + for (const auto& node : nodes) + { + node->initPrivileges(); +diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp +index d578de4..fbb259d 100644 +--- a/redfish-core/include/utils/json_utils.hpp ++++ b/redfish-core/include/utils/json_utils.hpp +@@ -13,15 +13,19 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + */ ++ + #pragma once + + #include + #include + ++#include + #include + #include + + #include ++#include ++#include + + namespace redfish + { +@@ -436,5 +440,102 @@ bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key, + return details::unpackValue(jsonValue, key, value); + } + ++template ++struct IsStdFunction ++{ ++ static constexpr bool value = false; ++}; ++ ++template ++struct IsStdFunction> ++{ ++ static constexpr bool value = true; ++}; ++ ++template ++constexpr bool is_std_function_v = IsStdFunction::value; ++ ++/** ++ * @brief Assign dbus property to http response attribute if property is stored ++ * on the map. ++ */ ++template ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* propertyName, nlohmann::json& attribute, const S& convert) ++{ ++ if constexpr (is_std_function_v) ++ { ++ if (!convert) ++ { ++ BMCWEB_LOG_ERROR << "Passed empty target as convert argument"; ++ return false; ++ } ++ } ++ ++ auto found = ret.find(propertyName); ++ if (found != ret.end()) ++ { ++ auto property = std::get_if(&found->second); ++ if (property) ++ { ++ attribute = convert(*property); ++ return true; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Variant does not contain this type"; ++ } ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Element not found in map"; ++ } ++ ++ return false; ++} ++ ++template ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* propertyName, nlohmann::json& attribute) ++{ ++ return assignIfPresent(ret, propertyName, attribute, ++ [](const T& v) -> T { return v; }); ++} ++ ++template ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* attributeName, crow::Response& res) ++{ ++ return assignIfPresent(ret, attributeName, res.jsonValue[attributeName]); ++} ++ ++/** ++ * @brief Translate dbusPaths received from ObjectMapper into Redfish ++ * collection members and fill http response with those information. ++ */ ++inline void dbusPathsToMembersArray(crow::Response& res, ++ const std::vector& reports, ++ const char* path) ++{ ++ nlohmann::json& members = res.jsonValue["Members"]; ++ members = nlohmann::json::array(); ++ ++ for (const std::string& objpath : reports) ++ { ++ std::size_t lastPos = objpath.rfind("/"); ++ if (lastPos == std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find '/' in " << objpath; ++ continue; ++ } ++ members.push_back({{"@odata.id", path + objpath.substr(lastPos + 1)}}); ++ } ++ ++ res.jsonValue["Members@odata.count"] = members.size(); ++} ++ + } // namespace json_util + } // namespace redfish +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +new file mode 100644 +index 0000000..05ed00f +--- /dev/null ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -0,0 +1,100 @@ ++/* ++// Copyright (c) 2018-2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++namespace redfish ++{ ++ ++namespace telemetry ++{ ++ ++static constexpr const char* metricReportDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; ++static constexpr const char* metricReportUri = ++ "/redfish/v1/TelemetryService/MetricReports/"; ++static constexpr const char* reportInterface = ++ "xyz.openbmc_project.MonitoringService.Report"; ++static constexpr const char* telemetryPath = ++ "/xyz/openbmc_project/MonitoringService/Reports/TelemetryService"; ++ ++static void getReportCollection(const std::shared_ptr& asyncResp, ++ const char* uri) ++{ ++ const std::array interfaces = {reportInterface}; ++ ++ dbus::utility::getSubTreePaths( ++ [asyncResp, uri](const boost::system::error_code ec, ++ const std::vector& reports) { ++ if (ec == boost::system::errc::no_such_file_or_directory) ++ { ++ asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); ++ asyncResp->res.jsonValue["Members@odata.count"] = 0; ++ return; ++ } ++ ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::dbusPathsToMembersArray(asyncResp->res, reports, uri); ++ }, ++ telemetryPath, 1, interfaces); ++} ++ ++template ++static void getReport(const std::shared_ptr& asyncResp, ++ const std::string& id, const char* schemaType, ++ const Callback&& callback) ++{ ++ const std::array interfaces = {reportInterface}; ++ ++ dbus::utility::getSubTreePaths( ++ [asyncResp, id, schemaType, ++ callback](const boost::system::error_code ec, ++ const std::vector& reports) { ++ if (ec == boost::system::errc::no_such_file_or_directory) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ const std::string target = "/xyz/openbmc_project/" ++ "MonitoringService/Reports/" ++ "TelemetryService/" + ++ id; ++ auto path = std::find(reports.begin(), reports.end(), target); ++ if (path == std::end(reports)) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ callback(asyncResp, *path, id); ++ }, ++ telemetryPath, 1, interfaces); ++} ++} // 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..0256b3f +--- /dev/null ++++ b/redfish-core/include/utils/time_utils.hpp +@@ -0,0 +1,97 @@ ++/* ++// Copyright (c) 2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++namespace redfish ++{ ++ ++namespace time_utils ++{ ++ ++namespace details ++{ ++ ++template ++std::string toDurationFormatItem(std::chrono::milliseconds& duration, ++ const char* postfix) ++{ ++ const auto t = std::chrono::duration_cast(duration); ++ if (t.count() == 0) ++ { ++ return ""; ++ } ++ ++ std::stringstream ss; ++ if constexpr (std::is_same::value) ++ { ++ ss << static_cast(t.count()) / ++ static_cast(std::chrono::milliseconds::period::den); ++ } ++ else ++ { ++ ss << t.count(); ++ } ++ ss << postfix; ++ duration -= t; ++ return ss.str(); ++} ++ ++} // namespace details ++ ++/** ++ * @brief Convert time value into duration format that is based on ISO 8601. ++ * Pattern: "-?P(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+(.\\d+)?S)?)?" ++ * Reference: "Redfish Telemetry White Paper". ++ */ ++std::string toDurationFormat(const uint32_t ms) ++{ ++ std::chrono::milliseconds duration(ms); ++ if (duration.count() == 0) ++ { ++ return "PT0S"; ++ } ++ ++ std::string fmt; ++ fmt.reserve(sizeof("PxxxDTxxHxxMxx.xxxxxxS")); ++ ++ using Days = std::chrono::duration>; ++ ++ fmt += "P"; ++ fmt += details::toDurationFormatItem(duration, "D"); ++ if (duration.count() == 0) ++ { ++ return fmt; ++ } ++ ++ fmt += "T"; ++ fmt += details::toDurationFormatItem(duration, "H"); ++ fmt += details::toDurationFormatItem(duration, "M"); ++ fmt += ++ details::toDurationFormatItem(duration, "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..a52d680 +--- /dev/null ++++ b/redfish-core/lib/metric_report.hpp +@@ -0,0 +1,149 @@ ++/* ++// Copyright (c) 2018-2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++#include "node.hpp" ++#include "utils/telemetry_utils.hpp" ++ ++#include ++ ++#include ++#include ++ ++namespace redfish ++{ ++ ++class MetricReportCollection : public Node ++{ ++ public: ++ MetricReportCollection(CrowApp& app) : Node(app, telemetry::metricReportUri) ++ { ++ 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& req, ++ const std::vector& params) 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(res); ++ telemetry::getReportCollection(asyncResp, telemetry::metricReportUri); ++ } ++}; ++ ++class MetricReport : public Node ++{ ++ public: ++ MetricReport(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportUri) + "/", ++ 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& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ telemetry::getReport(asyncResp, id, schemaType, getReportProperties); ++ } ++ ++ using Readings = ++ std::vector>; ++ using MetricValues = std::vector>; ++ ++ static MetricValues toMetricValues(const Readings& readings) ++ { ++ MetricValues metricValues; ++ ++ for (auto& [id, metadata, sensorValue, timestamp] : readings) ++ { ++ metricValues.push_back({ ++ {"MetricId", id}, ++ {"MetricProperty", metadata}, ++ {"MetricValue", std::to_string(sensorValue)}, ++ {"Timestamp", crow::utility::getDateTime(timestamp)}, ++ }); ++ } ++ ++ return metricValues; ++ } ++ ++ static void getReportProperties(const std::shared_ptr asyncResp, ++ const std::string& reportPath, ++ const std::string& id) ++ { ++ 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; ++ ++ dbus::utility::getAllProperties( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const boost::container::flat_map< ++ std::string, std::variant>& ret) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::assignIfPresent( ++ ret, "Timestamp", asyncResp->res.jsonValue["Timestamp"], ++ crow::utility::getDateTime); ++ json_util::assignIfPresent( ++ ret, "Readings", asyncResp->res.jsonValue["MetricValues"], ++ toMetricValues); ++ }, ++ "xyz.openbmc_project.MonitoringService", reportPath, ++ "xyz.openbmc_project.MonitoringService.Report"); ++ } ++ ++ 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..d82ae59 +--- /dev/null ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -0,0 +1,193 @@ ++/* ++// Copyright (c) 2018-2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++#include "node.hpp" ++#include "utils/telemetry_utils.hpp" ++#include "utils/time_utils.hpp" ++ ++#include ++ ++#include ++#include ++ ++namespace redfish ++{ ++ ++class MetricReportDefinitionCollection : public Node ++{ ++ public: ++ MetricReportDefinitionCollection(CrowApp& app) : ++ Node(app, telemetry::metricReportDefinitionUri) ++ { ++ 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& req, ++ const std::vector& params) 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(res); ++ telemetry::getReportCollection(asyncResp, ++ telemetry::metricReportDefinitionUri); ++ } ++}; ++ ++class MetricReportDefinition : public Node ++{ ++ public: ++ MetricReportDefinition(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportDefinitionUri) + "/", ++ 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& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ ++ telemetry::getReport(asyncResp, id, schemaType, ++ getReportDefinitonProperties); ++ } ++ ++ static std::vector ++ toReportActions(const std::vector& actions) ++ { ++ const boost::container::flat_map ++ reportActions = { ++ {"Event", "RedfishEvent"}, ++ {"Log", "LogToMetricReportsCollection"}, ++ }; ++ ++ std::vector out; ++ for (auto& action : actions) ++ { ++ auto found = reportActions.find(action); ++ if (found != reportActions.end()) ++ { ++ out.emplace_back(found->second); ++ } ++ } ++ return out; ++ } ++ ++ using ReadingParameters = ++ std::vector, ++ std::string, std::string, std::string>>; ++ using Metrics = std::vector>>>; ++ ++ static Metrics toMetrics(const ReadingParameters& params) ++ { ++ Metrics metrics; ++ ++ for (auto& [sensorPaths, operationType, id, metadata] : params) ++ { ++ metrics.push_back({ ++ {"MetricId", id}, ++ {"MetricProperties", std::vector() = {metadata}}, ++ }); ++ } ++ ++ return metrics; ++ } ++ ++ static void ++ getReportDefinitonProperties(const std::shared_ptr asyncResp, ++ const std::string& reportPath, ++ const std::string& id) ++ { ++ 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"; ++ ++ dbus::utility::getAllProperties( ++ [asyncResp](const boost::system::error_code ec, ++ const boost::container::flat_map< ++ std::string, ++ std::variant, ++ uint32_t, ReadingParameters>>& ret) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::assignIfPresent>( ++ ret, "ReportAction", ++ asyncResp->res.jsonValue["ReportActions"], toReportActions); ++ auto assigned = json_util::assignIfPresent( ++ ret, "ReportingType", ++ asyncResp->res.jsonValue["MetricReportDefinitionType"]); ++ if (assigned && ++ asyncResp->res.jsonValue["MetricReportDefinitionType"] == ++ "Periodic") ++ { ++ json_util::assignIfPresent( ++ ret, "ScanPeriod", ++ asyncResp->res ++ .jsonValue["Schedule"]["RecurrenceInterval"], ++ time_utils::toDurationFormat); ++ } ++ json_util::assignIfPresent( ++ ret, "ReadingParameters", ++ asyncResp->res.jsonValue["Metrics"], toMetrics); ++ }, ++ "xyz.openbmc_project.MonitoringService", reportPath, ++ "xyz.openbmc_project.MonitoringService.Report"); ++ } ++ ++ public: ++ 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 b6bd6e0..3302390 100644 +--- a/redfish-core/lib/service_root.hpp ++++ b/redfish-core/lib/service_root.hpp +@@ -69,6 +69,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..a410700 +--- /dev/null ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -0,0 +1,92 @@ ++/* ++// Copyright (c) 2018-2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++#include "node.hpp" ++#include "utils/time_utils.hpp" ++ ++#include ++ ++#include ++ ++namespace redfish ++{ ++ ++class TelemetryService : public Node ++{ ++ public: ++ TelemetryService(CrowApp& 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& req, ++ const std::vector& params) override ++ { ++ res.jsonValue["@odata.type"] = ++ "#TelemetryService.v1_2_0.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"; ++ ++ getMonitoringServiceProperties(res); ++ } ++ ++ void getMonitoringServiceProperties(crow::Response& res) ++ { ++ auto asyncResp = std::make_shared(res); ++ dbus::utility::getAllProperties( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const boost::container::flat_map>& ret) { ++ if (ec) ++ { ++ asyncResp->res.jsonValue["Status"]["State"] = "Absent"; ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; ++ ++ json_util::assignIfPresent(ret, "MaxReports", ++ asyncResp->res); ++ json_util::assignIfPresent( ++ ret, "PollRateResolution", ++ asyncResp->res.jsonValue["MinCollectionInterval"], ++ time_utils::toDurationFormat); ++ }, ++ "xyz.openbmc_project.MonitoringService", ++ "/xyz/openbmc_project/MonitoringService/Reports", ++ "xyz.openbmc_project.MonitoringService.ReportsManagement"); ++ } ++}; ++} // namespace redfish +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch new file mode 100644 index 000000000..8a8690bf3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch @@ -0,0 +1,594 @@ +From 941be2c7d819b4a55d5a8b67948e53658d907789 Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Mon, 18 May 2020 11:56:57 +0200 +Subject: [PATCH 2/5] Add support for POST in MetricReportDefinitions + +Added POST action in MetricReportDefinitions node to allow user +to add new MetricReportDefinition. Using minimal set of +MetricReportDefinition parameters from user bmcweb converts it to +DBus call "AddReport" to MonitoringService that serves as a backend +for TelemetryService. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated good cases with different parameters for POST action + - Validated bad cases with different parameters for POST action + - Validated fromDurationFormat() with range of arguments starting + from PT0.0S up to P49D (it is an upper limit for uint32_t) + +Signed-off-by: Wludzik, Jozef +Signed-off-by: Krzysztof Grobelny +Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a +--- + redfish-core/include/utils/time_utils.hpp | 49 +++ + .../include/utils/validate_params_length.hpp | 109 +++++++ + redfish-core/lib/metric_report_definition.hpp | 347 +++++++++++++++++++++ + 3 files changed, 505 insertions(+) + create mode 100644 redfish-core/include/utils/validate_params_length.hpp + +diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp +index 0256b3f..c365585 100644 +--- a/redfish-core/include/utils/time_utils.hpp ++++ b/redfish-core/include/utils/time_utils.hpp +@@ -57,6 +57,32 @@ std::string toDurationFormatItem(std::chrono::milliseconds& duration, + return ss.str(); + } + ++template ++static long long fromDurationFormatItem(std::string_view& fmt, ++ const char* postfix) ++{ ++ auto pos = fmt.find(postfix); ++ if (pos == std::string::npos) ++ { ++ return 0; ++ } ++ ++ long out; ++ if constexpr (std::is_same::value) ++ { ++ /* Half point is added to avoid numeric error on rounding */ ++ out = static_cast(std::strtof(fmt.data(), nullptr) * ++ std::chrono::milliseconds::period::den + ++ 0.5f); ++ } ++ else ++ { ++ out = std::strtol(fmt.data(), nullptr, 10); ++ } ++ fmt.remove_prefix(pos + 1); ++ return std::chrono::milliseconds(T(out)).count(); ++} ++ + } // namespace details + + /** +@@ -93,5 +119,28 @@ std::string toDurationFormat(const uint32_t ms) + return fmt; + } + ++static uint32_t fromDurationFormat(std::string_view fmt) ++{ ++ if (fmt.empty() || fmt[0] != 'P') ++ { ++ return 0; ++ } ++ using Days = std::chrono::duration>; ++ ++ fmt.remove_prefix(1); ++ auto out = details::fromDurationFormatItem(fmt, "D"); ++ if (fmt[0] != 'T') ++ { ++ return static_cast(out); ++ } ++ ++ fmt.remove_prefix(1); ++ out += details::fromDurationFormatItem(fmt, "H"); ++ out += details::fromDurationFormatItem(fmt, "M"); ++ out += details::fromDurationFormatItem(fmt, "S"); ++ ++ return static_cast(out); ++} ++ + } // namespace time_utils + } // namespace redfish +diff --git a/redfish-core/include/utils/validate_params_length.hpp b/redfish-core/include/utils/validate_params_length.hpp +new file mode 100644 +index 0000000..c4e0569 +--- /dev/null ++++ b/redfish-core/include/utils/validate_params_length.hpp +@@ -0,0 +1,109 @@ ++#pragma once ++ ++namespace redfish ++{ ++namespace detail ++{ ++template ++bool validateParamsLength(crow::Response& res, Limits&& limits, ++ std::index_sequence) ++{ ++ return (... && std::get(limits).validate(res)); ++} ++} // namespace detail ++ ++template ++struct ItemSizeValidator ++{ ++ ItemSizeValidator(const T&& item, std::string_view name, size_t limit) : ++ item(std::forward(item)), name(name), limit(limit) ++ {} ++ ++ bool validate(crow::Response& res) const ++ { ++ return ItemSizeValidator::validateItem(res, item, name, limit); ++ } ++ ++ private: ++ static bool validateItem(crow::Response& res, size_t item, ++ std::string_view name, size_t limit) ++ { ++ if (item > static_cast(limit)) ++ { ++ messages::stringValueTooLong(res, std::string(name), ++ static_cast(limit)); ++ return false; ++ } ++ return true; ++ } ++ ++ static bool validateItem(crow::Response& res, std::string_view item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.size(), name, limit); ++ } ++ ++ static bool validateItem(crow::Response& res, const std::string& item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.size(), name, limit); ++ } ++ ++ static bool validateItem(crow::Response& res, ++ const sdbusplus::message::object_path& item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.str.size(), name, limit); ++ } ++ ++ T item; ++ std::string_view name; ++ size_t limit; ++}; ++ ++template ++ItemSizeValidator(const T&, std::string_view, size_t) ++ -> ItemSizeValidator; ++ ++ItemSizeValidator(const char*, std::string_view, size_t) ++ ->ItemSizeValidator; ++ ++template ++struct ArrayItemsValidator ++{ ++ ArrayItemsValidator(const ContainerT& item, std::string_view name, ++ size_t limit) : ++ item(item), ++ name(name), limit(limit) ++ {} ++ ++ bool validate(crow::Response& res) const ++ { ++ return std::all_of( ++ item.begin(), item.end(), [&res, this](const auto& item) { ++ return ItemSizeValidator(item, name, limit).validate(res); ++ }); ++ } ++ ++ private: ++ const ContainerT& item; ++ std::string_view name; ++ size_t limit; ++}; ++ ++template ++bool validateParamLength(crow::Response& res, T&& item, std::string_view name, ++ size_t limit) ++{ ++ return ItemSizeValidator(std::forward(item), name, limit).validate(res); ++} ++ ++template ++bool validateParamsLength(crow::Response& res, Limits&& limits) ++{ ++ return detail::validateParamsLength( ++ res, std::forward(limits), ++ std::make_index_sequence>>()); ++} ++ ++} // namespace redfish +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index d82ae59..ecbab0c 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -17,16 +17,29 @@ + #pragma once + + #include "node.hpp" ++#include "sensors.hpp" + #include "utils/telemetry_utils.hpp" + #include "utils/time_utils.hpp" ++#include "utils/validate_params_length.hpp" + ++#include ++#include + #include + ++#include + #include ++#include + #include + + namespace redfish + { ++static constexpr size_t maxShortParamLength = 255; ++static constexpr size_t maxLongParamLength = 1024; ++static constexpr size_t maxDbusNameLength = 255; ++static constexpr size_t maxArraySize = 100; ++static constexpr size_t maxReportIdLen = ++ maxDbusNameLength - std::string_view(telemetry::telemetryPath).size() - ++ std::string_view("/").size(); + + class MetricReportDefinitionCollection : public Node + { +@@ -57,6 +70,339 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ using ChassisSensorNode = std::pair; ++ using DbusSensor = sdbusplus::message::object_path; ++ using DbusSensors = std::vector; ++ using MetricParam = ++ std::tuple; ++ using MetricParams = std::vector; ++ /* ++ * AddReportArgs misses "Domain" parameter because it is constant for ++ * TelemetryService and equals "TelemetryService". ++ */ ++ using AddReportArgs = ++ std::tuple, uint32_t, ++ MetricParams>; ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ AddReportArgs addReportArgs; ++ if (!getUserParameters(res, req, addReportArgs)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set chassisSensorSet; ++ auto unmatched = getChassisSensorNode( ++ std::get(addReportArgs), chassisSensorSet); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ auto addReportReq = ++ std::make_shared(addReportArgs, asyncResp); ++ for (auto& [chassis, sensorType] : chassisSensorSet) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map& ++ uriToDbus) { *addReportReq += uriToDbus; }); ++ } ++ } ++ ++ static std::optional ++ replaceReportActions(std::vector& actions) ++ { ++ const boost::container::flat_map ++ reportActions = { ++ {"RedfishEvent", "Event"}, ++ {"LogToMetricReportsCollection", "Log"}, ++ }; ++ ++ for (auto& action : actions) ++ { ++ auto found = reportActions.find(action); ++ if (found == reportActions.end()) ++ { ++ return action; ++ } ++ action = found->second; ++ } ++ return std::nullopt; ++ } ++ ++ static constexpr const std::array supportedDefinitionType = ++ {"Periodic", "OnRequest"}; ++ ++ static bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& params) ++ { ++ std::vector metrics; ++ std::optional schedule; ++ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = ++ params; ++ if (!json_util::readJson(req, res, "Id", name, "Metrics", metrics, ++ "MetricReportDefinitionType", reportingType, ++ "ReportActions", reportActions, "Schedule", ++ schedule)) ++ { ++ return false; ++ } ++ ++ auto limits = std::make_tuple( ++ ItemSizeValidator(name, "Id", maxReportIdLen), ++ ItemSizeValidator(reportingType, "MetricReportDefinitionType", ++ maxShortParamLength), ++ ItemSizeValidator(reportActions.size(), "ReportActions.size()", ++ maxArraySize), ++ ArrayItemsValidator(reportActions, "ReportActions", ++ maxShortParamLength), ++ ItemSizeValidator(metrics.size(), "Metrics.size()", maxArraySize)); ++ ++ if (!validateParamsLength(res, std::move(limits))) ++ { ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ++ "_"; ++ if (name.empty() || name.find_first_not_of(allowedCharactersInName) != ++ std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueFormatError(res, name, "Id"); ++ return false; ++ } ++ ++ if (!std::any_of( ++ supportedDefinitionType.begin(), supportedDefinitionType.end(), ++ [reportingType](auto& x) { return reportingType == x; })) ++ { ++ messages::propertyValueNotInList(res, reportingType, ++ "MetricReportDefinitionType"); ++ return false; ++ } ++ ++ auto unmatched = replaceReportActions(reportActions); ++ if (unmatched) ++ { ++ messages::propertyValueNotInList(res, *unmatched, "ReportActions"); ++ return false; ++ } ++ ++ if (reportingType == "Periodic") ++ { ++ if (!schedule) ++ { ++ messages::createFailedMissingReqProperties(res, "Schedule"); ++ return false; ++ } ++ ++ std::string interval; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ interval)) ++ { ++ return false; ++ } ++ ++ if (!validateParamLength(res, interval, "RecurrenceInterval", ++ maxShortParamLength)) ++ { ++ return false; ++ } ++ ++ constexpr const char* durationPattern = ++ "-?P(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+(.\\d+)?S)?)?"; ++ if (!std::regex_match(interval, std::regex(durationPattern))) ++ { ++ messages::propertyValueFormatError(res, interval, ++ "RecurrenceInterval"); ++ return false; ++ } ++ ++ scanPeriod = time_utils::fromDurationFormat(interval); ++ } ++ ++ return fillMetricParams(metrics, res, metricParams); ++ } ++ ++ static bool fillMetricParams(std::vector& metrics, ++ crow::Response& res, ++ MetricParams& metricParams) ++ { ++ metricParams.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string metricId; ++ std::vector metricProperties; ++ if (!json_util::readJson(m, res, "MetricId", metricId, ++ "MetricProperties", metricProperties)) ++ { ++ return false; ++ } ++ ++ auto limits = std::make_tuple( ++ ItemSizeValidator(metricId, "MetricId", maxLongParamLength), ++ ItemSizeValidator(metricProperties.size(), ++ "MetricProperties.size()", maxArraySize), ++ ArrayItemsValidator(metricProperties, "MetricProperties", ++ maxLongParamLength)); ++ ++ if (!validateParamsLength(res, std::move(limits))) ++ { ++ return false; ++ } ++ ++ DbusSensors dbusSensors; ++ dbusSensors.reserve(metricProperties.size()); ++ std::for_each( ++ metricProperties.begin(), metricProperties.end(), ++ [&dbusSensors](auto& x) { dbusSensors.emplace_back(x); }); ++ ++ metricParams.emplace_back( ++ dbusSensors, "SINGLE", metricId, ++ boost::algorithm::join(metricProperties, ", ")); ++ } ++ return true; ++ } ++ ++ static std::optional getChassisSensorNode( ++ const MetricParams& metricParams, ++ boost::container::flat_set& matched) ++ { ++ for (const auto& metricParam : metricParams) ++ { ++ const auto& sensors = std::get(metricParam); ++ for (const auto& sensor : sensors) ++ { ++ /* ++ * Support only following paths: ++ * "/redfish/v1/Chassis//Power#/..." ++ * "/redfish/v1/Chassis//Sensors/..." ++ * "/redfish/v1/Chassis//Thermal#/..." ++ */ ++ constexpr const char* uriPattern = ++ "\\/redfish\\/v1\\/Chassis\\/(\\w+)\\/" ++ "(Power|Sensors|Thermal)[#]?\\/.*"; ++ std::smatch m; ++ if (!std::regex_match(sensor.str, m, std::regex(uriPattern)) || ++ m.size() != 3) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << sensor.str ++ << " with pattern " << uriPattern; ++ return sensor; ++ } ++ ++ BMCWEB_LOG_DEBUG << "Chassis=" << m[1] << ", Type=" << m[2]; ++ matched.emplace(m[1], m[2]); ++ } ++ } ++ return std::nullopt; ++ } ++ ++ static std::optional replaceUriWithDbus( ++ const boost::container::flat_map& uriToDbus, ++ MetricParams& metricParams) ++ { ++ for (auto& metricParam : metricParams) ++ { ++ auto& dbusSensors = std::get(metricParam); ++ for (auto& uri : dbusSensors) ++ { ++ auto dbus = uriToDbus.find(uri); ++ if (dbus == uriToDbus.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " ++ << uri.str; ++ return uri; ++ } ++ uri = dbus->second; ++ } ++ } ++ return std::nullopt; ++ } ++ ++ static void addReport(std::shared_ptr asyncResp, ++ AddReportArgs args) ++ { ++ constexpr const char* domain = "TelemetryService"; ++ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = ++ args; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, name](const boost::system::error_code ec, ++ const std::string ret) { ++ 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) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ messages::created(asyncResp->res); ++ }, ++ "xyz.openbmc_project.MonitoringService", ++ "/xyz/openbmc_project/MonitoringService/Reports", ++ "xyz.openbmc_project.MonitoringService.ReportsManagement", ++ "AddReport", name, domain, reportingType, reportActions, scanPeriod, ++ metricParams); ++ } ++ ++ class AddReport ++ { ++ public: ++ AddReport(AddReportArgs& args, std::shared_ptr& asyncResp) : ++ asyncResp{asyncResp}, addReportArgs{args} ++ {} ++ ~AddReport() ++ { ++ auto unmatched = replaceUriWithDbus( ++ uriToDbus, std::get(addReportArgs)); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ addReport(asyncResp, addReportArgs); ++ } ++ ++ AddReport& operator+=( ++ const boost::container::flat_map& rhs) ++ { ++ this->uriToDbus.insert(rhs.begin(), rhs.end()); ++ return *this; ++ } ++ ++ private: ++ std::shared_ptr asyncResp; ++ AddReportArgs addReportArgs; ++ boost::container::flat_map uriToDbus{}; ++ }; + }; + + class MetricReportDefinition : public Node +@@ -148,6 +494,7 @@ class MetricReportDefinition : public Node + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = + telemetry::metricReportUri + id; + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; ++ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; + + dbus::utility::getAllProperties( + [asyncResp](const boost::system::error_code ec, +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch new file mode 100644 index 000000000..4c49b0cd3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch @@ -0,0 +1,68 @@ +From 8b2f4a6fe57bf2410cdb22f8c3c695e98d583040 Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Mon, 18 May 2020 12:40:15 +0200 +Subject: [PATCH 3/5] Add support for DELETE in MetricReportDefinitions/ + +Added support for DELETE action in MetricReportDefinitions/ +node. It allows user to remove MetricReportDefinition together +with MetricReport connected to it. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated DELETE action by removing exisiting + MetricReportDefinitions from MonitoringService + - Validated DELETE action with negative cases when + MetricReportDefinition does not exist + +Signed-off-by: Wludzik, Jozef +Change-Id: Iffde9f7bbf2955376e9714ac8d833967bd25eaa3 +--- + redfish-core/lib/metric_report_definition.hpp | 32 +++++++++++++++++++++++++++ + 1 file changed, 32 insertions(+) + +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index ecbab0c..8e04ac8 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -533,6 +533,38 @@ class MetricReportDefinition : public Node + "xyz.openbmc_project.MonitoringService.Report"); + } + ++ void doDelete(crow::Response& res, const crow::Request& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ telemetry::getReport(asyncResp, id, schemaType, deleteReport); ++ } ++ ++ static void deleteReport(const std::shared_ptr& asyncResp, ++ const std::string& path, const std::string& id) ++ { ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ asyncResp->res.result(boost::beast::http::status::no_content); ++ }, ++ "xyz.openbmc_project.MonitoringService", path, ++ "xyz.openbmc_project.Object.Delete", "Delete"); ++ } ++ + public: + static constexpr const char* schemaType = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch new file mode 100644 index 000000000..e996ac585 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch @@ -0,0 +1,169 @@ +From 9fc7d722b3192df9940062185b40ebb0fabad518 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Mon, 8 Jun 2020 15:16:10 +0200 +Subject: [PATCH 4/5] Add support for "OnRequest" in MetricReportDefinition + +Added support for "OnRequest" of ReportingType property in +MetricReportDefinition node. Now user is able to create +MetricReportDefinition that is updated on every GET request +on MetricReport. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Manually tested via curl + +Signed-off-by: Krzysztof Grobelny +Change-Id: I1cdfe47e56fdc5ec9753558145d0bf3645160aaf +--- + include/dbus_utility.hpp | 30 +++++++++++++++ + redfish-core/include/utils/telemetry_utils.hpp | 8 ++-- + redfish-core/lib/metric_report.hpp | 53 +++++++++++++++++++++++++- + 3 files changed, 87 insertions(+), 4 deletions(-) + +diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp +index 3df88d8..029d8d8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -17,6 +17,7 @@ + + #include + ++#include + #include + + namespace dbus +@@ -130,5 +131,34 @@ inline void getAllProperties(Callback&& callback, const std::string& service, + interface); + } + ++template ++static void getProperty( ++ std::function callback, ++ const std::string& service, const std::string& path, ++ const std::string& interface, const std::string& property) ++{ ++ crow::connections::systemBus->async_method_call( ++ [callback](const boost::system::error_code ec, ++ const std::variant& value) { ++ if (ec) ++ { ++ callback(ec, T{}); ++ return; ++ } ++ ++ if (auto v = std::get_if(&value)) ++ { ++ callback(ec, *v); ++ return; ++ } ++ ++ callback(boost::system::errc::make_error_code( ++ boost::system::errc::io_error), ++ T{}); ++ }, ++ service, path, "org.freedesktop.DBus.Properties", "Get", interface, ++ property); ++} ++ + } // namespace utility + } // namespace dbus +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 05ed00f..6c4e810 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -26,6 +26,8 @@ static constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions/"; + static constexpr const char* metricReportUri = + "/redfish/v1/TelemetryService/MetricReports/"; ++static constexpr const char* monitoringService = ++ "xyz.openbmc_project.MonitoringService"; + static constexpr const char* reportInterface = + "xyz.openbmc_project.MonitoringService.Report"; + static constexpr const char* telemetryPath = +@@ -66,9 +68,9 @@ static void getReport(const std::shared_ptr& asyncResp, + const std::array interfaces = {reportInterface}; + + dbus::utility::getSubTreePaths( +- [asyncResp, id, schemaType, +- callback](const boost::system::error_code ec, +- const std::vector& reports) { ++ [asyncResp, id, schemaType, callback = std::move(callback)]( ++ const boost::system::error_code ec, ++ const std::vector& reports) { + if (ec == boost::system::errc::no_such_file_or_directory) + { + messages::resourceNotFound(asyncResp->res, schemaType, id); +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index a52d680..877e7f1 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -85,7 +85,7 @@ class MetricReport : public Node + } + + const std::string& id = params[0]; +- telemetry::getReport(asyncResp, id, schemaType, getReportProperties); ++ telemetry::getReport(asyncResp, id, schemaType, updateReportIfRequired); + } + + using Readings = +@@ -143,6 +143,57 @@ class MetricReport : public Node + "xyz.openbmc_project.MonitoringService.Report"); + } + ++ template ++ static void updateReport(Callback&& callback, ++ const std::shared_ptr& asyncResp, ++ const std::string& path) ++ { ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, callback{std::move(callback)}]( ++ const boost::system::error_code& ec) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ callback(); ++ }, ++ telemetry::monitoringService, path, telemetry::reportInterface, ++ "Update"); ++ } ++ ++ static void ++ updateReportIfRequired(const std::shared_ptr asyncResp, ++ const std::string& reportPath, ++ const std::string& id) ++ { ++ dbus::utility::getProperty( ++ [asyncResp, id, reportPath](const boost::system::error_code& ec, ++ const std::string& reportingType) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ if (reportingType == "OnRequest") ++ { ++ updateReport( ++ [asyncResp, reportPath, id] { ++ getReportProperties(asyncResp, reportPath, id); ++ }, ++ asyncResp, reportPath); ++ } ++ else ++ { ++ getReportProperties(asyncResp, reportPath, id); ++ } ++ }, ++ telemetry::monitoringService, reportPath, ++ telemetry::reportInterface, "ReportingType"); ++ } ++ + static constexpr const char* schemaType = + "#MetricReport.v1_3_0.MetricReport"; + }; +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch new file mode 100644 index 000000000..f7da8a556 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch @@ -0,0 +1,535 @@ +From b1da8901b5985d6a77b63ca9eb0570b46528f0bd Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Mon, 8 Jun 2020 17:15:54 +0200 +Subject: [PATCH 5/5] Add support for MetricDefinition scheme + +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. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated a presence of MetricDefinition members + +Signed-off-by: Wludzik, Jozef +Signed-off-by: Krzysztof Grobelny +Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 +--- + redfish-core/include/redfish.hpp | 3 + + redfish-core/include/utils/telemetry_utils.hpp | 2 + + redfish-core/lib/metric_definition.hpp | 300 +++++++++++++++++++++++++ + redfish-core/lib/metric_report.hpp | 65 +++++- + redfish-core/lib/sensors.hpp | 43 +++- + redfish-core/lib/telemetry_service.hpp | 2 + + 6 files changed, 402 insertions(+), 13 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 3d4c117..2a12bf9 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,7 @@ + #include "../lib/log_services.hpp" + #include "../lib/managers.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" +@@ -206,6 +207,8 @@ class RedfishService + nodes.emplace_back(std::make_unique(app)); + + nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back( + std::make_unique(app)); + nodes.emplace_back(std::make_unique(app)); +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 6c4e810..bb747c4 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -22,6 +22,8 @@ namespace redfish + namespace telemetry + { + ++static constexpr const char* metricDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricDefinitions/"; + static constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions/"; + static 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..837a068 +--- /dev/null ++++ b/redfish-core/lib/metric_definition.hpp +@@ -0,0 +1,300 @@ ++/* ++// Copyright (c) 2018-2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++ ++#pragma once ++ ++#include "node.hpp" ++#include "sensors.hpp" ++#include "utils/telemetry_utils.hpp" ++ ++namespace redfish ++{ ++ ++namespace chassis ++{ ++template ++static inline void getChassisNames(F&& callback) ++{ ++ const std::array interfaces = { ++ "xyz.openbmc_project.Inventory.Item.Board", ++ "xyz.openbmc_project.Inventory.Item.Chassis"}; ++ ++ dbus::utility::getSubTreePaths( ++ [callback{std::move(callback)}](const boost::system::error_code ec, ++ std::vector& chassisList) { ++ if (ec) ++ { ++ return; ++ } ++ ++ std::vector chassisNames; ++ chassisNames.reserve(chassisList.size()); ++ for (auto& chassisPath : chassisList) ++ { ++ auto pos = chassisPath.rfind("/"); ++ if (pos == std::string::npos) ++ { ++ continue; ++ } ++ chassisNames.push_back(chassisPath.substr(pos + 1)); ++ } ++ ++ callback(chassisNames); ++ }, ++ "/xyz/openbmc_project/inventory", 0, interfaces); ++} ++} // namespace chassis ++ ++class MetricDefinitionCollection : public Node ++{ ++ public: ++ MetricDefinitionCollection(CrowApp& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions") ++ { ++ 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& req, ++ const std::vector& params) override ++ { ++ res.jsonValue["@odata.type"] = "#MetricDefinitionCollection." ++ "MetricDefinitionCollection"; ++ res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricDefinitions"; ++ res.jsonValue["Name"] = "Metric Definition Collection"; ++ res.jsonValue["Members"] = nlohmann::json::array(); ++ res.jsonValue["Members@odata.count"] = sensors::dbus::types.size(); ++ ++ auto asyncResp = std::make_shared(res); ++ auto collectionReduce = std::make_shared(asyncResp); ++ chassis::getChassisNames( ++ [asyncResp, ++ collectionReduce](const std::vector& chassisNames) { ++ for (auto& chassisName : chassisNames) ++ { ++ for (auto& [sensorNode, _] : sensors::dbus::types) ++ { ++ BMCWEB_LOG_INFO << "Chassis: " << chassisName ++ << " sensor: " << sensorNode; ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, collectionReduce]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ *collectionReduce += uriToDbus; ++ }); ++ } ++ } ++ }); ++ } ++ ++ class CollectionGather ++ { ++ public: ++ CollectionGather(const std::shared_ptr& asyncResp) : ++ asyncResp{asyncResp} ++ { ++ dbusTypes.reserve(sensors::dbus::paths.size()); ++ } ++ ++ ~CollectionGather() ++ { ++ json_util::dbusPathsToMembersArray( ++ asyncResp->res, ++ std::vector(dbusTypes.begin(), dbusTypes.end()), ++ telemetry::metricDefinitionUri); ++ } ++ ++ CollectionGather& operator+=( ++ const boost::container::flat_map& rhs) ++ { ++ for (auto& [_, dbusSensor] : rhs) ++ { ++ auto pos = dbusSensor.rfind("/"); ++ if (pos == std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " ++ << dbusSensor; ++ continue; ++ } ++ ++ this->dbusTypes.insert(dbusSensor.substr(0, pos)); ++ } ++ return *this; ++ } ++ ++ private: ++ const std::shared_ptr asyncResp; ++ boost::container::flat_set dbusTypes; ++ }; ++}; ++ ++class MetricDefinition : public Node ++{ ++ public: ++ MetricDefinition(CrowApp& app) : ++ Node(app, std::string(telemetry::metricDefinitionUri) + "/", ++ 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& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ ++ size_t sensorIndex = 0; ++ for (auto& name : sensors::dbus::names) ++ { ++ if (name == id) ++ { ++ break; ++ } ++ sensorIndex++; ++ } ++ if (sensorIndex >= sensors::dbus::max) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ ++ auto definitionGather = ++ std::make_shared(asyncResp, id); ++ chassis::getChassisNames( ++ [asyncResp, definitionGather, ++ sensorIndex](const std::vector& chassisNames) { ++ for (auto& chassisName : chassisNames) ++ { ++ for (auto& [sensorNode, dbusPaths] : sensors::dbus::types) ++ { ++ auto found = ++ std::find(dbusPaths.begin(), dbusPaths.end(), ++ sensors::dbus::paths[sensorIndex]); ++ if (found == dbusPaths.end()) ++ { ++ continue; ++ } ++ ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, definitionGather]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ *definitionGather += uriToDbus; ++ }); ++ } ++ } ++ }); ++ } ++ ++ class DefinitionGather ++ { ++ public: ++ DefinitionGather(const std::shared_ptr& asyncResp, ++ const std::string& id) : ++ id(id), ++ asyncResp{asyncResp} ++ {} ++ ~DefinitionGather() ++ { ++ if (redfishSensors.empty()) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["MetricProperties"] = ++ nlohmann::json::array(); ++ auto& members = asyncResp->res.jsonValue["MetricProperties"]; ++ for (auto& redfishSensor : redfishSensors) ++ { ++ members.push_back(redfishSensor); ++ } ++ ++ 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["Implementation"] = "PhysicalSensor"; ++ asyncResp->res.jsonValue["IsLinear"] = true; ++ asyncResp->res.jsonValue["TimestampAccuracy"] = "PT0.1S"; ++ auto unit = sensorUnits.find(id); ++ if (unit != sensorUnits.end()) ++ { ++ asyncResp->res.jsonValue["Units"] = unit->second; ++ } ++ } ++ ++ DefinitionGather& operator+=( ++ const boost::container::flat_map& rhs) ++ { ++ for (auto& [redfishSensor, dbusSensor] : rhs) ++ { ++ if (dbusSensor.find(id) != std::string::npos) ++ { ++ this->redfishSensors.push_back(redfishSensor); ++ } ++ } ++ return *this; ++ } ++ ++ const std::string id; ++ ++ private: ++ const std::shared_ptr asyncResp; ++ std::vector redfishSensors; ++ const boost::container::flat_map sensorUnits = ++ {{sensors::dbus::names[sensors::dbus::voltage], "V"}, ++ {sensors::dbus::names[sensors::dbus::power], "W"}, ++ {sensors::dbus::names[sensors::dbus::current], "A"}, ++ {sensors::dbus::names[sensors::dbus::fan_tach], "RPM"}, ++ {sensors::dbus::names[sensors::dbus::temperature], "Cel"}, ++ {sensors::dbus::names[sensors::dbus::utilization], "%"}, ++ {sensors::dbus::names[sensors::dbus::fan_pwm], "Duty cycle"}}; ++ }; ++ ++ static constexpr const char* schemaType = ++ "#MetricDefinition.v1_0_3.MetricDefinition"; ++}; ++ ++} // namespace redfish +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index 877e7f1..be72b18 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -91,6 +91,9 @@ class MetricReport : public Node + using Readings = + std::vector>; + using MetricValues = std::vector>; ++ using ReadingParameters = ++ std::vector, ++ std::string, std::string, std::string>>; + + static MetricValues toMetricValues(const Readings& readings) + { +@@ -109,6 +112,49 @@ class MetricReport : public Node + return metricValues; + } + ++ static void addMetricDefinition(nlohmann::json& metrics, ++ const ReadingParameters& params) ++ { ++ for (auto& metric : metrics) ++ { ++ if (!metric.contains("MetricId")) ++ { ++ continue; ++ } ++ ++ auto& id = metric["MetricId"].get_ref(); ++ auto param = ++ std::find_if(params.begin(), params.end(), [id](const auto& x) { ++ return id == std::get<2>(x); ++ }); ++ if (param == params.end()) ++ { ++ continue; ++ } ++ ++ auto& dbusPaths = ++ std::get>(*param); ++ if (dbusPaths.size() > 1) ++ { ++ continue; ++ } ++ ++ auto dbusPath = dbusPaths.begin(); ++ for (size_t i = 0; i < sensors::dbus::paths.size(); i++) ++ { ++ if (dbusPath->str.find(sensors::dbus::paths[i]) == ++ std::string::npos) ++ { ++ continue; ++ } ++ metric["MetricDefinition"]["@odata.id"] = ++ telemetry::metricDefinitionUri + ++ std::string(sensors::dbus::names[i]); ++ break; ++ } ++ } ++ } ++ + static void getReportProperties(const std::shared_ptr asyncResp, + const std::string& reportPath, + const std::string& id) +@@ -124,7 +170,8 @@ class MetricReport : public Node + [asyncResp]( + const boost::system::error_code ec, + const boost::container::flat_map< +- std::string, std::variant>& ret) { ++ std::string, ++ std::variant>& ret) { + if (ec) + { + messages::internalError(asyncResp->res); +@@ -138,6 +185,22 @@ class MetricReport : public Node + json_util::assignIfPresent( + ret, "Readings", asyncResp->res.jsonValue["MetricValues"], + toMetricValues); ++ ++ auto found = ret.find("ReadingParameters"); ++ if (found != ret.end()) ++ { ++ auto params = ++ std::get_if(&found->second); ++ if (params) ++ { ++ auto& jsonValue = asyncResp->res.jsonValue; ++ if (jsonValue.contains("MetricValues")) ++ { ++ addMetricDefinition(jsonValue["MetricValues"], ++ *params); ++ } ++ } ++ } + }, + "xyz.openbmc_project.MonitoringService", reportPath, + "xyz.openbmc_project.MonitoringService.Report"); +diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp +index f12bbe0..1fa1009 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -53,20 +53,39 @@ static constexpr std::string_view thermal = "Thermal"; + + namespace dbus + { ++ ++enum Index ++{ ++ voltage = 0, ++ power, ++ current, ++ fan_tach, ++ temperature, ++ fan_pwm, ++ utilization, ++ max ++}; ++ ++static constexpr std::array names = { ++ "voltage", "power", "current", "fan_tach", ++ "temperature", "fan_pwm", "utilization"}; ++ ++static constexpr std::array paths = { ++ "/xyz/openbmc_project/sensors/voltage", ++ "/xyz/openbmc_project/sensors/power", ++ "/xyz/openbmc_project/sensors/current", ++ "/xyz/openbmc_project/sensors/fan_tach", ++ "/xyz/openbmc_project/sensors/temperature", ++ "/xyz/openbmc_project/sensors/fan_pwm", ++ "/xyz/openbmc_project/sensors/utilization"}; ++ + static const boost::container::flat_map> +- types = {{node::power, +- {"/xyz/openbmc_project/sensors/voltage", +- "/xyz/openbmc_project/sensors/power"}}, +- {node::sensors, +- {"/xyz/openbmc_project/sensors/power", +- "/xyz/openbmc_project/sensors/current", +- "/xyz/openbmc_project/sensors/utilization"}}, +- {node::thermal, +- {"/xyz/openbmc_project/sensors/fan_tach", +- "/xyz/openbmc_project/sensors/temperature", +- "/xyz/openbmc_project/sensors/fan_pwm"}}}; +-} ++ types = { ++ {node::power, {paths[voltage], paths[power]}}, ++ {node::sensors, {paths[power], paths[current], paths[utilization]}}, ++ {node::thermal, {paths[fan_tach], paths[temperature], paths[fan_pwm]}}}; ++} // namespace dbus + } // namespace sensors + + /** +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index a410700..79e4154 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -52,6 +52,8 @@ class TelemetryService : public Node + + 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"] = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + res.jsonValue["MetricReports"]["@odata.id"] = +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch new file mode 100644 index 000000000..75d49b6d6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch @@ -0,0 +1,78 @@ +From b71f087a173c36a16526156fa34581673e2b860c Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Fri, 24 Jul 2020 17:05:38 +0200 +Subject: [PATCH 6/6] Fix MetricReport timestamp for EventService + +Changed MetricReport timestamp type from std::string to int32_t. + +Signed-off-by: Wludzik, Jozef +Change-Id: I0a52b6963e7bedda89a216256f64764cd8799bf1 +--- + redfish-core/include/event_service_manager.hpp | 23 +++++++++++++---------- + 1 file changed, 13 insertions(+), 10 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index d2f4f2a..dc04ccb 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -38,7 +38,7 @@ namespace redfish + { + + using ReadingsObjType = +- std::vector>; ++ std::vector>; + using EventServiceConfig = std::tuple; + + static constexpr const char* eventFormatType = "Event"; +@@ -532,10 +532,12 @@ class Subscription + metricValuesArray.push_back({}); + nlohmann::json& entry = metricValuesArray.back(); + +- entry = {{"MetricId", std::get<0>(it)}, +- {"MetricProperty", std::get<1>(it)}, +- {"MetricValue", std::to_string(std::get<2>(it))}, +- {"Timestamp", std::get<3>(it)}}; ++ auto& [id, property, value, timestamp] = it; ++ ++ entry = {{"MetricId", id}, ++ {"MetricProperty", property}, ++ {"MetricValue", value}, ++ {"Timestamp", crow::utility::getDateTime(timestamp)}}; + } + + nlohmann::json msg = { +@@ -1266,7 +1268,7 @@ class EventServiceManager + [idStr{std::move(idStr)}]( + const boost::system::error_code ec, + boost::container::flat_map< +- std::string, std::variant>& ++ std::string, std::variant>& + resp) { + if (ec) + { +@@ -1275,8 +1277,8 @@ class EventServiceManager + return; + } + +- const std::string* timestampPtr = +- std::get_if(&resp["Timestamp"]); ++ const int32_t* timestampPtr = ++ std::get_if(&resp["Timestamp"]); + if (!timestampPtr) + { + BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; +@@ -1303,8 +1305,9 @@ class EventServiceManager + std::shared_ptr entry = it.second; + if (entry->eventFormatType == metricReportFormatType) + { +- entry->filterAndSendReports(idStr, *timestampPtr, +- *readingsPtr); ++ entry->filterAndSendReports( ++ idStr, crow::utility::getDateTime(*timestampPtr), ++ *readingsPtr); + } + } + }, +-- +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 new file mode 100644 index 000000000..2929b0aec --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -0,0 +1,21 @@ +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/29 + +- Add support for POST in MetricReportDefinitions + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/24 + +- Add support for DELETE in MetricReportDefinitions/ + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32537/23 + +- Add support for "OnRequest" in MetricReportDefinition + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33358/17 + +- Add support for MetricDefinition scheme + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/20 + +- Temporary patch for EventService because of change in design + 0006-Fix-MetricReport-timestamp-for-EventService.patch -- cgit v1.2.3