From 2a64b8ae9b952b18b4aef38cb7c41ce6dba16c50 Mon Sep 17 00:00:00 2001 From: "Jason M. Bills" Date: Mon, 24 May 2021 12:54:37 -0700 Subject: Update to internal 0.52 Signed-off-by: Jason M. Bills --- ...OST-and-DELETE-in-MetricReportDefinitions.patch | 671 --------------------- 1 file changed, 671 deletions(-) delete mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch') 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 deleted file mode 100644 index aaf3f17c7..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch +++ /dev/null @@ -1,671 +0,0 @@ -From 03c4ece83b58b954323111a1a7b2bf2b61402d7e Mon Sep 17 00:00:00 2001 -From: "Wludzik, Jozef" -Date: Mon, 18 May 2020 11:56:57 +0200 -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 -MetricReportDefinition parameters from user bmcweb converts it to -DBus call "AddReport" to Telemetry that serves as a backend -for Redfish TelemetryService. -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: - - 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 -Signed-off-by: Krzysztof Grobelny -Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a ---- - meson.build | 1 + - .../include/utils/telemetry_utils.hpp | 2 + - redfish-core/include/utils/time_utils.hpp | 138 +++++++- - redfish-core/lib/metric_report_definition.hpp | 328 ++++++++++++++++++ - redfish-core/ut/time_utils_test.cpp | 63 ++++ - 5 files changed, 530 insertions(+), 2 deletions(-) - create mode 100644 redfish-core/ut/time_utils_test.cpp - -diff --git a/meson.build b/meson.build -index 66a066b..22a8c4a 100644 ---- a/meson.build -+++ b/meson.build -@@ -345,6 +345,7 @@ srcfiles_unittest = ['include/ut/dbus_utility_test.cpp', - 'redfish-core/ut/privileges_test.cpp', - 'redfish-core/ut/lock_test.cpp', - 'redfish-core/ut/configfile_test.cpp', -+ 'redfish-core/ut/time_utils_test.cpp', - 'http/ut/utility_test.cpp'] - - # Gather the Configuration data -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index a3a8156..0a3af5f 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -1,5 +1,7 @@ - #pragma once - -+#include "dbus_utility.hpp" -+ - namespace redfish - { - -diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp -index 4a87ba0..9965d4d 100644 ---- a/redfish-core/include/utils/time_utils.hpp -+++ b/redfish-core/include/utils/time_utils.hpp -@@ -1,7 +1,13 @@ - #pragma once - -+#include "logging.hpp" -+ -+#include - #include -+#include -+#include - #include -+#include - - namespace redfish - { -@@ -12,6 +18,8 @@ namespace time_utils - namespace details - { - -+using Days = std::chrono::duration>; -+ - inline void leftZeroPadding(std::string& str, const std::size_t padding) - { - if (str.size() < padding) -@@ -19,8 +27,135 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) - str.insert(0, padding - str.size(), '0'); - } - } -+ -+template -+bool fromDurationItem(std::string_view& fmt, const char postfix, -+ std::chrono::milliseconds& out) -+{ -+ const size_t pos = fmt.find(postfix); -+ if (pos == std::string::npos) -+ { -+ return true; -+ } -+ if ((pos + 1U) > fmt.size()) -+ { -+ return false; -+ } -+ -+ const char* end; -+ std::chrono::milliseconds::rep ticks = 0; -+ if constexpr (std::is_same_v) -+ { -+ end = fmt.data() + std::min(pos, 3U); -+ } -+ else -+ { -+ end = fmt.data() + pos; -+ } -+ -+ 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(ec) << "(" -+ << std::make_error_code(ec).message() << "), ptr{" -+ << static_cast(ptr) << "} != end{" -+ << static_cast(end) << "})"; -+ return false; -+ } -+ -+ if constexpr (std::is_same_v) -+ { -+ ticks *= static_cast( -+ std::pow(10, 3 - std::min(pos, 3U))); -+ } -+ if (ticks < 0) -+ { -+ return false; -+ } -+ -+ out += FromTime(ticks); -+ const auto maxConversionRange = -+ std::chrono::duration_cast(std::chrono::milliseconds::max()) -+ .count(); -+ if (out < FromTime(ticks) || maxConversionRange < ticks) -+ { -+ return false; -+ } -+ -+ fmt.remove_prefix(pos + 1U); -+ return true; -+} - } // namespace details - -+/** -+ * @brief Convert string that represents value in Duration Format to its numeric -+ * equivalent. -+ */ -+std::optional -+ fromDurationString(const std::string& str) -+{ -+ std::chrono::milliseconds out = std::chrono::milliseconds::zero(); -+ std::string_view v = str; -+ -+ if (v.empty()) -+ { -+ return out; -+ } -+ if (v.front() != 'P') -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ -+ v.remove_prefix(1); -+ if (!details::fromDurationItem(v, 'D', out)) -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ -+ if (v.empty()) -+ { -+ return out; -+ } -+ if (v.front() != 'T') -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ -+ v.remove_prefix(1); -+ if (!details::fromDurationItem(v, 'H', out) || -+ !details::fromDurationItem(v, 'M', out)) -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ -+ if (v.find('.') != std::string::npos && v.find('S') != std::string::npos) -+ { -+ if (!details::fromDurationItem(v, '.', out) || -+ !details::fromDurationItem(v, 'S', out)) -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ } -+ else if (!details::fromDurationItem(v, 'S', out)) -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ -+ if (!v.empty()) -+ { -+ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; -+ return std::nullopt; -+ } -+ return out; -+} -+ - /** - * @brief Convert time value into duration format that is based on ISO 8601. - * Example output: "P12DT1M5.5S" -@@ -36,8 +171,7 @@ inline std::string toDurationString(std::chrono::milliseconds ms) - std::string fmt; - fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); - -- using Days = std::chrono::duration>; -- Days days = std::chrono::floor(ms); -+ details::Days days = std::chrono::floor(ms); - ms -= days; - - std::chrono::hours hours = std::chrono::floor(ms); -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index 59025d9..fcbc99c 100644 ---- a/redfish-core/lib/metric_report_definition.hpp -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -1,9 +1,12 @@ - #pragma once - - #include "node.hpp" -+#include "sensors.hpp" - #include "utils/telemetry_utils.hpp" - #include "utils/time_utils.hpp" - -+#include -+ - #include - #include - -@@ -95,6 +98,252 @@ inline void fillReportDefinition( - asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = - time_utils::toDurationString(std::chrono::milliseconds(*interval)); - } -+ -+struct AddReportArgs -+{ -+ std::string name; -+ std::string reportingType; -+ bool emitsReadingsUpdate = false; -+ bool logToMetricReportsCollection = false; -+ uint64_t interval = 0; -+ std::vector>> metrics; -+}; -+ -+inline bool toDbusReportActions(crow::Response& res, -+ std::vector& actions, -+ AddReportArgs& args) -+{ -+ 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; -+} -+ -+inline bool getUserParameters(crow::Response& res, const crow::Request& req, -+ AddReportArgs& args) -+{ -+ std::vector metrics; -+ std::vector reportActions; -+ std::optional schedule; -+ if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, -+ "MetricReportDefinitionType", args.reportingType, -+ "ReportActions", reportActions, "Schedule", -+ schedule)) -+ { -+ return false; -+ } -+ -+ constexpr const char* allowedCharactersInName = -+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; -+ if (args.name.empty() || args.name.find_first_not_of( -+ allowedCharactersInName) != std::string::npos) -+ { -+ 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; -+ } -+ -+ if (!toDbusReportActions(res, reportActions, args)) -+ { -+ return false; -+ } -+ -+ if (args.reportingType == "Periodic") -+ { -+ if (!schedule) -+ { -+ messages::createFailedMissingReqProperties(res, "Schedule"); -+ return false; -+ } -+ -+ std::string durationStr; -+ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", -+ durationStr)) -+ { -+ return false; -+ } -+ -+ std::optional durationNum = -+ time_utils::fromDurationString(durationStr); -+ if (!durationNum) -+ { -+ messages::propertyValueIncorrect(res, "RecurrenceInterval", -+ durationStr); -+ return false; -+ } -+ args.interval = static_cast(durationNum->count()); -+ } -+ -+ args.metrics.reserve(metrics.size()); -+ for (auto& m : metrics) -+ { -+ std::string id; -+ std::vector uris; -+ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", -+ uris)) -+ { -+ return false; -+ } -+ -+ args.metrics.emplace_back(std::move(id), std::move(uris)); -+ } -+ -+ return true; -+} -+ -+inline bool getChassisSensorNode( -+ const std::shared_ptr& asyncResp, -+ const std::vector>>& -+ metrics, -+ boost::container::flat_set>& matched) -+{ -+ for (const auto& [id, uris] : 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)) -+ { -+ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " -+ "from " -+ << uri; -+ messages::propertyValueIncorrect(asyncResp->res, uri, -+ "MetricProperties/" + -+ std::to_string(i)); -+ return false; -+ } -+ -+ if (boost::ends_with(node, "#")) -+ { -+ node.pop_back(); -+ } -+ -+ matched.emplace(std::move(chassis), std::move(node)); -+ } -+ } -+ return true; -+} -+ -+class AddReport -+{ -+ public: -+ AddReport(AddReportArgs argsIn, std::shared_ptr 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) -+ { -+ for (size_t i = 0; i < uris.size(); i++) -+ { -+ const std::string& uri = uris[i]; -+ auto el = uriToDbus.find(uri); -+ if (el == uriToDbus.end()) -+ { -+ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " -+ "corresponding to URI " -+ << uri; -+ messages::propertyValueNotInList(asyncResp->res, uri, -+ "MetricProperties/" + -+ std::to_string(i)); -+ return; -+ } -+ -+ const std::string& dbusPath = el->second; -+ readingParams.emplace_back(dbusPath, "SINGLE", id, uri); -+ } -+ } -+ -+ 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) -+ { -+ 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) -+ { -+ metricProperties.emplace_back(uri); -+ } -+ messages::propertyValueIncorrect( -+ asyncResp->res, metricProperties, "MetricProperties"); -+ 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& el) -+ { -+ uriToDbus.insert(el.begin(), el.end()); -+ } -+ -+ private: -+ std::shared_ptr asyncResp; -+ AddReportArgs args; -+ boost::container::flat_map 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&) override -+ { -+ auto asyncResp = std::make_shared(res); -+ telemetry::AddReportArgs args; -+ if (!telemetry::getUserParameters(res, req, args)) -+ { -+ return; -+ } -+ -+ boost::container::flat_set> -+ chassisSensors; -+ if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, -+ chassisSensors)) -+ { -+ return; -+ } -+ -+ auto addReportReq = -+ std::make_shared(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& -+ uriToDbus) { -+ if (status != boost::beast::http::status::ok) -+ { -+ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " -+ "sensors map with err " -+ << static_cast(status); -+ return; -+ } -+ addReportReq->insert(uriToDbus); -+ }); -+ } -+ } - }; - - class MetricReportDefinition : public Node -@@ -184,5 +473,44 @@ class MetricReportDefinition : public Node - "org.freedesktop.DBus.Properties", "GetAll", - telemetry::reportInterface); - } -+ -+ void doDelete(crow::Response& res, const crow::Request&, -+ 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]; -+ const std::string reportPath = telemetry::getDbusReportPath(id); -+ -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, id](const boost::system::error_code ec) { -+ /* -+ * boost::system::errc and std::errc are missing value for -+ * EBADR error that is defined in Linux. -+ */ -+ if (ec.value() == EBADR) -+ { -+ messages::resourceNotFound(asyncResp->res, -+ "MetricReportDefinition", id); -+ return; -+ } -+ -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ asyncResp->res.result(boost::beast::http::status::no_content); -+ }, -+ telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", -+ "Delete"); -+ } - }; - } // 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 -+ -+using namespace testing; -+ -+class FromDurationTest : -+ public Test, -+ public WithParamInterface< -+ std::pair>> -+{}; -+ -+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> -+{}; -+ -+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 - -- cgit v1.2.3