summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch26
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch193
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch127
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch848
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch559
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch948
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch107
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README24
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