summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch
diff options
context:
space:
mode:
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.patch769
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