diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch | 769 |
1 files changed, 372 insertions, 397 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch index f40058ad8..fd7e8a445 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch @@ -1,7 +1,7 @@ -From 433358330c7f7d2fba99f6e488d67b314224317f Mon Sep 17 00:00:00 2001 +From bc1635622e122f307fb3b8eb9bbd66ea576a8f0c Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> Date: Mon, 18 May 2020 11:56:57 +0200 -Subject: [PATCH] Add POST and DELETE in MetricReportDefinitions +Subject: [PATCH 2/4] Add POST and DELETE in MetricReportDefinitions Added POST action in MetricReportDefinitions node to allow user to add new MetricReportDefinition. Using minimal set of @@ -12,64 +12,62 @@ Added DELETE request in MetricReportDefinitions node to allow user to remove report from Telemetry. Added conversion from string that represents duration format into its numeric equivalent. +Added unit tests for conversion from and to Duration format. Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated good cases with different parameters for POST action - - Validated bad cases with different parameters for POST action - - Verified time_utils::fromDurationString() + - Tested using witherspoon image on QEMU + - Verified POST action in different cases: + - all parameters are provided, new report is added to collection + - some parameters are missing or invalid, user gets response with + description of the issue - Verified that reports are removed on DELETE request + - Verified that on invalid DELETE request user receives response + with error + - Verified time_utils::fromDurationString() + - Succesfully passed RedfishServiceValidator.py Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a --- - .../include/utils/telemetry_utils.hpp | 5 +- - redfish-core/include/utils/time_utils.hpp | 145 ++++++- - redfish-core/lib/metric_report_definition.hpp | 382 +++++++++++++++++- - 3 files changed, 516 insertions(+), 16 deletions(-) + meson.build | 1 + + redfish-core/include/utils/time_utils.hpp | 138 ++++++++++- + redfish-core/lib/metric_report_definition.hpp | 328 ++++++++++++++++++++++++++ + redfish-core/ut/time_utils_test.cpp | 63 +++++ + 4 files changed, 528 insertions(+), 2 deletions(-) + create mode 100644 redfish-core/ut/time_utils_test.cpp -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index 8caee2d..acb739d 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -12,6 +12,8 @@ constexpr const char* metricReportDefinitionUri = - "/redfish/v1/TelemetryService/MetricReportDefinitions/"; - constexpr const char* metricReportUri = - "/redfish/v1/TelemetryService/MetricReports/"; -+constexpr const char* reportDir = -+ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/"; +diff --git a/meson.build b/meson.build +index 7a16e91..3d65b01 100644 +--- a/meson.build ++++ b/meson.build +@@ -330,6 +330,7 @@ srcfiles_bmcweb = ['src/webserver_main.cpp','redfish-core/src/error_messages.cpp + srcfiles_unittest = ['include/ut/dbus_utility_test.cpp', + 'redfish-core/ut/privileges_test.cpp', + 'redfish-core/ut/lock_test.cpp', ++ 'redfish-core/ut/time_utils_test.cpp', + 'http/ut/utility_test.cpp'] - inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - const std::string& uri) -@@ -61,8 +63,7 @@ inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, - - inline std::string getDbusReportPath(const std::string& id) - { -- std::string path = -- "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id; -+ std::string path = reportDir + id; - dbus::utility::escapePathForDbus(path); - return path; - } + # Gather the Configuration data diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp -index dd4ea75..d8985ab 100644 +index dd4ea75..e94801b 100644 --- a/redfish-core/include/utils/time_utils.hpp +++ b/redfish-core/include/utils/time_utils.hpp -@@ -1,7 +1,12 @@ +@@ -1,7 +1,13 @@ #pragma once +#include "logging.hpp" + +#include <charconv> #include <chrono> ++#include <cmath> +#include <optional> #include <string> +#include <system_error> namespace redfish { -@@ -12,6 +17,8 @@ namespace time_utils +@@ -12,6 +18,8 @@ namespace time_utils namespace details { @@ -78,32 +76,12 @@ index dd4ea75..d8985ab 100644 inline void leftZeroPadding(std::string& str, const std::size_t padding) { if (str.size() < padding) -@@ -19,8 +26,143 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) +@@ -19,8 +27,135 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) str.insert(0, padding - str.size(), '0'); } } + -+inline bool fromChars(const char* start, const char* end, -+ std::chrono::milliseconds::rep& val) -+{ -+ auto [ptr, ec] = std::from_chars(start, end, val); -+ if (ptr != end) -+ { -+ BMCWEB_LOG_ERROR -+ << "Failed to convert string to decimal because of unexpected sign"; -+ return false; -+ } -+ if (ec != std::errc()) -+ { -+ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " -+ << static_cast<int>(ec) << "(" -+ << std::make_error_code(ec).message() << ")"; -+ return false; -+ } -+ return true; -+} -+ -+template <typename T> ++template <typename FromTime> +bool fromDurationItem(std::string_view& fmt, const char postfix, + std::chrono::milliseconds& out) +{ @@ -117,31 +95,43 @@ index dd4ea75..d8985ab 100644 + return false; + } + -+ std::chrono::milliseconds::rep v = 0; -+ if constexpr (std::is_same_v<T, std::chrono::milliseconds>) ++ const char* end; ++ std::chrono::milliseconds::rep ticks = 0; ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) + { -+ std::string str(fmt.data(), std::min<size_t>(pos, 3U)); -+ while (str.size() < 3U) -+ { -+ str += '0'; -+ } -+ if (!fromChars(str.data(), str.data() + str.size(), v)) -+ { -+ return false; -+ } ++ end = fmt.data() + std::min<size_t>(pos, 3U); + } + else + { -+ if (!fromChars(fmt.data(), fmt.data() + pos, v)) -+ { -+ return false; -+ } ++ end = fmt.data() + pos; + } + -+ out += T(v); -+ if (out < T(v) || -+ std::chrono::duration_cast<T>(std::chrono::milliseconds::max()) -+ .count() < v) ++ auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks); ++ if (ptr != end || ec != std::errc()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " ++ << static_cast<int>(ec) << "(" ++ << std::make_error_code(ec).message() << "), ptr{" ++ << static_cast<const void*>(ptr) << "} != end{" ++ << static_cast<const void*>(end) << "})"; ++ return false; ++ } ++ ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) ++ { ++ ticks *= static_cast<std::chrono::milliseconds::rep>( ++ std::pow(10, 3 - std::min<size_t>(pos, 3U))); ++ } ++ if (ticks < 0) ++ { ++ return false; ++ } ++ ++ out += FromTime(ticks); ++ const auto maxConversionRange = ++ std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max()) ++ .count(); ++ if (out < FromTime(ticks) || maxConversionRange < ticks) + { + return false; + } @@ -222,7 +212,7 @@ index dd4ea75..d8985ab 100644 /** * @brief Convert time value into duration format that is based on ISO 8601. * Example output: "P12DT1M5.5S" -@@ -36,8 +178,7 @@ std::string toDurationString(std::chrono::milliseconds ms) +@@ -36,8 +171,7 @@ std::string toDurationString(std::chrono::milliseconds ms) std::string fmt; fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); @@ -233,10 +223,10 @@ index dd4ea75..d8985ab 100644 std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index 48c56e6..d5a540d 100644 +index 59025d9..fcbc99c 100644 --- a/redfish-core/lib/metric_report_definition.hpp +++ b/redfish-core/lib/metric_report_definition.hpp -@@ -1,15 +1,26 @@ +@@ -1,9 +1,12 @@ #pragma once #include "node.hpp" @@ -249,394 +239,311 @@ index 48c56e6..d5a540d 100644 #include <tuple> #include <variant> - namespace redfish - { - -+namespace telemetry -+{ -+ -+using ReadingParameters = -+ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -+ std::string, std::string, std::string>>; -+} // namespace telemetry -+ - class MetricReportDefinitionCollection : public Node - { - public: -@@ -39,6 +50,318 @@ class MetricReportDefinitionCollection : public Node - telemetry::getReportCollection(asyncResp, - telemetry::metricReportDefinitionUri); - } -+ -+ struct AddReportArgs -+ { -+ std::string name; -+ std::string reportingType; -+ bool emitsReadingsUpdate = false; -+ bool logToMetricReportsCollection = false; -+ uint64_t interval = 0; -+ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; -+ }; +@@ -95,6 +98,252 @@ inline void fillReportDefinition( + asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = + time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } + -+ void doPost(crow::Response& res, const crow::Request& req, -+ const std::vector<std::string>&) override ++struct AddReportArgs ++{ ++ std::string name; ++ std::string reportingType; ++ bool emitsReadingsUpdate = false; ++ bool logToMetricReportsCollection = false; ++ uint64_t interval = 0; ++ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++}; ++ ++inline bool toDbusReportActions(crow::Response& res, ++ std::vector<std::string>& actions, ++ AddReportArgs& args) ++{ ++ size_t index = 0; ++ for (auto& action : actions) + { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ AddReportArgs args; -+ if (!getUserParameters(res, req, args)) ++ if (action == "RedfishEvent") + { -+ return; ++ args.emitsReadingsUpdate = true; + } -+ -+ boost::container::flat_set<std::pair<std::string, std::string>> -+ chassisSensors; -+ if (!getChassisSensorNode(asyncResp, args.metrics, chassisSensors)) ++ else if (action == "LogToMetricReportsCollection") + { -+ return; ++ args.logToMetricReportsCollection = true; + } -+ -+ auto addReportReq = -+ std::make_shared<AddReport>(std::move(args), asyncResp); -+ for (const auto& [chassis, sensorType] : chassisSensors) ++ else + { -+ retrieveUriToDbusMap( -+ chassis, sensorType, -+ [asyncResp, addReportReq]( -+ const boost::beast::http::status status, -+ const boost::container::flat_map<std::string, std::string>& -+ uriToDbus) { -+ if (status != boost::beast::http::status::ok) -+ { -+ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " -+ "sensors map with err " -+ << static_cast<unsigned>(status); -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ addReportReq->insert(uriToDbus); -+ }); ++ messages::propertyValueNotInList( ++ res, action, "ReportActions/" + std::to_string(index)); ++ return false; + } ++ index++; + } ++ return true; ++} + -+ static bool toDbusReportActions(crow::Response& res, -+ std::vector<std::string>& actions, -+ AddReportArgs& args) ++inline bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& args) ++{ ++ std::vector<nlohmann::json> metrics; ++ std::vector<std::string> reportActions; ++ std::optional<nlohmann::json> schedule; ++ if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", args.reportingType, ++ "ReportActions", reportActions, "Schedule", ++ schedule)) + { -+ size_t index = 0; -+ for (auto& action : actions) -+ { -+ if (action == "RedfishEvent") -+ { -+ args.emitsReadingsUpdate = true; -+ } -+ else if (action == "LogToMetricReportsCollection") -+ { -+ args.logToMetricReportsCollection = true; -+ } -+ else -+ { -+ messages::propertyValueNotInList( -+ res, action, "ReportActions/" + std::to_string(index)); -+ return false; -+ } -+ index++; -+ } -+ return true; ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; ++ if (args.name.empty() || args.name.find_first_not_of( ++ allowedCharactersInName) != std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << args.name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueIncorrect(res, "Id", args.name); ++ return false; ++ } ++ ++ if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") ++ { ++ messages::propertyValueNotInList(res, args.reportingType, ++ "MetricReportDefinitionType"); ++ return false; + } + -+ static bool getUserParameters(crow::Response& res, const crow::Request& req, -+ AddReportArgs& args) ++ if (!toDbusReportActions(res, reportActions, args)) + { -+ std::vector<nlohmann::json> metrics; -+ std::vector<std::string> reportActions; -+ std::optional<nlohmann::json> schedule; -+ if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, -+ "MetricReportDefinitionType", -+ args.reportingType, "ReportActions", -+ reportActions, "Schedule", schedule)) ++ return false; ++ } ++ ++ if (args.reportingType == "Periodic") ++ { ++ if (!schedule) + { ++ messages::createFailedMissingReqProperties(res, "Schedule"); + return false; + } + -+ constexpr const char* allowedCharactersInName = -+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; -+ if (args.name.empty() || -+ args.name.find_first_not_of(allowedCharactersInName) != -+ std::string::npos) ++ std::string durationStr; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ durationStr)) + { -+ BMCWEB_LOG_ERROR << "Failed to match " << args.name -+ << " with allowed character " -+ << allowedCharactersInName; -+ messages::propertyValueIncorrect(res, "Id", args.name); + return false; + } + -+ if (args.reportingType != "Periodic" && -+ args.reportingType != "OnRequest") ++ std::optional<std::chrono::milliseconds> durationNum = ++ time_utils::fromDurationString(durationStr); ++ if (!durationNum) + { -+ messages::propertyValueNotInList(res, args.reportingType, -+ "MetricReportDefinitionType"); ++ messages::propertyValueIncorrect(res, "RecurrenceInterval", ++ durationStr); + return false; + } ++ args.interval = static_cast<uint64_t>(durationNum->count()); ++ } + -+ if (!toDbusReportActions(res, reportActions, args)) ++ args.metrics.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string id; ++ std::vector<std::string> uris; ++ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", ++ uris)) + { + return false; + } + -+ if (args.reportingType == "Periodic") ++ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ } ++ ++ return true; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::pair<std::string, std::vector<std::string>>>& ++ metrics, ++ boost::container::flat_set<std::pair<std::string, std::string>>& matched) ++{ ++ for (const auto& [id, uris] : metrics) ++ { ++ for (size_t i = 0; i < uris.size(); i++) + { -+ if (!schedule) -+ { -+ messages::createFailedMissingReqProperties(res, "Schedule"); -+ return false; -+ } ++ const std::string& uri = uris[i]; ++ std::string chassis; ++ std::string node; + -+ std::string durationStr; -+ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", -+ durationStr)) ++ if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || ++ !dbus::utility::getNthStringFromPath(uri, 3, chassis) || ++ !dbus::utility::getNthStringFromPath(uri, 4, node)) + { ++ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " ++ "from " ++ << uri; ++ messages::propertyValueIncorrect(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(i)); + return false; + } + -+ std::optional<std::chrono::milliseconds> durationNum = -+ time_utils::fromDurationString(durationStr); -+ if (!durationNum) ++ if (boost::ends_with(node, "#")) + { -+ messages::propertyValueIncorrect(res, "RecurrenceInterval", -+ durationStr); -+ return false; ++ node.pop_back(); + } -+ args.interval = static_cast<uint64_t>(durationNum->count()); ++ ++ matched.emplace(std::move(chassis), std::move(node)); + } ++ } ++ return true; ++} + -+ args.metrics.reserve(metrics.size()); -+ for (auto& m : metrics) ++class AddReport ++{ ++ public: ++ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : ++ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} ++ {} ++ ~AddReport() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) + { -+ std::string id; -+ std::vector<std::string> uris; -+ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", -+ uris)) -+ { -+ return false; -+ } -+ -+ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ return; + } + -+ return true; -+ } ++ telemetry::ReadingParameters readingParams; ++ readingParams.reserve(args.metrics.size()); + -+ static bool getChassisSensorNode( -+ const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::vector<std::pair<std::string, std::vector<std::string>>>& -+ metrics, -+ boost::container::flat_set<std::pair<std::string, std::string>>& -+ matched) -+ { -+ for (const auto& [id, uris] : metrics) ++ for (const auto& [id, uris] : args.metrics) + { + for (size_t i = 0; i < uris.size(); i++) + { + const std::string& uri = uris[i]; -+ std::string chassis; -+ std::string node; -+ -+ if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || -+ !dbus::utility::getNthStringFromPath(uri, 3, chassis) || -+ !dbus::utility::getNthStringFromPath(uri, 4, node)) ++ auto el = uriToDbus.find(uri); ++ if (el == uriToDbus.end()) + { -+ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " -+ "from " ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " + << uri; -+ messages::propertyValueIncorrect(asyncResp->res, uri, ++ messages::propertyValueNotInList(asyncResp->res, uri, + "MetricProperties/" + + std::to_string(i)); -+ return false; -+ } -+ -+ if (boost::ends_with(node, "#")) -+ { -+ node.pop_back(); ++ return; + } + -+ matched.emplace(std::move(chassis), std::move(node)); ++ const std::string& dbusPath = el->second; ++ readingParams.emplace_back(dbusPath, "SINGLE", id, uri); + } + } -+ return true; -+ } -+ -+ class AddReport -+ { -+ public: -+ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : -+ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} -+ {} -+ ~AddReport() -+ { -+ if (asyncResp->res.result() != boost::beast::http::status::ok) -+ { -+ return; -+ } -+ -+ telemetry::ReadingParameters readingParams; -+ readingParams.reserve(args.metrics.size()); + -+ for (const auto& [id, uris] : args.metrics) -+ { -+ std::vector<sdbusplus::message::object_path> dbusPaths; -+ dbusPaths.reserve(uris.size()); -+ -+ for (size_t i = 0; i < uris.size(); i++) ++ crow::connections::systemBus->async_method_call( ++ [asyncResp = std::move(asyncResp), name = args.name, ++ uriToDbus = std::move(uriToDbus)]( ++ const boost::system::error_code ec, const std::string&) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists( ++ asyncResp->res, "MetricReportDefinition", "Id", name); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) + { -+ const std::string& uri = uris[i]; -+ auto el = uriToDbus.find(uri); -+ if (el == uriToDbus.end()) ++ messages::createLimitReachedForResource(asyncResp->res); ++ return; ++ } ++ if (ec == boost::system::errc::argument_list_too_long) ++ { ++ nlohmann::json metricProperties = nlohmann::json::array(); ++ for (const auto& [uri, _] : uriToDbus) + { -+ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " -+ "corresponding to URI " -+ << uri; -+ messages::propertyValueNotInList(asyncResp->res, uri, -+ "MetricProperties/" + -+ std::to_string(i)); -+ return; ++ metricProperties.emplace_back(uri); + } -+ -+ dbusPaths.emplace_back(el->second); ++ messages::propertyValueIncorrect( ++ asyncResp->res, metricProperties, "MetricProperties"); ++ return; + } -+ -+ nlohmann::json metadata; -+ metadata["MetricProperties"] = uris; -+ if (uris.size() == 1) ++ if (ec) + { -+ metadata["MetricProperty"] = uris[0]; ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; + } -+ readingParams.emplace_back(std::move(dbusPaths), "SINGLE", id, -+ metadata.dump()); -+ } + -+ crow::connections::systemBus->async_method_call( -+ [asyncResp = asyncResp, name = args.name]( -+ const boost::system::error_code ec, const std::string&) { -+ if (ec == boost::system::errc::file_exists) -+ { -+ messages::resourceAlreadyExists( -+ asyncResp->res, "MetricReportDefinition", "Id", -+ name); -+ return; -+ } -+ if (ec == boost::system::errc::too_many_files_open) -+ { -+ messages::createLimitReachedForResource(asyncResp->res); -+ return; -+ } -+ if (ec == boost::system::errc::argument_list_too_long) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, "/Exceeds supported size/", -+ "Metrics"); -+ return; -+ } -+ if (ec == boost::system::errc::not_supported) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, -+ "/Only single property per metric is supported/", -+ "MetricProperties"); -+ return; -+ } -+ if (ec == boost::system::errc::invalid_argument) -+ { -+ messages::propertyValueNotInList( -+ asyncResp->res, "/Less then MinInterval/", -+ "RecurrenceInterval"); -+ return; -+ } -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } ++ messages::created(asyncResp->res); ++ }, ++ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", ++ "TelemetryService/" + args.name, args.reportingType, ++ args.emitsReadingsUpdate, args.logToMetricReportsCollection, ++ args.interval, readingParams); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } + -+ messages::created(asyncResp->res); -+ }, -+ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", -+ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", -+ "TelemetryService/" + args.name, args.reportingType, -+ args.emitsReadingsUpdate, args.logToMetricReportsCollection, -+ args.interval, readingParams); ++ private: ++ std::shared_ptr<AsyncResp> asyncResp; ++ AddReportArgs args; ++ boost::container::flat_map<std::string, std::string> uriToDbus{}; ++}; + } // namespace telemetry + + class MetricReportDefinitionCollection : public Node +@@ -126,6 +375,46 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ telemetry::AddReportArgs args; ++ if (!telemetry::getUserParameters(res, req, args)) ++ { ++ return; + } + -+ void insert( -+ const boost::container::flat_map<std::string, std::string>& el) ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, ++ chassisSensors)) + { -+ uriToDbus.insert(el.begin(), el.end()); ++ return; + } + -+ private: -+ std::shared_ptr<AsyncResp> asyncResp; -+ AddReportArgs args; -+ boost::container::flat_map<std::string, std::string> uriToDbus{}; -+ }; ++ auto addReportReq = ++ std::make_shared<telemetry::AddReport>(std::move(args), asyncResp); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map<std::string, std::string>& ++ uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ return; ++ } ++ addReportReq->insert(uriToDbus); ++ }); ++ } ++ } }; class MetricReportDefinition : public Node -@@ -73,9 +396,10 @@ class MetricReportDefinition : public Node - crow::connections::systemBus->async_method_call( - [asyncResp, - id](const boost::system::error_code ec, -- const std::vector<std::pair< -- std::string, std::variant<bool, ReadingParameters, -- std::string, uint64_t>>>& ret) { -+ const std::vector< -+ std::pair<std::string, -+ std::variant<bool, telemetry::ReadingParameters, -+ std::string, uint64_t>>>& ret) { - if (ec.value() == EBADR) - { - messages::resourceNotFound(asyncResp->res, schemaType, id); -@@ -95,15 +419,11 @@ class MetricReportDefinition : public Node +@@ -184,5 +473,44 @@ class MetricReportDefinition : public Node + "org.freedesktop.DBus.Properties", "GetAll", telemetry::reportInterface); } - -- using ReadingParameters = -- std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -- std::string, std::string, std::string>>; -- - static void fillReportDefinition( - const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id, -- const std::vector< -- std::pair<std::string, std::variant<bool, ReadingParameters, -- std::string, uint64_t>>>& ret) -+ const std::vector<std::pair< -+ std::string, std::variant<bool, telemetry::ReadingParameters, -+ std::string, uint64_t>>>& ret) - { - asyncResp->res.jsonValue["@odata.type"] = schemaType; - asyncResp->res.jsonValue["@odata.id"] = -@@ -117,7 +437,7 @@ class MetricReportDefinition : public Node - - const bool* emitsReadingsUpdate = nullptr; - const bool* logToMetricReportsCollection = nullptr; -- const ReadingParameters* readingParams = nullptr; -+ const telemetry::ReadingParameters* readingParams = nullptr; - const std::string* reportingType = nullptr; - const uint64_t* interval = nullptr; - for (const auto& [key, var] : ret) -@@ -132,7 +452,7 @@ class MetricReportDefinition : public Node - } - else if (key == "ReadingParameters") - { -- readingParams = std::get_if<ReadingParameters>(&var); -+ readingParams = std::get_if<telemetry::ReadingParameters>(&var); - } - else if (key == "ReportingType") - { -@@ -180,6 +500,44 @@ class MetricReportDefinition : public Node - time_utils::toDurationString(std::chrono::milliseconds(*interval)); - } - ++ + void doDelete(crow::Response& res, const crow::Request&, + const std::vector<std::string>& params) override + { @@ -658,7 +565,8 @@ index 48c56e6..d5a540d 100644 + */ + if (ec.value() == EBADR) + { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ messages::resourceNotFound(asyncResp->res, ++ "MetricReportDefinition", id); + return; + } + @@ -674,10 +582,77 @@ index 48c56e6..d5a540d 100644 + telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", + "Delete"); + } -+ - static constexpr const char* schemaType = - "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; }; + } // namespace redfish +diff --git a/redfish-core/ut/time_utils_test.cpp b/redfish-core/ut/time_utils_test.cpp +new file mode 100644 +index 0000000..70999ce +--- /dev/null ++++ b/redfish-core/ut/time_utils_test.cpp +@@ -0,0 +1,63 @@ ++#include "utils/time_utils.hpp" ++ ++#include <gmock/gmock.h> ++ ++using namespace testing; ++ ++class FromDurationTest : ++ public Test, ++ public WithParamInterface< ++ std::pair<std::string, std::optional<std::chrono::milliseconds>>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, FromDurationTest, ++ Values(std::make_pair("PT12S", std::chrono::milliseconds(12000)), ++ std::make_pair("PT0.204S", std::chrono::milliseconds(204)), ++ std::make_pair("PT0.2S", std::chrono::milliseconds(200)), ++ std::make_pair("PT50M", std::chrono::milliseconds(3000000)), ++ std::make_pair("PT23H", std::chrono::milliseconds(82800000)), ++ std::make_pair("P51D", std::chrono::milliseconds(4406400000)), ++ std::make_pair("PT2H40M10.1S", std::chrono::milliseconds(9610100)), ++ std::make_pair("P20DT2H40M10.1S", ++ std::chrono::milliseconds(1737610100)), ++ std::make_pair("", std::chrono::milliseconds(0)), ++ std::make_pair("PTS", std::nullopt), ++ std::make_pair("P1T", std::nullopt), ++ std::make_pair("PT100M1000S100", std::nullopt), ++ std::make_pair("PDTHMS", std::nullopt), ++ std::make_pair("P99999999999999999DT", std::nullopt), ++ std::make_pair("PD222T222H222M222.222S", std::nullopt), ++ std::make_pair("PT99999H9999999999999999999999M99999999999S", ++ std::nullopt), ++ std::make_pair("PT-9H", std::nullopt))); ++ ++TEST_P(FromDurationTest, convertToMilliseconds) ++{ ++ const auto& [str, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::fromDurationString(str), Eq(expected)); ++} ++ ++class ToDurationTest : ++ public Test, ++ public WithParamInterface<std::pair<std::chrono::milliseconds, std::string>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, ToDurationTest, ++ Values(std::make_pair(std::chrono::milliseconds(12000), "PT12.000S"), ++ std::make_pair(std::chrono::milliseconds(204), "PT0.204S"), ++ std::make_pair(std::chrono::milliseconds(200), "PT0.200S"), ++ std::make_pair(std::chrono::milliseconds(3000000), "PT50M"), ++ std::make_pair(std::chrono::milliseconds(82800000), "PT23H"), ++ std::make_pair(std::chrono::milliseconds(4406400000), "P51DT"), ++ std::make_pair(std::chrono::milliseconds(9610100), "PT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(1737610100), ++ "P20DT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(-250), ""))); ++ ++TEST_P(ToDurationTest, convertToDuration) ++{ ++ const auto& [ms, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::toDurationString(ms), Eq(expected)); ++} -- -2.17.1 +2.16.6 |