diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry')
8 files changed, 2832 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch new file mode 100644 index 000000000..6b57dc912 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch @@ -0,0 +1,26 @@ +From 71b55e9773c387d6510650e7cf64f050a853ac77 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Tue, 30 Nov 2021 16:29:12 +0100 +Subject: [PATCH] Revert "Remove LogService from TelemetryService" + +This reverts commit 2b3da45876aac57a36d3093379a992d699e7e396. +--- + redfish-core/lib/telemetry_service.hpp | 2 + + 1 files changed, 2 insertions(+), 0 deletions(-) + +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index b79a5cd..e3e4a25 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -25,6 +25,8 @@ inline void handleTelemetryServiceGet( + "/redfish/v1/TelemetryService/MetricReports"; + asyncResp->res.jsonValue["Triggers"]["@odata.id"] = + "/redfish/v1/TelemetryService/Triggers"; ++ asyncResp->res.jsonValue["LogService"]["@odata.id"] = ++ "/redfish/v1/Managers/bmc/LogServices/Journal"; + + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec, +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch new file mode 100644 index 000000000..242bec0dc --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch @@ -0,0 +1,193 @@ +From a6d32959d5420f3fdca0d391feb54ac89f65dca3 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke <szymon.dompke@intel.com> +Date: Tue, 1 Mar 2022 16:34:08 +0100 +Subject: [PATCH] ref: use url_view for telemetry uris + +This change refactor telemetry code to use bmcweb utility function for +uri construction, which is safe and preferred way, instead of string +operations. + +Testing done: +- Some basic GET operations done on Telemetry, no regression. + +Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> +Change-Id: I6de5d79a078944d398357f27dc0c201c130c4302 +--- + redfish-core/include/event_service_manager.hpp | 6 ++++-- + redfish-core/include/utils/telemetry_utils.hpp | 5 +---- + redfish-core/lib/metric_report.hpp | 14 +++++++++++--- + redfish-core/lib/metric_report_definition.hpp | 13 ++++++++++--- + redfish-core/lib/trigger.hpp | 15 +++++++++------ + 5 files changed, 35 insertions(+), 18 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index e879f9e..0ed404f 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -505,14 +505,16 @@ class Subscription : public persistent_data::UserSubscription + void filterAndSendReports(const std::string& reportId, + const telemetry::TimestampReadings& var) + { +- std::string mrdUri = telemetry::metricReportDefinitionUri + ("/" + id); ++ boost::urls::url mrdUri = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", reportId); + + // Empty list means no filter. Send everything. + if (!metricReportDefinitions.empty()) + { + if (std::find(metricReportDefinitions.begin(), + metricReportDefinitions.end(), +- mrdUri) == metricReportDefinitions.end()) ++ mrdUri.string()) == metricReportDefinitions.end()) + { + return; + } +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 8aeff0d..6930d0a 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -1,6 +1,7 @@ + #pragma once + + #include "dbus_utility.hpp" ++#include "utility.hpp" + + namespace redfish + { +@@ -9,10 +10,6 @@ namespace telemetry + { + constexpr const char* service = "xyz.openbmc_project.Telemetry"; + constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; +-constexpr const char* metricReportDefinitionUri = +- "/redfish/v1/TelemetryService/MetricReportDefinitions"; +-constexpr const char* metricReportUri = +- "/redfish/v1/TelemetryService/MetricReports"; + + inline std::string getDbusReportPath(const std::string& id) + { +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index 89bd8db..2fb8d82 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -14,6 +14,9 @@ namespace redfish + namespace telemetry + { + ++constexpr const char* metricReportUri = ++ "/redfish/v1/TelemetryService/MetricReports"; ++ + using Readings = + std::vector<std::tuple<std::string, std::string, double, uint64_t>>; + using TimestampReadings = std::tuple<uint64_t, Readings>; +@@ -39,11 +42,16 @@ inline bool fillReport(nlohmann::json& json, const std::string& id, + const TimestampReadings& timestampReadings) + { + json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; +- json["@odata.id"] = telemetry::metricReportUri + std::string("/") + id; ++ json["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); + json["Id"] = id; + json["Name"] = id; + json["MetricReportDefinition"]["@odata.id"] = +- telemetry::metricReportDefinitionUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); + + const auto& [timestamp, readings] = timestampReadings; + json["Timestamp"] = crow::utility::getDateTimeUintMs(timestamp); +@@ -62,7 +70,7 @@ inline void requestRoutesMetricReportCollection(App& app) + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportCollection.MetricReportCollection"; + asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/MetricReports"; ++ telemetry::metricReportUri; + asyncResp->res.jsonValue["Name"] = "Metric Report Collection"; + const std::vector<const char*> interfaces{ + telemetry::reportInterface}; +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index ab7c88b..1a520f3 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -18,6 +18,9 @@ namespace redfish + namespace telemetry + { + ++constexpr const char* metricReportDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ + using ReadingParameters = + std::vector<std::tuple<sdbusplus::message::object_path, std::string, + std::string, std::string>>; +@@ -30,11 +33,15 @@ inline void fillReportDefinition( + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + asyncResp->res.jsonValue["@odata.id"] = +- telemetry::metricReportDefinitionUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); + asyncResp->res.jsonValue["Id"] = id; + asyncResp->res.jsonValue["Name"] = id; + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = +- telemetry::metricReportUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; + asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; + +@@ -365,7 +372,7 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) + "#MetricReportDefinitionCollection." + "MetricReportDefinitionCollection"; + asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ telemetry::metricReportDefinitionUri; + asyncResp->res.jsonValue["Name"] = + "Metric Definition Collection"; + const std::vector<const char*> interfaces{ +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index cdd5781..da6a5db 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -143,9 +143,11 @@ inline nlohmann::json + nlohmann::json reports = nlohmann::json::array(); + for (const std::string& name : reportNames) + { +- reports.push_back({ +- {"@odata.id", metricReportDefinitionUri + std::string("/") + name}, +- }); ++ reports.push_back( ++ {{"@odata.id", ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", name) ++ .string()}}); + } + + return reports; +@@ -214,7 +216,9 @@ inline bool fillTrigger( + } + + json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; +- json["@odata.id"] = triggerUri + std::string("/") + id; ++ json["@odata.id"] = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", id) ++ .string(); + json["Id"] = id; + json["Name"] = *name; + +@@ -282,8 +286,7 @@ inline void requestRoutesTriggerCollection(App& app) + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { + asyncResp->res.jsonValue["@odata.type"] = + "#TriggersCollection.TriggersCollection"; +- asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/Triggers"; ++ asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri; + asyncResp->res.jsonValue["Name"] = "Triggers Collection"; + const std::vector<const char*> interfaces{ + telemetry::triggerInterface}; +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch new file mode 100644 index 000000000..f741142c4 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch @@ -0,0 +1,127 @@ +From cc10d221a17e1136bf3ec7f62583a4ca44212adc Mon Sep 17 00:00:00 2001 +From: Szymon Dompke <szymon.dompke@intel.com> +Date: Tue, 22 Feb 2022 13:58:00 +0100 +Subject: [PATCH] Fix Trigger GET functionality + +This change is fixing 2 issues related to GET on Trigger schema: +- Links to MetricReportDefintions were not parsed properly. Dbus is + returning collection of strings prefixed with "TelemetryService/". + This prefix was supposed to be removed. +- In case of error occurring during internal data parsing, GET could + return both "Internal Error" message and incomplete Trigger + representation. By swapping few lines of code, this issue is fixed: + now either error is returned or full Trigger representation, but never + both of them. + +Testing done: +- Links are now returning proper uris of existing MRD. +- Internal Error is returned for malformed data returned by service. +- Other Trigger GET functionality remained unchanged. + +Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> +Change-Id: I81ebbf3889a152199bef7230de56a73bb112731b +--- + redfish-core/lib/trigger.hpp | 65 ++++++++++++++++++++++-------------- + 1 file changed, 40 insertions(+), 25 deletions(-) + +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index da6a5db..5e0897e 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -137,20 +137,29 @@ inline std::optional<nlohmann::json> + return std::make_optional(thresholds); + } + +-inline nlohmann::json ++inline std::optional<nlohmann::json> + getMetricReportDefinitions(const std::vector<std::string>& reportNames) + { + nlohmann::json reports = nlohmann::json::array(); ++ + for (const std::string& name : reportNames) + { +- reports.push_back( +- {{"@odata.id", +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReportDefinitions", name) +- .string()}}); ++ sdbusplus::message::object_path path(name); ++ if (path.parent_path() != "TelemetryService") ++ { ++ BMCWEB_LOG_ERROR << "Property ReportNames contains invalid value: " ++ << name; ++ return std::nullopt; ++ } ++ reports.push_back({ ++ {"@odata.id", crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", path.filename()) ++ .string()}, ++ }); + } + +- return reports; ++ return std::make_optional(reports); + } + + inline std::vector<std::string> +@@ -215,12 +224,23 @@ inline bool fillTrigger( + return false; + } + +- json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; +- json["@odata.id"] = crow::utility::urlFromPieces( +- "redfish", "v1", "TelemetryService", "Triggers", id) +- .string(); +- json["Id"] = id; +- json["Name"] = *name; ++ std::optional<std::vector<std::string>> triggerActions = ++ getTriggerActions(*actions); ++ if (!triggerActions) ++ { ++ BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: " ++ << id; ++ return false; ++ } ++ ++ std::optional<nlohmann::json> linkedReports = ++ getMetricReportDefinitions(*reports); ++ if (!linkedReports) ++ { ++ BMCWEB_LOG_ERROR << "Property ReportNames is invalid in Trigger: " ++ << id; ++ return false; ++ } + + if (*discrete) + { +@@ -257,20 +277,15 @@ inline bool fillTrigger( + json["MetricType"] = "Numeric"; + } + +- std::optional<std::vector<std::string>> triggerActions = +- getTriggerActions(*actions); +- +- if (!triggerActions) +- { +- BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: " +- << id; +- return false; +- } +- ++ json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; ++ json["@odata.id"] = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", id) ++ .string(); ++ json["Id"] = id; ++ json["Name"] = *name; + json["TriggerActions"] = *triggerActions; + json["MetricProperties"] = getMetricProperties(*sensors); +- json["Links"]["MetricReportDefinitions"] = +- getMetricReportDefinitions(*reports); ++ json["Links"]["MetricReportDefinitions"] = *linkedReports; + + return true; + } +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch new file mode 100644 index 000000000..735033c70 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch @@ -0,0 +1,848 @@ +From c3da912209b3c6d8d3a3724652a09b77e85e8897 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke <szymon.dompke@intel.com> +Date: Fri, 4 Mar 2022 13:11:38 +0100 +Subject: [PATCH] Add support for POST on TriggersCollection +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Added POST method on /redfish/v1/TelemetryService/Triggers uri, which +creates new trigger in telemetry service, by using dbus call AddTrigger. + +By DMTF, most of the properties are not required, and as such are +treated as optional. Some values can be deduced from others (like +'MetricType', depending on 'DiscreteTriggers' or 'NumericThresholds'). +All properties provided in POST body by user will be verified against +each other, and errors will be raised. Few examples of such situations: +- 'MetricType' is set to 'Discrete' but 'NumericThresholds' was passed. +- 'MetricType' is set to 'Numeric' but "DiscreteTriggers' or + 'DiscreteTriggerCondition' were passed +- 'DiscreteTriggerCondition' is set to 'Specified' but + 'DiscreteTriggers' is an empty array or was not passed. +- 'DiscreteTriggerCondition' is set to 'Changed' but 'DiscreteTriggers' + is passed and is not an empty array. + +Example 1 – Trigger with discrete values: +{ + "Id": "TestTrigger", + "MetricType": "Discrete", + "TriggerActions": [ + "RedfishEvent" + ], + "DiscreteTriggerCondition": "Specified", + "DiscreteTriggers": [ + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Warning" + }, + { + "Name": "My discrete trigger", + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "OK" + }, + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Critical" + } + ], + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading" + ], + "Links": { + "MetricReportDefinitions": [] + } +} + +Example 2 – trigger with numeric threshold: +{ + "Id": "TestTrigger2", + "Name": "My Numeric Trigger", + "MetricType": "Numeric", + "TriggerActions": [ + "RedfishEvent", + "RedfishMetricReport" + ], + "NumericThresholds": { + "UpperCritical": { + "Reading": 50, + "Activation": "Increasing", + "DwellTime": "PT0.001S" + }, + "UpperWarning": { + "Reading": 48.1, + "Activation": "Increasing", + "DwellTime": "PT0.004S" + } + }, + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/17/Reading" + ], + "Links": { + "MetricReportDefinitions": [ + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetrics", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetricStats", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PlatformPowerUsage" + ] + } +} + +Tested: +- Triggers were successfully created with above example message bodies. + This can be checked by calling: + 'busctl tree xyz.openbmc_project.Telemetry'. +- Expected errors were returned for messages with incorrect or mutually + exclusive properties and incorrect values. +- Redfish service validator is passing. + +Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> +Change-Id: Ief8c76de8aa660ae0d2dbe4610c26a28186a290a +--- + redfish-core/include/utils/finalizer.hpp | 38 ++ + .../include/utils/telemetry_utils.hpp | 77 +++ + redfish-core/lib/trigger.hpp | 544 +++++++++++++++++- + 3 files changed, 653 insertions(+), 6 deletions(-) + create mode 100644 redfish-core/include/utils/finalizer.hpp + +diff --git a/redfish-core/include/utils/finalizer.hpp b/redfish-core/include/utils/finalizer.hpp +new file mode 100644 +index 0000000..f14a34a +--- /dev/null ++++ b/redfish-core/include/utils/finalizer.hpp +@@ -0,0 +1,38 @@ ++#pragma once ++ ++#include <functional> ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++class Finalizer ++{ ++ public: ++ Finalizer() = delete; ++ Finalizer(std::function<void()> finalizer) : finalizer(std::move(finalizer)) ++ {} ++ ++ Finalizer(const Finalizer&) = delete; ++ Finalizer(Finalizer&&) = delete; ++ ++ Finalizer& operator=(const Finalizer&) = delete; ++ Finalizer& operator=(Finalizer&&) = delete; ++ ++ ~Finalizer() ++ { ++ if (finalizer) ++ { ++ finalizer(); ++ } ++ } ++ ++ private: ++ std::function<void()> finalizer; ++}; ++ ++} // namespace utils ++ ++} // namespace redfish +\ No newline at end of file +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 6930d0a..908ccfc 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -25,5 +25,82 @@ inline std::string getDbusTriggerPath(const std::string& id) + return {triggersPath / id}; + } + ++inline std::optional<std::string> ++ getReportNameFromReportDefinitionUri(const std::string& uri) ++{ ++ constexpr std::string_view uriPattern = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; ++ if (uri.starts_with(uriPattern)) ++ { ++ return uri.substr(uriPattern.length()); ++ } ++ return std::nullopt; ++} ++ ++inline std::optional<std::string> ++ getTriggerIdFromDbusPath(const std::string& dbusPath) ++{ ++ constexpr std::string_view triggerTree = ++ "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService/"; ++ if (dbusPath.starts_with(triggerTree)) ++ { ++ return dbusPath.substr(triggerTree.length()); ++ } ++ return std::nullopt; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::vector<std::string>& uris, ++ boost::container::flat_set<std::pair<std::string, std::string>>& matched) ++{ ++ size_t uriIdx = 0; ++ for (const std::string& uri : uris) ++ { ++ std::string chassis; ++ std::string node; ++ ++ if (!uri.starts_with("/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(uriIdx)); ++ return false; ++ } ++ ++ if (node.ends_with('#')) ++ { ++ node.pop_back(); ++ } ++ ++ matched.emplace(std::move(chassis), std::move(node)); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline std::optional<std::string> ++ redfishActionToDbusAction(const std::string& redfishAction) ++{ ++ if (redfishAction == "RedfishMetricReport") ++ { ++ return "UpdateReport"; ++ } ++ if (redfishAction == "RedfishEvent") ++ { ++ return "RedfishEvent"; ++ } ++ if (redfishAction == "LogToLogService") ++ { ++ return "LogToLogService"; ++ } ++ return std::nullopt; ++} ++ + } // namespace telemetry + } // namespace redfish +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index 5e0897e..67aa5e6 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -1,7 +1,9 @@ + #pragma once + +-#include "utils/collection.hpp" ++#include "sensors.hpp" ++#include "utils/finalizer.hpp" + #include "utils/telemetry_utils.hpp" ++#include "utils/time_utils.hpp" + + #include <app.hpp> + #include <registries/privilege_registry.hpp> +@@ -14,9 +16,13 @@ namespace redfish + { + namespace telemetry + { ++ + constexpr const char* triggerInterface = + "xyz.openbmc_project.Telemetry.Trigger"; +-constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers"; ++ ++static constexpr std::array<std::string_view, 4> ++ supportedNumericThresholdNames = {"UpperCritical", "LowerCritical", ++ "UpperWarning", "LowerWarning"}; + + using NumericThresholdParams = + std::tuple<std::string, uint64_t, std::string, double>; +@@ -24,6 +30,10 @@ using NumericThresholdParams = + using DiscreteThresholdParams = + std::tuple<std::string, std::string, uint64_t, std::string>; + ++using TriggerThresholdParams = ++ std::variant<std::vector<NumericThresholdParams>, ++ std::vector<DiscreteThresholdParams>>; ++ + using TriggerThresholdParamsExt = + std::variant<std::monostate, std::vector<NumericThresholdParams>, + std::vector<DiscreteThresholdParams>>; +@@ -35,6 +45,463 @@ using TriggerGetParamsVariant = + std::variant<std::monostate, bool, std::string, TriggerThresholdParamsExt, + TriggerSensorsParams, std::vector<std::string>>; + ++namespace add_trigger ++{ ++ ++enum class MetricType ++{ ++ Discrete, ++ Numeric ++}; ++ ++enum class DiscreteCondition ++{ ++ Specified, ++ Changed ++}; ++ ++struct Context ++{ ++ struct ++ { ++ std::string id; ++ std::string name; ++ std::vector<std::string> actions; ++ std::vector<std::pair<sdbusplus::message::object_path, std::string>> ++ sensors; ++ std::vector<std::string> reportNames; ++ TriggerThresholdParams thresholds; ++ } dbusArgs; ++ ++ struct ++ { ++ std::optional<DiscreteCondition> discreteCondition; ++ std::optional<MetricType> metricType; ++ std::optional<std::vector<std::string>> metricProperties; ++ } parsedInfo; ++ ++ boost::container::flat_map<std::string, std::string> uriToDbusMerged{}; ++}; ++ ++inline std::optional<MetricType> getMetricType(const std::string& metricType) ++{ ++ if (metricType == "Discrete") ++ { ++ return MetricType::Discrete; ++ } ++ if (metricType == "Numeric") ++ { ++ return MetricType::Numeric; ++ } ++ return std::nullopt; ++} ++ ++inline std::optional<DiscreteCondition> ++ getDiscreteCondition(const std::string& discreteTriggerCondition) ++{ ++ if (discreteTriggerCondition == "Specified") ++ { ++ return DiscreteCondition::Specified; ++ } ++ if (discreteTriggerCondition == "Changed") ++ { ++ return DiscreteCondition::Changed; ++ } ++ return std::nullopt; ++} ++ ++inline bool parseNumericThresholds(crow::Response& res, ++ nlohmann::json& numericThresholds, ++ Context& ctx) ++{ ++ if (!numericThresholds.is_object()) ++ { ++ messages::propertyValueTypeError(res, numericThresholds.dump(), ++ "NumericThresholds"); ++ return false; ++ } ++ ++ std::vector<NumericThresholdParams> parsedParams; ++ parsedParams.reserve(numericThresholds.size()); ++ ++ for (auto& [thresholdName, thresholdData] : numericThresholds.items()) ++ { ++ if (std::find(supportedNumericThresholdNames.begin(), ++ supportedNumericThresholdNames.end(), ++ thresholdName) == supportedNumericThresholdNames.end()) ++ { ++ messages::propertyUnknown(res, thresholdName); ++ return false; ++ } ++ ++ double reading = .0; ++ std::string activation; ++ std::string dwellTimeStr; ++ ++ if (!json_util::readJson(thresholdData, res, "Reading", reading, ++ "Activation", activation, "DwellTime", ++ dwellTimeStr)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ parsedParams.emplace_back(thresholdName, ++ static_cast<uint64_t>(dwellTime->count()), ++ activation, reading); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseDiscreteTriggers( ++ crow::Response& res, ++ std::optional<std::vector<nlohmann::json>>& discreteTriggers, Context& ctx) ++{ ++ std::vector<DiscreteThresholdParams> parsedParams; ++ if (!discreteTriggers) ++ { ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++ } ++ ++ parsedParams.reserve(discreteTriggers->size()); ++ for (nlohmann::json& thresholdInfo : *discreteTriggers) ++ { ++ std::optional<std::string> name; ++ std::string value; ++ std::string dwellTimeStr; ++ std::string severity; ++ ++ if (!json_util::readJson(thresholdInfo, res, "Name", name, "Value", ++ value, "DwellTime", dwellTimeStr, "Severity", ++ severity)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ if (!name) ++ { ++ name = ""; ++ } ++ ++ parsedParams.emplace_back( ++ *name, severity, static_cast<uint64_t>(dwellTime->count()), value); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseTriggerThresholds( ++ crow::Response& res, ++ std::optional<std::vector<nlohmann::json>>& discreteTriggers, ++ std::optional<nlohmann::json>& numericThresholds, Context& ctx) ++{ ++ if (discreteTriggers && numericThresholds) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "NumericThresholds"); ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "DiscreteTriggers"); ++ return false; ++ } ++ ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (numericThresholds) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggerCondition", ++ "NumericThresholds"); ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ ++ if (ctx.parsedInfo.metricType) ++ { ++ if (*ctx.parsedInfo.metricType == MetricType::Discrete && ++ numericThresholds) ++ { ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ discreteTriggers) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ ctx.parsedInfo.discreteCondition) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ ++ if (discreteTriggers || ctx.parsedInfo.discreteCondition || ++ (ctx.parsedInfo.metricType && ++ *ctx.parsedInfo.metricType == MetricType::Discrete)) ++ { ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ !discreteTriggers) ++ { ++ messages::createFailedMissingReqProperties(res, ++ "DiscreteTriggers"); ++ return false; ++ } ++ if (discreteTriggers && ((*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ discreteTriggers->empty()) || ++ (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Changed && ++ !discreteTriggers->empty()))) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ if (!parseDiscreteTriggers(res, discreteTriggers, ctx)) ++ { ++ return false; ++ } ++ } ++ else if (numericThresholds) ++ { ++ if (!parseNumericThresholds(res, *numericThresholds, ctx)) ++ { ++ return false; ++ } ++ } ++ else ++ { ++ messages::createFailedMissingReqProperties( ++ res, "'DiscreteTriggers', 'NumericThresholds', " ++ "'DiscreteTriggerCondition' or 'MetricType'"); ++ return false; ++ } ++ return true; ++} ++ ++inline bool parseLinks(crow::Response& res, nlohmann::json& links, Context& ctx) ++{ ++ if (links.empty()) ++ { ++ return true; ++ } ++ ++ std::optional<std::vector<std::string>> metricReportDefinitions; ++ if (!json_util::readJson(links, res, "MetricReportDefinitions", ++ metricReportDefinitions)) ++ { ++ return false; ++ } ++ ++ if (metricReportDefinitions) ++ { ++ ctx.dbusArgs.reportNames.reserve(metricReportDefinitions->size()); ++ for (std::string& reportDefinionUri : *metricReportDefinitions) ++ { ++ std::optional<std::string> reportName = ++ getReportNameFromReportDefinitionUri(reportDefinionUri); ++ if (!reportName) ++ { ++ messages::propertyValueIncorrect(res, "MetricReportDefinitions", ++ reportDefinionUri); ++ return false; ++ } ++ ctx.dbusArgs.reportNames.emplace_back("TelemetryService/" + ++ *reportName); ++ } ++ } ++ return true; ++} ++ ++inline bool parseMetricProperties(crow::Response& res, Context& ctx) ++{ ++ if (!ctx.parsedInfo.metricProperties) ++ { ++ return true; ++ } ++ ++ ctx.dbusArgs.sensors.reserve(ctx.parsedInfo.metricProperties->size()); ++ ++ size_t uriIdx = 0; ++ for (const std::string& uri : *ctx.parsedInfo.metricProperties) ++ { ++ auto el = ctx.uriToDbusMerged.find(uri); ++ if (el == ctx.uriToDbusMerged.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corsresponding to URI " ++ << uri; ++ messages::propertyValueNotInList( ++ res, uri, "MetricProperties/" + std::to_string(uriIdx)); ++ return false; ++ } ++ ++ const std::string& dbusPath = el->second; ++ ctx.dbusArgs.sensors.emplace_back(dbusPath, uri); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline bool parsePostTriggerParams(crow::Response& res, ++ const crow::Request& req, Context& ctx) ++{ ++ std::optional<std::string> id; ++ std::optional<std::string> name; ++ std::optional<std::string> metricType; ++ std::optional<std::vector<std::string>> triggerActions; ++ std::optional<std::string> discreteTriggerCondition; ++ std::optional<std::vector<nlohmann::json>> discreteTriggers; ++ std::optional<nlohmann::json> numericThresholds; ++ std::optional<nlohmann::json> links; ++ if (!json_util::readJsonPatch( ++ req, res, "Id", id, "Name", name, "MetricType", metricType, ++ "TriggerActions", triggerActions, "DiscreteTriggerCondition", ++ discreteTriggerCondition, "DiscreteTriggers", discreteTriggers, ++ "NumericThresholds", numericThresholds, "MetricProperties", ++ ctx.parsedInfo.metricProperties, "Links", links)) ++ { ++ return false; ++ } ++ ++ ctx.dbusArgs.id = id.value_or(""); ++ ctx.dbusArgs.name = name.value_or(""); ++ ++ if (metricType) ++ { ++ if (!(ctx.parsedInfo.metricType = getMetricType(*metricType))) ++ { ++ messages::propertyValueIncorrect(res, "MetricType", *metricType); ++ return false; ++ } ++ } ++ ++ if (discreteTriggerCondition) ++ { ++ if (!(ctx.parsedInfo.discreteCondition = ++ getDiscreteCondition(*discreteTriggerCondition))) ++ { ++ messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", ++ *discreteTriggerCondition); ++ return false; ++ } ++ } ++ ++ if (triggerActions) ++ { ++ ctx.dbusArgs.actions.reserve(triggerActions->size()); ++ for (const std::string& action : *triggerActions) ++ { ++ if (const std::optional<std::string>& dbusAction = ++ redfishActionToDbusAction(action)) ++ { ++ ctx.dbusArgs.actions.emplace_back(*dbusAction); ++ } ++ else ++ { ++ messages::propertyValueNotInList(res, action, "TriggerActions"); ++ return false; ++ } ++ } ++ } ++ ++ if (!parseTriggerThresholds(res, discreteTriggers, numericThresholds, ctx)) ++ { ++ return false; ++ } ++ ++ if (links) ++ { ++ if (!parseLinks(res, *links, ctx)) ++ { ++ return false; ++ } ++ } ++ return true; ++} ++ ++inline void createTrigger(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ Context& ctx) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp = asyncResp, id = ctx.dbusArgs.id]( ++ const boost::system::error_code ec, const std::string& dbusPath) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists(aResp->res, "Trigger", "Id", ++ id); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(aResp->res); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ const std::optional<std::string>& triggerId = ++ getTriggerIdFromDbusPath(dbusPath); ++ if (!triggerId) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "Unknown data returned by " ++ "AddTrigger DBus method"; ++ return; ++ } ++ ++ messages::created(aResp->res); ++ const boost::url locationUrl = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", *triggerId); ++ aResp->res.addHeader("Location", ++ std::string_view(locationUrl.string().data(), ++ locationUrl.string().size())); ++ }, ++ service, "/xyz/openbmc_project/Telemetry/Triggers", ++ "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", ++ "TelemetryService/" + ctx.dbusArgs.id, ctx.dbusArgs.name, ++ ctx.dbusArgs.actions, ctx.dbusArgs.sensors, ctx.dbusArgs.reportNames, ++ ctx.dbusArgs.thresholds); ++} ++ ++} // namespace add_trigger ++ ++namespace get_trigger ++{ ++ + inline std::optional<std::string> + getRedfishFromDbusAction(const std::string& dbusAction) + { +@@ -290,6 +757,8 @@ inline bool fillTrigger( + return true; + } + ++} // namespace get_trigger ++ + } // namespace telemetry + + inline void requestRoutesTriggerCollection(App& app) +@@ -301,14 +770,77 @@ inline void requestRoutesTriggerCollection(App& app) + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { + asyncResp->res.jsonValue["@odata.type"] = + "#TriggersCollection.TriggersCollection"; +- asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri; ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/Triggers"; + asyncResp->res.jsonValue["Name"] = "Triggers Collection"; + const std::vector<const char*> interfaces{ + telemetry::triggerInterface}; + collection_util::getCollectionMembers( +- asyncResp, telemetry::triggerUri, interfaces, ++ asyncResp, "/redfish/v1/TelemetryService/Triggers", ++ interfaces, + "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); + }); ++ ++ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") ++ .privileges(redfish::privileges::postTriggersCollection) ++ .methods(boost::beast::http::verb::post)( ++ [](const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { ++ const auto ctx = ++ std::make_shared<telemetry::add_trigger::Context>(); ++ if (!telemetry::add_trigger::parsePostTriggerParams( ++ asyncResp->res, req, *ctx)) ++ { ++ return; ++ } ++ ++ if (!ctx->parsedInfo.metricProperties || ++ ctx->parsedInfo.metricProperties->empty()) ++ { ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ return; ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode( ++ asyncResp, *ctx->parsedInfo.metricProperties, ++ chassisSensors)) ++ { ++ return; ++ } ++ ++ const auto finalizer = ++ std::make_shared<utils::Finalizer>([asyncResp, ctx] { ++ if ((asyncResp->res).result() != ++ boost::beast::http::status::ok) ++ { ++ return; ++ } ++ if (!telemetry::add_trigger::parseMetricProperties( ++ asyncResp->res, *ctx)) ++ { ++ return; ++ } ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ }); ++ ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, ctx, ++ finalizer](const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status == boost::beast::http::status::ok) ++ { ++ ctx->uriToDbusMerged.insert(uriToDbus.begin(), ++ uriToDbus.end()); ++ } ++ }); ++ } ++ }); + } + + inline void requestRoutesTrigger(App& app) +@@ -339,8 +871,8 @@ inline void requestRoutesTrigger(App& app) + return; + } + +- if (!telemetry::fillTrigger(asyncResp->res.jsonValue, +- id, ret)) ++ if (!telemetry::get_trigger::fillTrigger( ++ asyncResp->res.jsonValue, id, ret)) + { + messages::internalError(asyncResp->res); + } +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch new file mode 100644 index 000000000..0ce5a3ea6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch @@ -0,0 +1,559 @@ +From 4bd7f53cc01315774eedef637f5d1824cd7f662a Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Thu, 17 Jun 2021 13:37:57 +0000 +Subject: [PATCH] Switched bmcweb to use new telemetry service API + +Added support for multiple MetricProperties. Added support for new +parameters: CollectionTimeScope, CollectionDuration. + +Tested: + - It is possible to create MetricReportDefinitions with multiple + MetricProperties. + - Stub values for new parameters are correctly passed to telemetry + service. + - All existing telemetry service functionalities remain unchanged. + +Change-Id: I2cd17069e3ea015c8f5571c29278f1d50536272a +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> +--- + include/dbus_utility.hpp | 5 +- + redfish-core/lib/metric_report_definition.hpp | 330 ++++++++++++------ + redfish-core/lib/telemetry_service.hpp | 13 + + 3 files changed, 240 insertions(+), 108 deletions(-) + +diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp +index 481b33d..32153f8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -50,8 +50,9 @@ using DbusVariantType = std::variant< + std::vector<std::tuple<std::string, std::string>>, + std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>, + std::vector<std::tuple<uint32_t, size_t>>, +- std::vector<std::tuple<sdbusplus::message::object_path, std::string, +- std::string, std::string>> ++ std::vector<std::tuple< ++ std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, ++ std::string, std::string, std::string, uint64_t>> + >; + + // clang-format on +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 1a520f3..ac980aa 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -17,50 +17,68 @@ namespace redfish + + namespace telemetry + { +- + constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + +-using ReadingParameters = +- std::vector<std::tuple<sdbusplus::message::object_path, std::string, +- std::string, std::string>>; ++using ReadingParameters = std::vector<std::tuple< ++ std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, ++ std::string, std::string, std::string, uint64_t>>; ++ ++std::string toReadfishReportAction(std::string_view action) ++{ ++ if (action == "EmitsReadingsUpdate") ++ { ++ return "RedfishEvent"; ++ } ++ if (action == "LogToMetricReportsCollection") ++ { ++ return "LogToMetricReportsCollection"; ++ } ++ return ""; ++} ++ ++std::string toDbusReportAction(std::string_view action) ++{ ++ if (action == "RedfishEvent") ++ { ++ return "EmitsReadingsUpdate"; ++ } ++ if (action == "LogToMetricReportsCollection") ++ { ++ return "LogToMetricReportsCollection"; ++ } ++ return ""; ++} + + inline void fillReportDefinition( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, + const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& +- ret) ++ properties) + { +- asyncResp->res.jsonValue["@odata.type"] = +- "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; +- asyncResp->res.jsonValue["@odata.id"] = +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReportDefinitions", id) +- .string(); +- asyncResp->res.jsonValue["Id"] = id; +- asyncResp->res.jsonValue["Name"] = id; +- asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReports", id) +- .string(); +- asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; +- asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; +- +- const bool* emitsReadingsUpdate = nullptr; +- const bool* logToMetricReportsCollection = nullptr; ++ const std::vector<std::string>* reportActions = nullptr; + const ReadingParameters* readingParams = nullptr; + const std::string* reportingType = nullptr; ++ const std::string* reportUpdates = nullptr; ++ const std::string* name = nullptr; ++ const uint64_t* appendLimit = nullptr; + const uint64_t* interval = nullptr; +- for (const auto& [key, var] : ret) ++ const bool* enabled = nullptr; ++ ++ for (const auto& [key, var] : properties) + { +- if (key == "EmitsReadingsUpdate") ++ if (key == "ReportActions") ++ { ++ reportActions = std::get_if<std::vector<std::string>>(&var); ++ } ++ else if (key == "ReportUpdates") + { +- emitsReadingsUpdate = std::get_if<bool>(&var); ++ reportUpdates = std::get_if<std::string>(&var); + } +- else if (key == "LogToMetricReportsCollection") ++ else if (key == "AppendLimit") + { +- logToMetricReportsCollection = std::get_if<bool>(&var); ++ appendLimit = std::get_if<uint64_t>(&var); + } +- else if (key == "ReadingParameters") ++ else if (key == "ReadingParametersFutureVersion") + { + readingParams = std::get_if<ReadingParameters>(&var); + } +@@ -72,73 +90,149 @@ inline void fillReportDefinition( + { + interval = std::get_if<uint64_t>(&var); + } ++ else if (key == "Name") ++ { ++ name = std::get_if<std::string>(&var); ++ } ++ else if (key == "Enabled") ++ { ++ enabled = std::get_if<bool>(&var); ++ } + } +- if (emitsReadingsUpdate == nullptr || +- logToMetricReportsCollection == nullptr || readingParams == nullptr || +- reportingType == nullptr || interval == nullptr) ++ ++ std::vector<std::string> redfishReportActions; ++ if (reportActions != nullptr) + { +- BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; +- messages::internalError(asyncResp->res); +- return; ++ for (const std::string& action : *reportActions) ++ { ++ std::string redfishAction = toReadfishReportAction(action); ++ ++ if (redfishAction.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Unknown ReportActions element: " << action; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ redfishReportActions.emplace_back(std::move(redfishAction)); ++ } + } + +- std::vector<std::string> redfishReportActions; +- redfishReportActions.reserve(2); +- if (*emitsReadingsUpdate) ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; ++ asyncResp->res.jsonValue["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); ++ ++ if (enabled != nullptr) + { +- redfishReportActions.emplace_back("RedfishEvent"); ++ asyncResp->res.jsonValue["Status"]["State"] = ++ *enabled ? "Enabled" : "Disabled"; ++ asyncResp->res.jsonValue["MetricReportDefinitionEnabled"] = *enabled; + } +- if (*logToMetricReportsCollection) ++ ++ if (appendLimit != nullptr) + { +- redfishReportActions.emplace_back("LogToMetricReportsCollection"); ++ asyncResp->res.jsonValue["AppendLimit"] = *appendLimit; + } + +- nlohmann::json metrics = nlohmann::json::array(); +- for (const auto& [sensorPath, operationType, id, metadata] : *readingParams) ++ if (reportUpdates != nullptr) + { +- metrics.push_back({ +- {"MetricId", id}, +- {"MetricProperties", {metadata}}, +- }); ++ asyncResp->res.jsonValue["ReportUpdates"] = *reportUpdates; ++ } ++ ++ if (name != nullptr) ++ { ++ asyncResp->res.jsonValue["Name"] = *name; ++ } ++ ++ if (reportActions != nullptr) ++ { ++ asyncResp->res.jsonValue["ReportActions"] = ++ std::move(redfishReportActions); ++ } ++ ++ if (reportingType != nullptr) ++ { ++ asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; ++ } ++ ++ if (interval != nullptr) ++ { ++ asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = ++ time_utils::toDurationString(std::chrono::milliseconds(*interval)); ++ } ++ ++ if (readingParams != nullptr) ++ { ++ nlohmann::json& metrics = asyncResp->res.jsonValue["Metrics"]; ++ metrics = nlohmann::json::array(); ++ for (auto& [sensorData, collectionFunction, id, collectionTimeScope, ++ collectionDuration] : *readingParams) ++ { ++ std::vector<std::string> metricProperties; ++ ++ for (const auto& [sensorPath, sensorMetadata] : sensorData) ++ { ++ metricProperties.emplace_back(sensorMetadata); ++ } ++ ++ metrics.push_back( ++ {{"MetricId", id}, ++ {"MetricProperties", std::move(metricProperties)}, ++ {"CollectionFunction", collectionFunction}, ++ {"CollectionDuration", ++ time_utils::toDurationString( ++ std::chrono::milliseconds(collectionDuration))}, ++ {"CollectionTimeScope", collectionTimeScope}}); ++ } + } +- asyncResp->res.jsonValue["Metrics"] = metrics; +- asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; +- asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; +- asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = +- time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } + + struct AddReportArgs + { +- std::string name; ++ struct MetricArgs ++ { ++ std::string id; ++ std::vector<std::string> uris; ++ std::optional<std::string> collectionFunction; ++ std::optional<std::string> collectionTimeScope; ++ std::optional<uint64_t> collectionDuration; ++ }; ++ ++ std::optional<std::string> id; ++ std::optional<std::string> name; + std::string reportingType; +- bool emitsReadingsUpdate = false; +- bool logToMetricReportsCollection = false; ++ std::optional<std::string> reportUpdates; ++ std::optional<uint64_t> appendLimit; ++ std::vector<std::string> reportActions; + uint64_t interval = 0; +- std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++ std::vector<MetricArgs> metrics; + }; + + inline bool toDbusReportActions(crow::Response& res, +- std::vector<std::string>& actions, ++ const std::vector<std::string>& actions, + AddReportArgs& args) + { + size_t index = 0; +- for (auto& action : actions) ++ for (const auto& action : actions) + { +- if (action == "RedfishEvent") +- { +- args.emitsReadingsUpdate = true; +- } +- else if (action == "LogToMetricReportsCollection") +- { +- args.logToMetricReportsCollection = true; +- } +- else ++ std::string dbusReportAction = toDbusReportAction(action); ++ ++ if (dbusReportAction.empty()) + { + messages::propertyValueNotInList( + res, action, "ReportActions/" + std::to_string(index)); + return false; + } ++ ++ args.reportActions.emplace_back(std::move(dbusReportAction)); + index++; + } + return true; +@@ -150,27 +244,17 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + std::vector<nlohmann::json> metrics; + std::vector<std::string> reportActions; + std::optional<nlohmann::json> schedule; +- if (!json_util::readJsonPatch(req, res, "Id", args.name, "Metrics", metrics, +- "MetricReportDefinitionType", +- args.reportingType, "ReportActions", +- reportActions, "Schedule", schedule)) +- { +- return false; +- } +- +- constexpr const char* allowedCharactersInName = +- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; +- if (args.name.empty() || args.name.find_first_not_of( +- allowedCharactersInName) != std::string::npos) ++ if (!json_util::readJsonPatch( ++ req, res, "Id", args.id, "Name", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", args.reportingType, "ReportUpdates", ++ args.reportUpdates, "AppendLimit", args.appendLimit, ++ "ReportActions", reportActions, "Schedule", schedule)) + { +- 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") ++ if (args.reportingType != "Periodic" && args.reportingType != "OnRequest" && ++ args.reportingType != "OnChange") + { + messages::propertyValueNotInList(res, args.reportingType, + "MetricReportDefinitionType"); +@@ -211,15 +295,35 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + 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)) ++ std::optional<std::string> collectionDurationStr; ++ AddReportArgs::MetricArgs metricArgs; ++ if (!json_util::readJson( ++ m, res, "MetricId", metricArgs.id, "MetricProperties", ++ metricArgs.uris, "CollectionFunction", ++ metricArgs.collectionFunction, "CollectionTimeScope", ++ metricArgs.collectionTimeScope, "CollectionDuration", ++ collectionDurationStr)) + { + return false; + } + +- args.metrics.emplace_back(std::move(id), std::move(uris)); ++ if (collectionDurationStr) ++ { ++ std::optional<std::chrono::milliseconds> duration = ++ time_utils::fromDurationString(*collectionDurationStr); ++ ++ if (!duration || duration->count() < 0) ++ { ++ messages::propertyValueIncorrect(res, "CollectionDuration", ++ *collectionDurationStr); ++ return false; ++ } ++ ++ metricArgs.collectionDuration = ++ static_cast<uint64_t>(duration->count()); ++ } ++ ++ args.metrics.emplace_back(std::move(metricArgs)); + } + + return true; +@@ -227,15 +331,14 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + + inline bool getChassisSensorNode( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +- const std::vector<std::pair<std::string, std::vector<std::string>>>& +- metrics, ++ const std::vector<AddReportArgs::MetricArgs>& metrics, + boost::container::flat_set<std::pair<std::string, std::string>>& matched) + { +- for (const auto& [id, uris] : metrics) ++ for (const auto& metric : metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ for (size_t i = 0; i < metric.uris.size(); i++) + { +- const std::string& uri = uris[i]; ++ const std::string& uri = metric.uris[i]; + std::string chassis; + std::string node; + +@@ -280,11 +383,16 @@ class AddReport + telemetry::ReadingParameters readingParams; + readingParams.reserve(args.metrics.size()); + +- for (const auto& [id, uris] : args.metrics) ++ for (auto& metric : args.metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ std::vector< ++ std::tuple<sdbusplus::message::object_path, std::string>> ++ sensorParams; ++ sensorParams.reserve(metric.uris.size()); ++ ++ for (size_t i = 0; i < metric.uris.size(); i++) + { +- const std::string& uri = uris[i]; ++ const std::string& uri = metric.uris[i]; + auto el = uriToDbus.find(uri); + if (el == uriToDbus.end()) + { +@@ -298,17 +406,23 @@ class AddReport + } + + const std::string& dbusPath = el->second; +- readingParams.emplace_back(dbusPath, "SINGLE", id, uri); ++ sensorParams.emplace_back(dbusPath, uri); + } ++ ++ readingParams.emplace_back( ++ std::move(sensorParams), metric.collectionFunction.value_or(""), ++ std::move(metric.id), metric.collectionTimeScope.value_or(""), ++ metric.collectionDuration.value_or(0U)); + } + const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp; + crow::connections::systemBus->async_method_call( +- [aResp, name = args.name, uriToDbus = std::move(uriToDbus)]( ++ [aResp, id = args.id.value_or(""), ++ uriToDbus = std::move(uriToDbus)]( + const boost::system::error_code ec, const std::string&) { + if (ec == boost::system::errc::file_exists) + { + messages::resourceAlreadyExists( +- aResp->res, "MetricReportDefinition", "Id", name); ++ aResp->res, "MetricReportDefinition", "Id", id); + return; + } + if (ec == boost::system::errc::too_many_files_open) +@@ -338,10 +452,12 @@ class AddReport + messages::created(aResp->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); ++ "xyz.openbmc_project.Telemetry.ReportManager", ++ "AddReportFutureVersion", ++ "TelemetryService/" + args.id.value_or(""), args.name.value_or(""), ++ args.reportingType, args.reportUpdates.value_or("Overwrite"), ++ args.appendLimit.value_or(0), args.reportActions, args.interval, ++ readingParams); + } + + AddReport(const AddReport&) = delete; +@@ -436,10 +552,10 @@ inline void requestRoutesMetricReportDefinition(App& app) + const std::string& id) { + crow::connections::systemBus->async_method_call( + [asyncResp, +- id](const boost::system::error_code ec, ++ id](boost::system::error_code ec, + const std::vector<std::pair< + std::string, dbus::utility::DbusVariantType>>& +- ret) { ++ properties) { + if (ec.value() == EBADR || + ec == boost::system::errc::host_unreachable) + { +@@ -454,12 +570,14 @@ inline void requestRoutesMetricReportDefinition(App& app) + return; + } + +- telemetry::fillReportDefinition(asyncResp, id, ret); ++ telemetry::fillReportDefinition(asyncResp, id, ++ properties); + }, + telemetry::service, telemetry::getDbusReportPath(id), + "org.freedesktop.DBus.Properties", "GetAll", + telemetry::reportInterface); + }); ++ + BMCWEB_ROUTE(app, + "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") + .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index c1fe7d0..64d712b 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -49,6 +49,8 @@ inline void handleTelemetryServiceGet( + + const size_t* maxReports = nullptr; + const uint64_t* minInterval = nullptr; ++ const std::vector<std::string>* supportedCollectionFunction = ++ nullptr; + for (const auto& [key, var] : ret) + { + if (key == "MaxReports") +@@ -59,6 +61,11 @@ inline void handleTelemetryServiceGet( + { + minInterval = std::get_if<uint64_t>(&var); + } ++ else if (key == "SupportedOperationTypes") ++ { ++ supportedCollectionFunction = ++ std::get_if<std::vector<std::string>>(&var); ++ } + } + if (maxReports == nullptr || minInterval == nullptr) + { +@@ -72,6 +79,12 @@ inline void handleTelemetryServiceGet( + asyncResp->res.jsonValue["MinCollectionInterval"] = + time_utils::toDurationString(std::chrono::milliseconds( + static_cast<time_t>(*minInterval))); ++ ++ if (supportedCollectionFunction != nullptr) ++ { ++ asyncResp->res.jsonValue["SupportedCollectionFunction"] = ++ *supportedCollectionFunction; ++ } + }, + telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", + "org.freedesktop.DBus.Properties", "GetAll", +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch new file mode 100644 index 000000000..ea409fe09 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch @@ -0,0 +1,948 @@ +From 4c39922af8bf73b13150455166e7bd1fd8645a47 Mon Sep 17 00:00:00 2001 +From: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> +Date: Fri, 17 Dec 2021 13:02:23 +0100 +Subject: [PATCH] Add PUT and PATCH for MetricReportDefinition + +Support for PUT and PATCH methods is added to Metric Report Definition, +now Report can be replaced by PUT or selected read/write properties can +be modified by PATCH method + +Tested: +- Added new Report via PUT and extracted Report via GET checking if + received data is appropriate +- Added Report via POST, overwrite it via PUT and extracted Report via + GET checking if received data is appropriate +- Added Report via POST, overwrite editable properties via PATCH and + fetched Report via GET checking if received data is properly modified + +Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com> +Change-Id: If75110a92c55c9e4f2415f0ed4471baa802643ff +--- + redfish-core/lib/metric_report_definition.hpp | 774 ++++++++++++++++-- + 1 file changed, 692 insertions(+), 82 deletions(-) + +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 30d83b0..e726c8c 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -8,9 +8,9 @@ + #include <boost/container/flat_map.hpp> + #include <dbus_utility.hpp> + #include <registries/privilege_registry.hpp> ++#include <utils/stl_utils.hpp> + + #include <tuple> +-#include <variant> + + namespace redfish + { +@@ -22,6 +22,12 @@ using ReadingParameters = std::vector<std::tuple< + std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, + std::string, std::string, std::string, uint64_t>>; + ++enum class addReportType ++{ ++ create, ++ replace ++}; ++ + std::string toReadfishReportAction(std::string_view action) + { + if (action == "EmitsReadingsUpdate") +@@ -48,6 +54,38 @@ std::string toDbusReportAction(std::string_view action) + return ""; + } + ++inline bool verifyCommonErrors(crow::Response& res, const std::string& id, ++ const boost::system::error_code ec) ++{ ++ if (ec.value() == EBADR || ec == boost::system::errc::host_unreachable) ++ { ++ messages::resourceNotFound(res, "MetricReportDefinition", id); ++ return false; ++ } ++ ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists(res, "MetricReportDefinition", "Id", ++ id); ++ return false; ++ } ++ ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(res); ++ return false; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "DBUS response error " << ec; ++ messages::internalError(res); ++ return false; ++ } ++ ++ return true; ++} ++ + inline void fillReportDefinition( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, + const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& +@@ -193,17 +231,17 @@ inline void fillReportDefinition( + } + } + +-struct AddReportArgs ++struct MetricArgs + { +- struct MetricArgs +- { +- std::string id; +- std::vector<std::string> uris; +- std::optional<std::string> collectionFunction; +- std::optional<std::string> collectionTimeScope; +- std::optional<uint64_t> collectionDuration; +- }; ++ std::string id; ++ std::vector<std::string> uris; ++ std::optional<std::string> collectionFunction; ++ std::optional<std::string> collectionTimeScope; ++ std::optional<uint64_t> collectionDuration; ++}; + ++struct AddReportArgs ++{ + std::optional<std::string> id; + std::optional<std::string> name; + std::string reportingType; +@@ -215,22 +253,22 @@ struct AddReportArgs + }; + + inline bool toDbusReportActions(crow::Response& res, +- const std::vector<std::string>& actions, +- AddReportArgs& args) ++ const std::vector<std::string>& redfishActions, ++ std::vector<std::string>& dbusActions) + { + size_t index = 0; +- for (const auto& action : actions) ++ for (const auto& redfishAction : redfishActions) + { +- std::string dbusReportAction = toDbusReportAction(action); ++ std::string dbusAction = toDbusReportAction(redfishAction); + +- if (dbusReportAction.empty()) ++ if (dbusAction.empty()) + { + messages::propertyValueNotInList( +- res, action, "ReportActions/" + std::to_string(index)); ++ res, redfishAction, "ReportActions/" + std::to_string(index)); + return false; + } + +- args.reportActions.emplace_back(std::move(dbusReportAction)); ++ dbusActions.emplace_back(std::move(dbusAction)); + index++; + } + return true; +@@ -259,7 +297,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + return false; + } + +- if (!toDbusReportActions(res, reportActions, args)) ++ if (!toDbusReportActions(res, reportActions, args.reportActions)) + { + return false; + } +@@ -294,7 +332,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + for (auto& m : metrics) + { + std::optional<std::string> collectionDurationStr; +- AddReportArgs::MetricArgs metricArgs; ++ MetricArgs metricArgs; + if (!json_util::readJson( + m, res, "MetricId", metricArgs.id, "MetricProperties", + metricArgs.uris, "CollectionFunction", +@@ -329,7 +367,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + + inline bool getChassisSensorNode( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +- const std::vector<AddReportArgs::MetricArgs>& metrics, ++ const std::vector<MetricArgs>& metrics, + boost::container::flat_set<std::pair<std::string, std::string>>& matched) + { + for (const auto& metric : metrics) +@@ -363,13 +401,122 @@ inline bool getChassisSensorNode( + return true; + } + ++inline bool getReadingParametersFromMetrics( ++ crow::Response& res, const std::vector<MetricArgs>& metrics, ++ const boost::container::flat_map<std::string, std::string>& uriToDbus, ++ ReadingParameters& readingParams) ++{ ++ if (metrics.empty()) ++ { ++ return true; ++ } ++ ++ readingParams.reserve(metrics.size()); ++ for (const auto& metric : metrics) ++ { ++ std::vector<std::tuple<sdbusplus::message::object_path, std::string>> ++ sensorParams; ++ sensorParams.reserve(metric.uris.size()); ++ ++ for (size_t i = 0; i < metric.uris.size(); i++) ++ { ++ const std::string& uri = metric.uris[i]; ++ auto el = uriToDbus.find(uri); ++ if (el == uriToDbus.end()) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to find DBus sensor corresponding to URI " ++ << uri; ++ messages::propertyValueNotInList( ++ res, uri, "MetricProperties/" + std::to_string(i)); ++ return false; ++ } ++ ++ const std::string& dbusPath = el->second; ++ sensorParams.emplace_back(dbusPath, uri); ++ } ++ ++ readingParams.emplace_back( ++ std::move(sensorParams), metric.collectionFunction.value_or(""), ++ metric.id, metric.collectionTimeScope.value_or(""), ++ metric.collectionDuration.value_or(0U)); ++ } ++ ++ return true; ++} ++ ++class UpdateMetrics ++{ ++ public: ++ UpdateMetrics(const std::string& idIn, std::vector<MetricArgs> metricsIn, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) : ++ asyncResp(asyncResp), ++ id(idIn), metrics{std::move(metricsIn)} ++ {} ++ ++ ~UpdateMetrics() ++ { ++ setReadingParams(); ++ } ++ ++ UpdateMetrics(const UpdateMetrics&) = delete; ++ UpdateMetrics(UpdateMetrics&&) = delete; ++ UpdateMetrics& operator=(const UpdateMetrics&) = delete; ++ UpdateMetrics& operator=(UpdateMetrics&&) = delete; ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } ++ ++ void setReadingParams() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ if (!getReadingParametersFromMetrics(asyncResp->res, metrics, uriToDbus, ++ readingParams)) ++ { ++ return; ++ } ++ ++ const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp; ++ crow::connections::systemBus->async_method_call( ++ [aResp, id = id](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified(aResp->res, "Metrics", ++ "Updated"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", ++ "ReadingParametersFutureVersion", ++ dbus::utility::DbusVariantType{readingParams}); ++ } ++ ++ private: ++ const std::shared_ptr<bmcweb::AsyncResp> asyncResp; ++ std::string id; ++ std::vector<MetricArgs> metrics; ++ boost::container::flat_map<std::string, std::string> uriToDbus{}; ++ ReadingParameters readingParams{}; ++}; ++ + class AddReport + { + public: + AddReport(AddReportArgs argsIn, +- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) : ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ addReportType type) : + asyncResp(asyncResp), +- args{std::move(argsIn)} ++ args{std::move(argsIn)}, type(type) + {} + ~AddReport() + { +@@ -378,7 +525,7 @@ class AddReport + return; + } + +- telemetry::ReadingParameters readingParams; ++ ReadingParameters readingParams; + readingParams.reserve(args.metrics.size()); + + for (auto& metric : args.metrics) +@@ -412,22 +559,12 @@ class AddReport + std::move(metric.id), metric.collectionTimeScope.value_or(""), + metric.collectionDuration.value_or(0U)); + } ++ + const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp; + crow::connections::systemBus->async_method_call( +- [aResp, id = args.id.value_or(""), +- uriToDbus = std::move(uriToDbus)]( +- const boost::system::error_code ec, const std::string&) { +- if (ec == boost::system::errc::file_exists) +- { +- messages::resourceAlreadyExists( +- aResp->res, "MetricReportDefinition", "Id", id); +- return; +- } +- if (ec == boost::system::errc::too_many_files_open) +- { +- messages::createLimitReachedForResource(aResp->res); +- return; +- } ++ [aResp, id = args.id.value_or(""), uriToDbus = std::move(uriToDbus), ++ type = type](const boost::system::error_code ec, ++ const std::string&) { + if (ec == boost::system::errc::argument_list_too_long) + { + nlohmann::json metricProperties = nlohmann::json::array(); +@@ -440,16 +577,22 @@ class AddReport + "MetricProperties"); + return; + } +- if (ec) ++ ++ if (!verifyCommonErrors(aResp->res, id, ec)) + { +- messages::internalError(aResp->res); +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; + return; + } + +- messages::created(aResp->res); ++ if (type == addReportType::create) ++ { ++ messages::created(aResp->res); ++ } ++ else ++ { ++ messages::success(aResp->res); ++ } + }, +- telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ service, "/xyz/openbmc_project/Telemetry/Reports", + "xyz.openbmc_project.Telemetry.ReportManager", + "AddReportFutureVersion", + "TelemetryService/" + args.id.value_or(""), args.name.value_or(""), +@@ -471,8 +614,491 @@ class AddReport + private: + const std::shared_ptr<bmcweb::AsyncResp> asyncResp; + AddReportArgs args; ++ const addReportType type; + boost::container::flat_map<std::string, std::string> uriToDbus{}; + }; ++ ++inline void setReportEnabled(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, bool enabled) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ enabled](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currEnabledVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const bool* currEnabled = std::get_if<bool>(&currEnabledVar); ++ if (currEnabled == nullptr || *currEnabled == enabled) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, enabled](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "MetricReportDefinitionEnabled", ++ enabled ? "True" : "False"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "Enabled", ++ dbus::utility::DbusVariantType{enabled}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "Enabled"); ++} ++ ++inline void setReportType(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, const std::string& type) ++{ ++ if (type != "Periodic" && type != "OnChange" && type != "OnRequest") ++ { ++ messages::propertyValueNotInList(aResp->res, type, ++ "MetricReportDefinitionType"); ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, type](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currTypeVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* currType = ++ std::get_if<std::string>(&currTypeVar); ++ if (currType == nullptr || *currType == type) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, type](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "MetricReportDefinitionType", type); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType", ++ dbus::utility::DbusVariantType{type}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType"); ++} ++ ++inline void setReportUpdates(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, const std::string& updates) ++{ ++ if (updates != "Overwrite" && updates != "AppendWrapsWhenFull" && ++ updates != "AppendStopsWhenFull") ++ { ++ messages::propertyValueNotInList(aResp->res, updates, "ReportUpdates"); ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ updates](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currUpdatesVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* currUpdates = ++ std::get_if<std::string>(&currUpdatesVar); ++ if (currUpdates == nullptr || *currUpdates == updates) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, updates](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified(aResp->res, "ReportUpdates", ++ updates); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportUpdates", ++ dbus::utility::DbusVariantType{updates}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportUpdates"); ++} ++ ++inline void setReportActions(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, ++ std::vector<std::string>& redfishActions) ++{ ++ if (redfishActions.size() > 1) ++ { ++ stl_utils::removeDuplicate(redfishActions); ++ } ++ ++ std::vector<std::string> newDbusActions; ++ if (!toDbusReportActions(aResp->res, redfishActions, newDbusActions)) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, redfishActions = std::move(redfishActions), ++ newDbusActions = std::move(newDbusActions)]( ++ const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currDbusActionsVar) mutable { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ std::vector<std::string> currDbusActions; ++ if (const std::vector<std::string>* tmp = ++ std::get_if<std::vector<std::string>>(&currDbusActionsVar)) ++ { ++ currDbusActions = *tmp; ++ } ++ else ++ { ++ messages::internalError(aResp->res); ++ return; ++ } ++ ++ if (newDbusActions.size() == currDbusActions.size()) ++ { ++ std::sort(newDbusActions.begin(), newDbusActions.end()); ++ if (newDbusActions == currDbusActions) ++ { ++ return; ++ } ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ redfishActions](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ std::string redfishActionsStr; ++ for (const auto& redfishAction : redfishActions) ++ { ++ redfishActionsStr += redfishAction + std::string(" "); ++ } ++ if (!redfishActionsStr.empty()) ++ { ++ redfishActionsStr.pop_back(); ++ } ++ messages::propertyValueModified(aResp->res, "ReportActions", ++ redfishActionsStr); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportActions", ++ dbus::utility::DbusVariantType{newDbusActions}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportActions"); ++} ++ ++inline void setReportInterval(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, nlohmann::json& schedule) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp, schedule = std::move(schedule), ++ id](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& typeVar) mutable { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* reportingType = ++ std::get_if<std::string>(&typeVar); ++ if (reportingType == nullptr || *reportingType != "Periodic") ++ { ++ return; ++ } ++ ++ std::string durationStr; ++ if (!json_util::readJson(schedule, aResp->res, "RecurrenceInterval", ++ durationStr)) ++ { ++ return; ++ } ++ ++ std::optional<std::chrono::milliseconds> durationNum = ++ time_utils::fromDurationString(durationStr); ++ uint64_t interval = static_cast<uint64_t>(durationNum->count()); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, interval, durationStr]( ++ const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currIntervalVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const uint64_t* currInterval = ++ std::get_if<uint64_t>(&currIntervalVar); ++ if (currInterval == nullptr || *currInterval == interval) ++ { ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ durationStr](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "RecurrenceInterval", durationStr); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/" ++ "TelemetryService/" + ++ id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "Interval", ++ dbus::utility::DbusVariantType{interval}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "Interval"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType"); ++} ++ ++inline void setReportMetrics(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id, ++ std::vector<nlohmann::json>& metricJsons) ++{ ++ std::vector<MetricArgs> metrics; ++ metrics.reserve(metricJsons.size()); ++ ++ for (auto& m : metricJsons) ++ { ++ MetricArgs metricArgs; ++ std::optional<std::string> collectionDurationStr; ++ if (!json_util::readJson( ++ m, aResp->res, "MetricId", metricArgs.id, "MetricProperties", ++ metricArgs.uris, "CollectionFunction", ++ metricArgs.collectionFunction, "CollectionTimeScope", ++ metricArgs.collectionTimeScope, "CollectionDuration", ++ collectionDurationStr)) ++ { ++ return; ++ } ++ ++ if (collectionDurationStr) ++ { ++ std::optional<std::chrono::milliseconds> duration = ++ time_utils::fromDurationString(*collectionDurationStr); ++ ++ if (!duration || duration->count() < 0) ++ { ++ messages::propertyValueIncorrect( ++ aResp->res, "CollectionDuration", *collectionDurationStr); ++ return; ++ } ++ ++ metricArgs.collectionDuration = ++ static_cast<uint64_t>(duration->count()); ++ } ++ ++ metrics.emplace_back(std::move(metricArgs)); ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!getChassisSensorNode(aResp, metrics, chassisSensors)) ++ { ++ return; ++ } ++ ++ auto updateMetricsReq = ++ std::make_shared<UpdateMetrics>(id, std::move(metrics), aResp); ++ ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [aResp, updateMetricsReq]( ++ 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; ++ } ++ updateMetricsReq->insert(uriToDbus); ++ }); ++ } ++} ++ ++inline void handleReportPatch(const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id) ++{ ++ std::optional<bool> enabled; ++ std::optional<std::string> type; ++ std::optional<std::string> updates; ++ std::optional<std::vector<std::string>> actions; ++ std::optional<nlohmann::json> schedule; ++ std::optional<std::vector<nlohmann::json>> metrics; ++ ++ if (!json_util::readJsonPatch( ++ req, aResp->res, "MetricReportDefinitionEnabled", enabled, ++ "Schedule", schedule, "ReportActions", actions, "Metrics", metrics, ++ "MetricReportDefinitionType", type, "ReportUpdates", updates)) ++ { ++ return; ++ } ++ ++ if (enabled) ++ { ++ setReportEnabled(aResp, id, *enabled); ++ } ++ if (type) ++ { ++ setReportType(aResp, id, *type); ++ } ++ if (updates) ++ { ++ setReportUpdates(aResp, id, *updates); ++ } ++ if (actions) ++ { ++ setReportActions(aResp, id, *actions); ++ } ++ if (schedule) ++ { ++ setReportInterval(aResp, id, *schedule); ++ } ++ if (metrics) ++ { ++ setReportMetrics(aResp, id, *metrics); ++ } ++} ++ ++inline void handleReportPut(const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id) ++{ ++ AddReportArgs args; ++ if (!getUserParameters(aResp->res, req, args)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!getChassisSensorNode(aResp, args.metrics, chassisSensors)) ++ { ++ return; ++ } ++ ++ const std::string reportPath = getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, args = std::move(args), ++ chassisSensors = ++ std::move(chassisSensors)](const boost::system::error_code ec) { ++ addReportType addReportMode = addReportType::replace; ++ if (ec) ++ { ++ if (ec.value() != EBADR) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(aResp->res); ++ return; ++ } ++ BMCWEB_LOG_INFO << "Report not found, creating new report: " ++ << id; ++ addReportMode = addReportType::create; ++ } ++ ++ auto addReportReq = ++ std::make_shared<AddReport>(args, aResp, addReportMode); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [aResp, ++ 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); ++ }); ++ } ++ }, ++ service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); ++} ++ ++inline void handleReportDelete(const std::shared_ptr<bmcweb::AsyncResp>& aResp, ++ const std::string& id) ++{ ++ const std::string reportPath = getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ aResp->res.result(boost::beast::http::status::no_content); ++ }, ++ service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); ++} + } // namespace telemetry + + inline void requestRoutesMetricReportDefinitionCollection(App& app) +@@ -517,7 +1143,7 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) + } + + auto addReportReq = std::make_shared<telemetry::AddReport>( +- std::move(args), asyncResp); ++ std::move(args), asyncResp, telemetry::addReportType::create); + for (const auto& [chassis, sensorType] : chassisSensors) + { + retrieveUriToDbusMap( +@@ -554,17 +1180,9 @@ inline void requestRoutesMetricReportDefinition(App& app) + const std::vector<std::pair< + std::string, dbus::utility::DbusVariantType>>& + properties) { +- if (ec.value() == EBADR || +- ec == boost::system::errc::host_unreachable) ++ if (!redfish::telemetry::verifyCommonErrors( ++ asyncResp->res, id, ec)) + { +- messages::resourceNotFound( +- asyncResp->res, "MetricReportDefinition", id); +- return; +- } +- if (ec) +- { +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; +- messages::internalError(asyncResp->res); + return; + } + +@@ -578,40 +1196,32 @@ inline void requestRoutesMetricReportDefinition(App& app) + + BMCWEB_ROUTE(app, + "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") +- .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) ++ .privileges(redfish::privileges::deleteMetricReportDefinition) + .methods(boost::beast::http::verb::delete_)( + [](const crow::Request&, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +- const std::string& id) +- +- { +- const std::string reportPath = telemetry::getDbusReportPath(id); +- +- crow::connections::systemBus->async_method_call( +- [asyncResp, id](const boost::system::error_code ec) { +- /* +- * boost::system::errc and std::errc are missing value +- * for EBADR error that is defined in Linux. +- */ +- if (ec.value() == EBADR) +- { +- messages::resourceNotFound( +- asyncResp->res, "MetricReportDefinition", id); +- return; +- } ++ const std::string& id) { ++ telemetry::handleReportDelete(asyncResp, id); ++ }); + +- if (ec) +- { +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; +- messages::internalError(asyncResp->res); +- return; +- } ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") ++ .privileges(redfish::privileges::putMetricReportDefinition) ++ .methods(boost::beast::http::verb::put)( ++ [](const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& id) { ++ telemetry::handleReportPut(req, asyncResp, id); ++ }); + +- asyncResp->res.result( +- boost::beast::http::status::no_content); +- }, +- telemetry::service, reportPath, +- "xyz.openbmc_project.Object.Delete", "Delete"); ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") ++ .privileges(redfish::privileges::patchMetricReportDefinition) ++ .methods(boost::beast::http::verb::patch)( ++ [](const crow::Request& req, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& id) { ++ telemetry::handleReportPatch(req, asyncResp, id); + }); + } + } // namespace redfish +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch new file mode 100644 index 000000000..e60da9421 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch @@ -0,0 +1,107 @@ +From 0343399b0a609384da302272576a9c409f3b2794 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke <szymon.dompke@intel.com> +Date: Mon, 21 Mar 2022 17:40:36 +0100 +Subject: [PATCH] Add Links/Triggers to MetricReportDefinition + +This change is adding Triggers property to Links when GET is called on +MetricReportDefinition. It contains array of @odata.id pointing to +Trigger resource if it is also linking to given MRD. + +Testing done: +- Links/Trigger property is returned by GET request on + /redfish/v1/TelemetryService/MetricReportDefinitions/<str>/ + +Signed-off-by: Szymon Dompke <szymon.dompke@intel.com> +Change-Id: I5accf4b50324437b0b185003200078ad2c7020b0 +--- + redfish-core/lib/metric_report_definition.hpp | 46 +++++++++++++++++++ + 1 file changed, 46 insertions(+) + +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index e6b08a1..9ee6013 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -88,6 +88,31 @@ inline bool verifyCommonErrors(crow::Response& res, const std::string& id, + return true; + } + ++inline std::optional<nlohmann::json> ++ getLinkedTriggers(const std::vector<std::string>& triggerIds) ++{ ++ nlohmann::json triggers = nlohmann::json::array(); ++ ++ for (const std::string& id : triggerIds) ++ { ++ sdbusplus::message::object_path path(id); ++ if (path.parent_path() != "TelemetryService") ++ { ++ BMCWEB_LOG_ERROR << "Property TriggerIds contains invalid value: " ++ << id; ++ return std::nullopt; ++ } ++ triggers.push_back({ ++ {"@odata.id", ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "Triggers", path.filename()) ++ .string()}, ++ }); ++ } ++ ++ return std::make_optional(triggers); ++} ++ + inline void fillReportDefinition( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, + const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& +@@ -101,6 +126,7 @@ inline void fillReportDefinition( + const uint64_t* appendLimit = nullptr; + const uint64_t* interval = nullptr; + const bool* enabled = nullptr; ++ const std::vector<std::string>* triggerIds = nullptr; + + for (const auto& [key, var] : properties) + { +@@ -136,6 +162,10 @@ inline void fillReportDefinition( + { + enabled = std::get_if<bool>(&var); + } ++ else if (key == "TriggerIds") ++ { ++ triggerIds = std::get_if<std::vector<std::string>>(&var); ++ } + } + + std::vector<std::string> redfishReportActions; +@@ -156,6 +186,17 @@ inline void fillReportDefinition( + } + } + ++ std::optional<nlohmann::json> linkedTriggers; ++ if (triggerIds != nullptr) ++ { ++ linkedTriggers = getLinkedTriggers(*triggerIds); ++ if (!linkedTriggers) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ } ++ + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + asyncResp->res.jsonValue["@odata.id"] = +@@ -231,6 +272,11 @@ inline void fillReportDefinition( + {"CollectionTimeScope", collectionTimeScope}}); + } + } ++ ++ if (triggerIds != nullptr) ++ { ++ asyncResp->res.jsonValue["Links"]["Triggers"] = *linkedTriggers; ++ } + } + + struct MetricArgs +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README new file mode 100644 index 000000000..224790df8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -0,0 +1,24 @@ +These patches are mirror of upstream TelemetryService implementation. +Until change is integrated they will be manually merged here to enable feature in Intel builds. + +Current revisions: +- LogService field, actual implementation will be upstreamed with triggers feature + file://telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch + +- ref: use url_view for telemetry uris + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51650/7 + +- Fix Trigger GET functionality + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51521/9 + +- Add support for POST on TriggersCollection + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44935/27 + +- Switched bmcweb to use new telemetry service API + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44270/39 + +- Add PUT and PATCH for MetricReportDefinition + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/49796/13 + +- Add Links/Triggers to MetricReportDefinition + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51723/1 |