summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch594
1 files changed, 594 insertions, 0 deletions
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" <jozef.wludzik@intel.com>
+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 <jozef.wludzik@intel.com>
+Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
+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 <typename T>
++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<T, std::chrono::milliseconds>::value)
++ {
++ /* Half point is added to avoid numeric error on rounding */
++ out = static_cast<long>(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<int, std::ratio<24 * 60 * 60>>;
++
++ fmt.remove_prefix(1);
++ auto out = details::fromDurationFormatItem<Days>(fmt, "D");
++ if (fmt[0] != 'T')
++ {
++ return static_cast<uint32_t>(out);
++ }
++
++ fmt.remove_prefix(1);
++ out += details::fromDurationFormatItem<std::chrono::hours>(fmt, "H");
++ out += details::fromDurationFormatItem<std::chrono::minutes>(fmt, "M");
++ out += details::fromDurationFormatItem<std::chrono::milliseconds>(fmt, "S");
++
++ return static_cast<uint32_t>(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 <class Limits, size_t... Seq>
++bool validateParamsLength(crow::Response& res, Limits&& limits,
++ std::index_sequence<Seq...>)
++{
++ return (... && std::get<Seq>(limits).validate(res));
++}
++} // namespace detail
++
++template <class T>
++struct ItemSizeValidator
++{
++ ItemSizeValidator(const T&& item, std::string_view name, size_t limit) :
++ item(std::forward<const T>(item)), name(name), limit(limit)
++ {}
++
++ bool validate(crow::Response& res) const
++ {
++ return ItemSizeValidator<T>::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<size_t>(limit))
++ {
++ messages::stringValueTooLong(res, std::string(name),
++ static_cast<int>(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 <class T>
++ItemSizeValidator(const T&, std::string_view, size_t)
++ -> ItemSizeValidator<const T&>;
++
++ItemSizeValidator(const char*, std::string_view, size_t)
++ ->ItemSizeValidator<std::string_view>;
++
++template <class ContainerT>
++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 <class T>
++bool validateParamLength(crow::Response& res, T&& item, std::string_view name,
++ size_t limit)
++{
++ return ItemSizeValidator(std::forward<T>(item), name, limit).validate(res);
++}
++
++template <class Limits>
++bool validateParamsLength(crow::Response& res, Limits&& limits)
++{
++ return detail::validateParamsLength(
++ res, std::forward<Limits>(limits),
++ std::make_index_sequence<std::tuple_size_v<std::decay_t<Limits>>>());
++}
++
++} // 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 <boost/algorithm/string/join.hpp>
++#include <boost/algorithm/string/split.hpp>
+ #include <boost/container/flat_map.hpp>
+
++#include <regex>
+ #include <system_error>
++#include <tuple>
+ #include <variant>
+
+ 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<std::string, std::string>;
++ using DbusSensor = sdbusplus::message::object_path;
++ using DbusSensors = std::vector<DbusSensor>;
++ using MetricParam =
++ std::tuple<DbusSensors, std::string, std::string, std::string>;
++ using MetricParams = std::vector<MetricParam>;
++ /*
++ * AddReportArgs misses "Domain" parameter because it is constant for
++ * TelemetryService and equals "TelemetryService".
++ */
++ using AddReportArgs =
++ std::tuple<std::string, std::string, std::vector<std::string>, uint32_t,
++ MetricParams>;
++
++ void doPost(crow::Response& res, const crow::Request& req,
++ const std::vector<std::string>& params) override
++ {
++ auto asyncResp = std::make_shared<AsyncResp>(res);
++ AddReportArgs addReportArgs;
++ if (!getUserParameters(res, req, addReportArgs))
++ {
++ return;
++ }
++
++ boost::container::flat_set<ChassisSensorNode> chassisSensorSet;
++ auto unmatched = getChassisSensorNode(
++ std::get<MetricParams>(addReportArgs), chassisSensorSet);
++ if (unmatched)
++ {
++ messages::resourceNotFound(asyncResp->res, "MetricProperties",
++ *unmatched);
++ return;
++ }
++
++ auto addReportReq =
++ std::make_shared<AddReport>(addReportArgs, asyncResp);
++ for (auto& [chassis, sensorType] : chassisSensorSet)
++ {
++ retrieveUriToDbusMap(
++ chassis, sensorType,
++ [asyncResp, addReportReq](
++ const boost::beast::http::status status,
++ const boost::container::flat_map<std::string, std::string>&
++ uriToDbus) { *addReportReq += uriToDbus; });
++ }
++ }
++
++ static std::optional<std::string>
++ replaceReportActions(std::vector<std::string>& actions)
++ {
++ const boost::container::flat_map<std::string, std::string>
++ 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<const char*, 2> supportedDefinitionType =
++ {"Periodic", "OnRequest"};
++
++ static bool getUserParameters(crow::Response& res, const crow::Request& req,
++ AddReportArgs& params)
++ {
++ std::vector<nlohmann::json> metrics;
++ std::optional<nlohmann::json> 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<nlohmann::json>& metrics,
++ crow::Response& res,
++ MetricParams& metricParams)
++ {
++ metricParams.reserve(metrics.size());
++ for (auto& m : metrics)
++ {
++ std::string metricId;
++ std::vector<std::string> 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<std::string> getChassisSensorNode(
++ const MetricParams& metricParams,
++ boost::container::flat_set<ChassisSensorNode>& matched)
++ {
++ for (const auto& metricParam : metricParams)
++ {
++ const auto& sensors = std::get<DbusSensors>(metricParam);
++ for (const auto& sensor : sensors)
++ {
++ /*
++ * Support only following paths:
++ * "/redfish/v1/Chassis/<chassis>/Power#/..."
++ * "/redfish/v1/Chassis/<chassis>/Sensors/..."
++ * "/redfish/v1/Chassis/<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<std::string> replaceUriWithDbus(
++ const boost::container::flat_map<std::string, std::string>& uriToDbus,
++ MetricParams& metricParams)
++ {
++ for (auto& metricParam : metricParams)
++ {
++ auto& dbusSensors = std::get<DbusSensors>(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> 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{asyncResp}, addReportArgs{args}
++ {}
++ ~AddReport()
++ {
++ auto unmatched = replaceUriWithDbus(
++ uriToDbus, std::get<MetricParams>(addReportArgs));
++ if (unmatched)
++ {
++ messages::resourceNotFound(asyncResp->res, "MetricProperties",
++ *unmatched);
++ return;
++ }
++
++ addReport(asyncResp, addReportArgs);
++ }
++
++ AddReport& operator+=(
++ const boost::container::flat_map<std::string, std::string>& rhs)
++ {
++ this->uriToDbus.insert(rhs.begin(), rhs.end());
++ return *this;
++ }
++
++ private:
++ std::shared_ptr<AsyncResp> asyncResp;
++ AddReportArgs addReportArgs;
++ boost::container::flat_map<std::string, std::string> 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
+