From f99301c1a626951ee7feee081a1494e795d0e243 Mon Sep 17 00:00:00 2001 From: "Jason M. Bills" Date: Mon, 31 Aug 2020 13:56:28 -0700 Subject: Update to internal 0.74 Signed-off-by: Jason M. Bills --- ...pport-for-POST-in-MetricReportDefinitions.patch | 594 +++++++++++++++++++++ 1 file changed, 594 insertions(+) create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch') diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch new file mode 100644 index 000000000..8a8690bf3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch @@ -0,0 +1,594 @@ +From 941be2c7d819b4a55d5a8b67948e53658d907789 Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" +Date: Mon, 18 May 2020 11:56:57 +0200 +Subject: [PATCH 2/5] Add support for POST in MetricReportDefinitions + +Added POST action in MetricReportDefinitions node to allow user +to add new MetricReportDefinition. Using minimal set of +MetricReportDefinition parameters from user bmcweb converts it to +DBus call "AddReport" to MonitoringService that serves as a backend +for TelemetryService. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated good cases with different parameters for POST action + - Validated bad cases with different parameters for POST action + - Validated fromDurationFormat() with range of arguments starting + from PT0.0S up to P49D (it is an upper limit for uint32_t) + +Signed-off-by: Wludzik, Jozef +Signed-off-by: Krzysztof Grobelny +Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a +--- + redfish-core/include/utils/time_utils.hpp | 49 +++ + .../include/utils/validate_params_length.hpp | 109 +++++++ + redfish-core/lib/metric_report_definition.hpp | 347 +++++++++++++++++++++ + 3 files changed, 505 insertions(+) + create mode 100644 redfish-core/include/utils/validate_params_length.hpp + +diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp +index 0256b3f..c365585 100644 +--- a/redfish-core/include/utils/time_utils.hpp ++++ b/redfish-core/include/utils/time_utils.hpp +@@ -57,6 +57,32 @@ std::string toDurationFormatItem(std::chrono::milliseconds& duration, + return ss.str(); + } + ++template ++static long long fromDurationFormatItem(std::string_view& fmt, ++ const char* postfix) ++{ ++ auto pos = fmt.find(postfix); ++ if (pos == std::string::npos) ++ { ++ return 0; ++ } ++ ++ long out; ++ if constexpr (std::is_same::value) ++ { ++ /* Half point is added to avoid numeric error on rounding */ ++ out = static_cast(std::strtof(fmt.data(), nullptr) * ++ std::chrono::milliseconds::period::den + ++ 0.5f); ++ } ++ else ++ { ++ out = std::strtol(fmt.data(), nullptr, 10); ++ } ++ fmt.remove_prefix(pos + 1); ++ return std::chrono::milliseconds(T(out)).count(); ++} ++ + } // namespace details + + /** +@@ -93,5 +119,28 @@ std::string toDurationFormat(const uint32_t ms) + return fmt; + } + ++static uint32_t fromDurationFormat(std::string_view fmt) ++{ ++ if (fmt.empty() || fmt[0] != 'P') ++ { ++ return 0; ++ } ++ using Days = std::chrono::duration>; ++ ++ fmt.remove_prefix(1); ++ auto out = details::fromDurationFormatItem(fmt, "D"); ++ if (fmt[0] != 'T') ++ { ++ return static_cast(out); ++ } ++ ++ fmt.remove_prefix(1); ++ out += details::fromDurationFormatItem(fmt, "H"); ++ out += details::fromDurationFormatItem(fmt, "M"); ++ out += details::fromDurationFormatItem(fmt, "S"); ++ ++ return static_cast(out); ++} ++ + } // namespace time_utils + } // namespace redfish +diff --git a/redfish-core/include/utils/validate_params_length.hpp b/redfish-core/include/utils/validate_params_length.hpp +new file mode 100644 +index 0000000..c4e0569 +--- /dev/null ++++ b/redfish-core/include/utils/validate_params_length.hpp +@@ -0,0 +1,109 @@ ++#pragma once ++ ++namespace redfish ++{ ++namespace detail ++{ ++template ++bool validateParamsLength(crow::Response& res, Limits&& limits, ++ std::index_sequence) ++{ ++ return (... && std::get(limits).validate(res)); ++} ++} // namespace detail ++ ++template ++struct ItemSizeValidator ++{ ++ ItemSizeValidator(const T&& item, std::string_view name, size_t limit) : ++ item(std::forward(item)), name(name), limit(limit) ++ {} ++ ++ bool validate(crow::Response& res) const ++ { ++ return ItemSizeValidator::validateItem(res, item, name, limit); ++ } ++ ++ private: ++ static bool validateItem(crow::Response& res, size_t item, ++ std::string_view name, size_t limit) ++ { ++ if (item > static_cast(limit)) ++ { ++ messages::stringValueTooLong(res, std::string(name), ++ static_cast(limit)); ++ return false; ++ } ++ return true; ++ } ++ ++ static bool validateItem(crow::Response& res, std::string_view item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.size(), name, limit); ++ } ++ ++ static bool validateItem(crow::Response& res, const std::string& item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.size(), name, limit); ++ } ++ ++ static bool validateItem(crow::Response& res, ++ const sdbusplus::message::object_path& item, ++ std::string_view name, size_t limit) ++ { ++ return validateItem(res, item.str.size(), name, limit); ++ } ++ ++ T item; ++ std::string_view name; ++ size_t limit; ++}; ++ ++template ++ItemSizeValidator(const T&, std::string_view, size_t) ++ -> ItemSizeValidator; ++ ++ItemSizeValidator(const char*, std::string_view, size_t) ++ ->ItemSizeValidator; ++ ++template ++struct ArrayItemsValidator ++{ ++ ArrayItemsValidator(const ContainerT& item, std::string_view name, ++ size_t limit) : ++ item(item), ++ name(name), limit(limit) ++ {} ++ ++ bool validate(crow::Response& res) const ++ { ++ return std::all_of( ++ item.begin(), item.end(), [&res, this](const auto& item) { ++ return ItemSizeValidator(item, name, limit).validate(res); ++ }); ++ } ++ ++ private: ++ const ContainerT& item; ++ std::string_view name; ++ size_t limit; ++}; ++ ++template ++bool validateParamLength(crow::Response& res, T&& item, std::string_view name, ++ size_t limit) ++{ ++ return ItemSizeValidator(std::forward(item), name, limit).validate(res); ++} ++ ++template ++bool validateParamsLength(crow::Response& res, Limits&& limits) ++{ ++ return detail::validateParamsLength( ++ res, std::forward(limits), ++ std::make_index_sequence>>()); ++} ++ ++} // namespace redfish +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index d82ae59..ecbab0c 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -17,16 +17,29 @@ + #pragma once + + #include "node.hpp" ++#include "sensors.hpp" + #include "utils/telemetry_utils.hpp" + #include "utils/time_utils.hpp" ++#include "utils/validate_params_length.hpp" + ++#include ++#include + #include + ++#include + #include ++#include + #include + + namespace redfish + { ++static constexpr size_t maxShortParamLength = 255; ++static constexpr size_t maxLongParamLength = 1024; ++static constexpr size_t maxDbusNameLength = 255; ++static constexpr size_t maxArraySize = 100; ++static constexpr size_t maxReportIdLen = ++ maxDbusNameLength - std::string_view(telemetry::telemetryPath).size() - ++ std::string_view("/").size(); + + class MetricReportDefinitionCollection : public Node + { +@@ -57,6 +70,339 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ using ChassisSensorNode = std::pair; ++ using DbusSensor = sdbusplus::message::object_path; ++ using DbusSensors = std::vector; ++ using MetricParam = ++ std::tuple; ++ using MetricParams = std::vector; ++ /* ++ * AddReportArgs misses "Domain" parameter because it is constant for ++ * TelemetryService and equals "TelemetryService". ++ */ ++ using AddReportArgs = ++ std::tuple, uint32_t, ++ MetricParams>; ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ AddReportArgs addReportArgs; ++ if (!getUserParameters(res, req, addReportArgs)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set chassisSensorSet; ++ auto unmatched = getChassisSensorNode( ++ std::get(addReportArgs), chassisSensorSet); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ auto addReportReq = ++ std::make_shared(addReportArgs, asyncResp); ++ for (auto& [chassis, sensorType] : chassisSensorSet) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map& ++ uriToDbus) { *addReportReq += uriToDbus; }); ++ } ++ } ++ ++ static std::optional ++ replaceReportActions(std::vector& actions) ++ { ++ const boost::container::flat_map ++ reportActions = { ++ {"RedfishEvent", "Event"}, ++ {"LogToMetricReportsCollection", "Log"}, ++ }; ++ ++ for (auto& action : actions) ++ { ++ auto found = reportActions.find(action); ++ if (found == reportActions.end()) ++ { ++ return action; ++ } ++ action = found->second; ++ } ++ return std::nullopt; ++ } ++ ++ static constexpr const std::array supportedDefinitionType = ++ {"Periodic", "OnRequest"}; ++ ++ static bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& params) ++ { ++ std::vector metrics; ++ std::optional schedule; ++ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = ++ params; ++ if (!json_util::readJson(req, res, "Id", name, "Metrics", metrics, ++ "MetricReportDefinitionType", reportingType, ++ "ReportActions", reportActions, "Schedule", ++ schedule)) ++ { ++ return false; ++ } ++ ++ auto limits = std::make_tuple( ++ ItemSizeValidator(name, "Id", maxReportIdLen), ++ ItemSizeValidator(reportingType, "MetricReportDefinitionType", ++ maxShortParamLength), ++ ItemSizeValidator(reportActions.size(), "ReportActions.size()", ++ maxArraySize), ++ ArrayItemsValidator(reportActions, "ReportActions", ++ maxShortParamLength), ++ ItemSizeValidator(metrics.size(), "Metrics.size()", maxArraySize)); ++ ++ if (!validateParamsLength(res, std::move(limits))) ++ { ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ++ "_"; ++ if (name.empty() || name.find_first_not_of(allowedCharactersInName) != ++ std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueFormatError(res, name, "Id"); ++ return false; ++ } ++ ++ if (!std::any_of( ++ supportedDefinitionType.begin(), supportedDefinitionType.end(), ++ [reportingType](auto& x) { return reportingType == x; })) ++ { ++ messages::propertyValueNotInList(res, reportingType, ++ "MetricReportDefinitionType"); ++ return false; ++ } ++ ++ auto unmatched = replaceReportActions(reportActions); ++ if (unmatched) ++ { ++ messages::propertyValueNotInList(res, *unmatched, "ReportActions"); ++ return false; ++ } ++ ++ if (reportingType == "Periodic") ++ { ++ if (!schedule) ++ { ++ messages::createFailedMissingReqProperties(res, "Schedule"); ++ return false; ++ } ++ ++ std::string interval; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ interval)) ++ { ++ return false; ++ } ++ ++ if (!validateParamLength(res, interval, "RecurrenceInterval", ++ maxShortParamLength)) ++ { ++ return false; ++ } ++ ++ constexpr const char* durationPattern = ++ "-?P(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+(.\\d+)?S)?)?"; ++ if (!std::regex_match(interval, std::regex(durationPattern))) ++ { ++ messages::propertyValueFormatError(res, interval, ++ "RecurrenceInterval"); ++ return false; ++ } ++ ++ scanPeriod = time_utils::fromDurationFormat(interval); ++ } ++ ++ return fillMetricParams(metrics, res, metricParams); ++ } ++ ++ static bool fillMetricParams(std::vector& metrics, ++ crow::Response& res, ++ MetricParams& metricParams) ++ { ++ metricParams.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string metricId; ++ std::vector metricProperties; ++ if (!json_util::readJson(m, res, "MetricId", metricId, ++ "MetricProperties", metricProperties)) ++ { ++ return false; ++ } ++ ++ auto limits = std::make_tuple( ++ ItemSizeValidator(metricId, "MetricId", maxLongParamLength), ++ ItemSizeValidator(metricProperties.size(), ++ "MetricProperties.size()", maxArraySize), ++ ArrayItemsValidator(metricProperties, "MetricProperties", ++ maxLongParamLength)); ++ ++ if (!validateParamsLength(res, std::move(limits))) ++ { ++ return false; ++ } ++ ++ DbusSensors dbusSensors; ++ dbusSensors.reserve(metricProperties.size()); ++ std::for_each( ++ metricProperties.begin(), metricProperties.end(), ++ [&dbusSensors](auto& x) { dbusSensors.emplace_back(x); }); ++ ++ metricParams.emplace_back( ++ dbusSensors, "SINGLE", metricId, ++ boost::algorithm::join(metricProperties, ", ")); ++ } ++ return true; ++ } ++ ++ static std::optional getChassisSensorNode( ++ const MetricParams& metricParams, ++ boost::container::flat_set& matched) ++ { ++ for (const auto& metricParam : metricParams) ++ { ++ const auto& sensors = std::get(metricParam); ++ for (const auto& sensor : sensors) ++ { ++ /* ++ * Support only following paths: ++ * "/redfish/v1/Chassis//Power#/..." ++ * "/redfish/v1/Chassis//Sensors/..." ++ * "/redfish/v1/Chassis//Thermal#/..." ++ */ ++ constexpr const char* uriPattern = ++ "\\/redfish\\/v1\\/Chassis\\/(\\w+)\\/" ++ "(Power|Sensors|Thermal)[#]?\\/.*"; ++ std::smatch m; ++ if (!std::regex_match(sensor.str, m, std::regex(uriPattern)) || ++ m.size() != 3) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << sensor.str ++ << " with pattern " << uriPattern; ++ return sensor; ++ } ++ ++ BMCWEB_LOG_DEBUG << "Chassis=" << m[1] << ", Type=" << m[2]; ++ matched.emplace(m[1], m[2]); ++ } ++ } ++ return std::nullopt; ++ } ++ ++ static std::optional replaceUriWithDbus( ++ const boost::container::flat_map& uriToDbus, ++ MetricParams& metricParams) ++ { ++ for (auto& metricParam : metricParams) ++ { ++ auto& dbusSensors = std::get(metricParam); ++ for (auto& uri : dbusSensors) ++ { ++ auto dbus = uriToDbus.find(uri); ++ if (dbus == uriToDbus.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " ++ << uri.str; ++ return uri; ++ } ++ uri = dbus->second; ++ } ++ } ++ return std::nullopt; ++ } ++ ++ static void addReport(std::shared_ptr asyncResp, ++ AddReportArgs args) ++ { ++ constexpr const char* domain = "TelemetryService"; ++ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = ++ args; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, name](const boost::system::error_code ec, ++ const std::string ret) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists( ++ asyncResp->res, "MetricReportDefinition", "Id", name); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(asyncResp->res); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ messages::created(asyncResp->res); ++ }, ++ "xyz.openbmc_project.MonitoringService", ++ "/xyz/openbmc_project/MonitoringService/Reports", ++ "xyz.openbmc_project.MonitoringService.ReportsManagement", ++ "AddReport", name, domain, reportingType, reportActions, scanPeriod, ++ metricParams); ++ } ++ ++ class AddReport ++ { ++ public: ++ AddReport(AddReportArgs& args, std::shared_ptr& asyncResp) : ++ asyncResp{asyncResp}, addReportArgs{args} ++ {} ++ ~AddReport() ++ { ++ auto unmatched = replaceUriWithDbus( ++ uriToDbus, std::get(addReportArgs)); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ addReport(asyncResp, addReportArgs); ++ } ++ ++ AddReport& operator+=( ++ const boost::container::flat_map& rhs) ++ { ++ this->uriToDbus.insert(rhs.begin(), rhs.end()); ++ return *this; ++ } ++ ++ private: ++ std::shared_ptr asyncResp; ++ AddReportArgs addReportArgs; ++ boost::container::flat_map uriToDbus{}; ++ }; + }; + + class MetricReportDefinition : public Node +@@ -148,6 +494,7 @@ class MetricReportDefinition : public Node + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = + telemetry::metricReportUri + id; + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; ++ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; + + dbus::utility::getAllProperties( + [asyncResp](const boost::system::error_code ec, +-- +2.16.6 + -- cgit v1.2.3