1 #pragma once
2 
3 #include "app.hpp"
4 #include "dbus_utility.hpp"
5 #include "generated/enums/metric_report_definition.hpp"
6 #include "query.hpp"
7 #include "registries/privilege_registry.hpp"
8 #include "sensors.hpp"
9 #include "utils/collection.hpp"
10 #include "utils/dbus_utils.hpp"
11 #include "utils/json_utils.hpp"
12 #include "utils/telemetry_utils.hpp"
13 #include "utils/time_utils.hpp"
14 
15 #include <boost/container/flat_map.hpp>
16 #include <boost/url/format.hpp>
17 #include <sdbusplus/asio/property.hpp>
18 #include <sdbusplus/unpack_properties.hpp>
19 
20 #include <array>
21 #include <map>
22 #include <optional>
23 #include <span>
24 #include <string>
25 #include <string_view>
26 #include <tuple>
27 #include <utility>
28 #include <variant>
29 #include <vector>
30 
31 namespace redfish
32 {
33 
34 namespace telemetry
35 {
36 
37 using ReadingParameters = std::vector<std::tuple<
38     std::vector<std::tuple<sdbusplus::message::object_path, std::string>>,
39     std::string, std::string, uint64_t>>;
40 
41 inline bool verifyCommonErrors(crow::Response& res, const std::string& id,
42                                const boost::system::error_code& ec)
43 {
44     if (ec.value() == EBADR || ec == boost::system::errc::host_unreachable)
45     {
46         messages::resourceNotFound(res, "MetricReportDefinition", id);
47         return false;
48     }
49 
50     if (ec == boost::system::errc::file_exists)
51     {
52         messages::resourceAlreadyExists(res, "MetricReportDefinition", "Id",
53                                         id);
54         return false;
55     }
56 
57     if (ec == boost::system::errc::too_many_files_open)
58     {
59         messages::createLimitReachedForResource(res);
60         return false;
61     }
62 
63     if (ec)
64     {
65         BMCWEB_LOG_ERROR("DBUS response error {}", ec);
66         messages::internalError(res);
67         return false;
68     }
69 
70     return true;
71 }
72 
73 inline metric_report_definition::ReportActionsEnum
74     toRedfishReportAction(std::string_view dbusValue)
75 {
76     if (dbusValue ==
77         "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate")
78     {
79         return metric_report_definition::ReportActionsEnum::RedfishEvent;
80     }
81     if (dbusValue ==
82         "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection")
83     {
84         return metric_report_definition::ReportActionsEnum::
85             LogToMetricReportsCollection;
86     }
87     return metric_report_definition::ReportActionsEnum::Invalid;
88 }
89 
90 inline std::string toDbusReportAction(std::string_view redfishValue)
91 {
92     if (redfishValue == "RedfishEvent")
93     {
94         return "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate";
95     }
96     if (redfishValue == "LogToMetricReportsCollection")
97     {
98         return "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection";
99     }
100     return "";
101 }
102 
103 inline metric_report_definition::MetricReportDefinitionType
104     toRedfishReportingType(std::string_view dbusValue)
105 {
106     if (dbusValue ==
107         "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange")
108     {
109         return metric_report_definition::MetricReportDefinitionType::OnChange;
110     }
111     if (dbusValue ==
112         "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest")
113     {
114         return metric_report_definition::MetricReportDefinitionType::OnRequest;
115     }
116     if (dbusValue ==
117         "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic")
118     {
119         return metric_report_definition::MetricReportDefinitionType::Periodic;
120     }
121     return metric_report_definition::MetricReportDefinitionType::Invalid;
122 }
123 
124 inline std::string toDbusReportingType(std::string_view redfishValue)
125 {
126     if (redfishValue == "OnChange")
127     {
128         return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange";
129     }
130     if (redfishValue == "OnRequest")
131     {
132         return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest";
133     }
134     if (redfishValue == "Periodic")
135     {
136         return "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic";
137     }
138     return "";
139 }
140 
141 inline metric_report_definition::CollectionTimeScope
142     toRedfishCollectionTimeScope(std::string_view dbusValue)
143 {
144     if (dbusValue ==
145         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point")
146     {
147         return metric_report_definition::CollectionTimeScope::Point;
148     }
149     if (dbusValue ==
150         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval")
151     {
152         return metric_report_definition::CollectionTimeScope::Interval;
153     }
154     if (dbusValue ==
155         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval")
156     {
157         return metric_report_definition::CollectionTimeScope::StartupInterval;
158     }
159     return metric_report_definition::CollectionTimeScope::Invalid;
160 }
161 
162 inline std::string toDbusCollectionTimeScope(std::string_view redfishValue)
163 {
164     if (redfishValue == "Point")
165     {
166         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point";
167     }
168     if (redfishValue == "Interval")
169     {
170         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval";
171     }
172     if (redfishValue == "StartupInterval")
173     {
174         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval";
175     }
176     return "";
177 }
178 
179 inline metric_report_definition::ReportUpdatesEnum
180     toRedfishReportUpdates(std::string_view dbusValue)
181 {
182     if (dbusValue ==
183         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite")
184     {
185         return metric_report_definition::ReportUpdatesEnum::Overwrite;
186     }
187     if (dbusValue ==
188         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull")
189     {
190         return metric_report_definition::ReportUpdatesEnum::AppendWrapsWhenFull;
191     }
192     if (dbusValue ==
193         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull")
194     {
195         return metric_report_definition::ReportUpdatesEnum::AppendStopsWhenFull;
196     }
197     return metric_report_definition::ReportUpdatesEnum::Invalid;
198 }
199 
200 inline std::string toDbusReportUpdates(std::string_view redfishValue)
201 {
202     if (redfishValue == "Overwrite")
203     {
204         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite";
205     }
206     if (redfishValue == "AppendWrapsWhenFull")
207     {
208         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull";
209     }
210     if (redfishValue == "AppendStopsWhenFull")
211     {
212         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull";
213     }
214     return "";
215 }
216 
217 inline std::optional<nlohmann::json::array_t> getLinkedTriggers(
218     std::span<const sdbusplus::message::object_path> triggerPaths)
219 {
220     nlohmann::json::array_t triggers;
221 
222     for (const sdbusplus::message::object_path& path : triggerPaths)
223     {
224         if (path.parent_path() !=
225             "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService")
226         {
227             BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}",
228                              path.str);
229             return std::nullopt;
230         }
231 
232         std::string id = path.filename();
233         if (id.empty())
234         {
235             BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}",
236                              path.str);
237             return std::nullopt;
238         }
239         nlohmann::json::object_t trigger;
240         trigger["@odata.id"] =
241             boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id);
242         triggers.emplace_back(std::move(trigger));
243     }
244 
245     return triggers;
246 }
247 
248 inline void
249     fillReportDefinition(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
250                          const std::string& id,
251                          const dbus::utility::DBusPropertiesMap& properties)
252 {
253     std::vector<std::string> reportActions;
254     ReadingParameters readingParams;
255     std::string reportingType;
256     std::string reportUpdates;
257     std::string name;
258     uint64_t appendLimit = 0;
259     uint64_t interval = 0;
260     bool enabled = false;
261     std::vector<sdbusplus::message::object_path> triggers;
262 
263     const bool success = sdbusplus::unpackPropertiesNoThrow(
264         dbus_utils::UnpackErrorPrinter(), properties, "ReportingType",
265         reportingType, "Interval", interval, "ReportActions", reportActions,
266         "ReportUpdates", reportUpdates, "AppendLimit", appendLimit,
267         "ReadingParameters", readingParams, "Name", name, "Enabled", enabled,
268         "Triggers", triggers);
269 
270     if (!success)
271     {
272         messages::internalError(asyncResp->res);
273         return;
274     }
275 
276     metric_report_definition::MetricReportDefinitionType redfishReportingType =
277         toRedfishReportingType(reportingType);
278     if (redfishReportingType ==
279         metric_report_definition::MetricReportDefinitionType::Invalid)
280     {
281         messages::internalError(asyncResp->res);
282         return;
283     }
284 
285     asyncResp->res.jsonValue["MetricReportDefinitionType"] =
286         redfishReportingType;
287 
288     std::optional<nlohmann::json::array_t> linkedTriggers =
289         getLinkedTriggers(triggers);
290     if (!linkedTriggers)
291     {
292         messages::internalError(asyncResp->res);
293         return;
294     }
295 
296     asyncResp->res.jsonValue["Links"]["Triggers"] = std::move(*linkedTriggers);
297 
298     nlohmann::json::array_t redfishReportActions;
299     for (const std::string& action : reportActions)
300     {
301         metric_report_definition::ReportActionsEnum redfishAction =
302             toRedfishReportAction(action);
303         if (redfishAction ==
304             metric_report_definition::ReportActionsEnum::Invalid)
305         {
306             messages::internalError(asyncResp->res);
307             return;
308         }
309 
310         redfishReportActions.emplace_back(redfishAction);
311     }
312 
313     asyncResp->res.jsonValue["ReportActions"] = std::move(redfishReportActions);
314 
315     nlohmann::json::array_t metrics = nlohmann::json::array();
316     for (const auto& [sensorData, collectionFunction, collectionTimeScope,
317                       collectionDuration] : readingParams)
318     {
319         nlohmann::json::array_t metricProperties;
320 
321         for (const auto& [sensorPath, sensorMetadata] : sensorData)
322         {
323             metricProperties.emplace_back(sensorMetadata);
324         }
325 
326         nlohmann::json::object_t metric;
327 
328         metric_report_definition::CalculationAlgorithmEnum
329             redfishCollectionFunction =
330                 telemetry::toRedfishCollectionFunction(collectionFunction);
331         if (redfishCollectionFunction ==
332             metric_report_definition::CalculationAlgorithmEnum::Invalid)
333         {
334             messages::internalError(asyncResp->res);
335             return;
336         }
337         metric["CollectionFunction"] = redfishCollectionFunction;
338 
339         metric_report_definition::CollectionTimeScope
340             redfishCollectionTimeScope =
341                 toRedfishCollectionTimeScope(collectionTimeScope);
342         if (redfishCollectionTimeScope ==
343             metric_report_definition::CollectionTimeScope::Invalid)
344         {
345             messages::internalError(asyncResp->res);
346             return;
347         }
348         metric["CollectionTimeScope"] = redfishCollectionTimeScope;
349 
350         metric["MetricProperties"] = std::move(metricProperties);
351         metric["CollectionDuration"] = time_utils::toDurationString(
352             std::chrono::milliseconds(collectionDuration));
353         metrics.emplace_back(std::move(metric));
354     }
355     asyncResp->res.jsonValue["Metrics"] = std::move(metrics);
356 
357     if (enabled)
358     {
359         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
360     }
361     else
362     {
363         asyncResp->res.jsonValue["Status"]["State"] = "Disabled";
364     }
365 
366     metric_report_definition::ReportUpdatesEnum redfishReportUpdates =
367         toRedfishReportUpdates(reportUpdates);
368     if (redfishReportUpdates ==
369         metric_report_definition::ReportUpdatesEnum::Invalid)
370     {
371         messages::internalError(asyncResp->res);
372         return;
373     }
374     asyncResp->res.jsonValue["ReportUpdates"] = redfishReportUpdates;
375 
376     asyncResp->res.jsonValue["MetricReportDefinitionEnabled"] = enabled;
377     asyncResp->res.jsonValue["AppendLimit"] = appendLimit;
378     asyncResp->res.jsonValue["Name"] = name;
379     asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
380         time_utils::toDurationString(std::chrono::milliseconds(interval));
381     asyncResp->res.jsonValue["@odata.type"] =
382         "#MetricReportDefinition.v1_3_0.MetricReportDefinition";
383     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
384         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", id);
385     asyncResp->res.jsonValue["Id"] = id;
386     asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = boost::urls::format(
387         "/redfish/v1/TelemetryService/MetricReports/{}", id);
388 }
389 
390 struct AddReportArgs
391 {
392     struct MetricArgs
393     {
394         std::vector<std::string> uris;
395         std::string collectionFunction;
396         std::string collectionTimeScope;
397         uint64_t collectionDuration = 0;
398     };
399 
400     std::string id;
401     std::string name;
402     std::string reportingType;
403     std::string reportUpdates;
404     uint64_t appendLimit = std::numeric_limits<uint64_t>::max();
405     std::vector<std::string> reportActions;
406     uint64_t interval = std::numeric_limits<uint64_t>::max();
407     std::vector<MetricArgs> metrics;
408     bool metricReportDefinitionEnabled = true;
409 };
410 
411 inline bool toDbusReportActions(crow::Response& res,
412                                 const std::vector<std::string>& actions,
413                                 std::vector<std::string>& outReportActions)
414 {
415     size_t index = 0;
416     for (const std::string& action : actions)
417     {
418         std::string dbusReportAction = toDbusReportAction(action);
419         if (dbusReportAction.empty())
420         {
421             messages::propertyValueNotInList(
422                 res, action, "ReportActions/" + std::to_string(index));
423             return false;
424         }
425 
426         outReportActions.emplace_back(std::move(dbusReportAction));
427         index++;
428     }
429     return true;
430 }
431 
432 inline bool getUserMetric(crow::Response& res, nlohmann::json::object_t& metric,
433                           AddReportArgs::MetricArgs& metricArgs)
434 {
435     std::optional<std::vector<std::string>> uris;
436     std::optional<std::string> collectionDurationStr;
437     std::optional<std::string> collectionFunction;
438     std::optional<std::string> collectionTimeScopeStr;
439 
440     if (!json_util::readJsonObject(
441             metric, res, "MetricProperties", uris, "CollectionFunction",
442             collectionFunction, "CollectionTimeScope", collectionTimeScopeStr,
443             "CollectionDuration", collectionDurationStr))
444     {
445         return false;
446     }
447 
448     if (uris)
449     {
450         metricArgs.uris = std::move(*uris);
451     }
452 
453     if (collectionFunction)
454     {
455         std::string dbusCollectionFunction =
456             telemetry::toDbusCollectionFunction(*collectionFunction);
457         if (dbusCollectionFunction.empty())
458         {
459             messages::propertyValueIncorrect(res, "CollectionFunction",
460                                              *collectionFunction);
461             return false;
462         }
463         metricArgs.collectionFunction = std::move(dbusCollectionFunction);
464     }
465 
466     if (collectionTimeScopeStr)
467     {
468         std::string dbusCollectionTimeScope =
469             toDbusCollectionTimeScope(*collectionTimeScopeStr);
470         if (dbusCollectionTimeScope.empty())
471         {
472             messages::propertyValueIncorrect(res, "CollectionTimeScope",
473                                              *collectionTimeScopeStr);
474             return false;
475         }
476         metricArgs.collectionTimeScope = std::move(dbusCollectionTimeScope);
477     }
478 
479     if (collectionDurationStr)
480     {
481         std::optional<std::chrono::milliseconds> duration =
482             time_utils::fromDurationString(*collectionDurationStr);
483 
484         if (!duration || duration->count() < 0)
485         {
486             messages::propertyValueIncorrect(res, "CollectionDuration",
487                                              *collectionDurationStr);
488             return false;
489         }
490 
491         metricArgs.collectionDuration =
492             static_cast<uint64_t>(duration->count());
493     }
494 
495     return true;
496 }
497 
498 inline bool getUserMetrics(crow::Response& res,
499                            std::span<nlohmann::json::object_t> metrics,
500                            std::vector<AddReportArgs::MetricArgs>& result)
501 {
502     result.reserve(metrics.size());
503 
504     for (nlohmann::json::object_t& m : metrics)
505     {
506         AddReportArgs::MetricArgs metricArgs;
507 
508         if (!getUserMetric(res, m, metricArgs))
509         {
510             return false;
511         }
512 
513         result.emplace_back(std::move(metricArgs));
514     }
515 
516     return true;
517 }
518 
519 inline bool getUserParameters(crow::Response& res, const crow::Request& req,
520                               AddReportArgs& args)
521 {
522     std::optional<std::string> id;
523     std::optional<std::string> name;
524     std::optional<std::string> reportingTypeStr;
525     std::optional<std::string> reportUpdatesStr;
526     std::optional<uint64_t> appendLimit;
527     std::optional<bool> metricReportDefinitionEnabled;
528     std::optional<std::vector<nlohmann::json::object_t>> metrics;
529     std::optional<std::vector<std::string>> reportActionsStr;
530     std::optional<std::string> scheduleDurationStr;
531 
532     if (!json_util::readJsonPatch(
533             req, res, "Id", id, "Name", name, "Metrics", metrics,
534             "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates",
535             reportUpdatesStr, "AppendLimit", appendLimit, "ReportActions",
536             reportActionsStr, "Schedule/RecurrenceInterval",
537             scheduleDurationStr, "MetricReportDefinitionEnabled",
538             metricReportDefinitionEnabled))
539     {
540         return false;
541     }
542 
543     if (id)
544     {
545         constexpr const char* allowedCharactersInId =
546             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
547         if (id->empty() ||
548             id->find_first_not_of(allowedCharactersInId) != std::string::npos)
549         {
550             messages::propertyValueIncorrect(res, "Id", *id);
551             return false;
552         }
553         args.id = *id;
554     }
555 
556     if (name)
557     {
558         args.name = *name;
559     }
560 
561     if (reportingTypeStr)
562     {
563         std::string dbusReportingType = toDbusReportingType(*reportingTypeStr);
564         if (dbusReportingType.empty())
565         {
566             messages::propertyValueNotInList(res, *reportingTypeStr,
567                                              "MetricReportDefinitionType");
568             return false;
569         }
570         args.reportingType = dbusReportingType;
571     }
572 
573     if (reportUpdatesStr)
574     {
575         std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr);
576         if (dbusReportUpdates.empty())
577         {
578             messages::propertyValueNotInList(res, *reportUpdatesStr,
579                                              "ReportUpdates");
580             return false;
581         }
582         args.reportUpdates = dbusReportUpdates;
583     }
584 
585     if (appendLimit)
586     {
587         args.appendLimit = *appendLimit;
588     }
589 
590     if (metricReportDefinitionEnabled)
591     {
592         args.metricReportDefinitionEnabled = *metricReportDefinitionEnabled;
593     }
594 
595     if (reportActionsStr)
596     {
597         if (!toDbusReportActions(res, *reportActionsStr, args.reportActions))
598         {
599             return false;
600         }
601     }
602 
603     if (reportingTypeStr == "Periodic")
604     {
605         if (!scheduleDurationStr)
606         {
607             messages::createFailedMissingReqProperties(res, "Schedule");
608             return false;
609         }
610 
611         std::optional<std::chrono::milliseconds> durationNum =
612             time_utils::fromDurationString(*scheduleDurationStr);
613         if (!durationNum || durationNum->count() < 0)
614         {
615             messages::propertyValueIncorrect(res, "RecurrenceInterval",
616                                              *scheduleDurationStr);
617             return false;
618         }
619         args.interval = static_cast<uint64_t>(durationNum->count());
620     }
621 
622     if (metrics)
623     {
624         if (!getUserMetrics(res, *metrics, args.metrics))
625         {
626             return false;
627         }
628     }
629 
630     return true;
631 }
632 
633 inline bool getChassisSensorNodeFromMetrics(
634     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
635     std::span<const AddReportArgs::MetricArgs> metrics,
636     boost::container::flat_set<std::pair<std::string, std::string>>& matched)
637 {
638     for (const auto& metric : metrics)
639     {
640         std::optional<IncorrectMetricUri> error =
641             getChassisSensorNode(metric.uris, matched);
642         if (error)
643         {
644             messages::propertyValueIncorrect(asyncResp->res, error->uri,
645                                              "MetricProperties/" +
646                                                  std::to_string(error->index));
647             return false;
648         }
649     }
650     return true;
651 }
652 
653 class AddReport
654 {
655   public:
656     AddReport(AddReportArgs&& argsIn,
657               const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
658         asyncResp(asyncRespIn),
659         args(std::move(argsIn))
660     {}
661 
662     ~AddReport()
663     {
664         boost::asio::post(crow::connections::systemBus->get_io_context(),
665                           std::bind_front(&performAddReport, asyncResp, args,
666                                           std::move(uriToDbus)));
667     }
668 
669     static void performAddReport(
670         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
671         const AddReportArgs& args,
672         const boost::container::flat_map<std::string, std::string>& uriToDbus)
673     {
674         if (asyncResp->res.result() != boost::beast::http::status::ok)
675         {
676             return;
677         }
678 
679         telemetry::ReadingParameters readingParams;
680         readingParams.reserve(args.metrics.size());
681 
682         for (const auto& metric : args.metrics)
683         {
684             std::vector<
685                 std::tuple<sdbusplus::message::object_path, std::string>>
686                 sensorParams;
687             sensorParams.reserve(metric.uris.size());
688 
689             for (size_t i = 0; i < metric.uris.size(); i++)
690             {
691                 const std::string& uri = metric.uris[i];
692                 auto el = uriToDbus.find(uri);
693                 if (el == uriToDbus.end())
694                 {
695                     BMCWEB_LOG_ERROR(
696                         "Failed to find DBus sensor corresponding to URI {}",
697                         uri);
698                     messages::propertyValueNotInList(asyncResp->res, uri,
699                                                      "MetricProperties/" +
700                                                          std::to_string(i));
701                     return;
702                 }
703 
704                 const std::string& dbusPath = el->second;
705                 sensorParams.emplace_back(dbusPath, uri);
706             }
707 
708             readingParams.emplace_back(
709                 std::move(sensorParams), metric.collectionFunction,
710                 metric.collectionTimeScope, metric.collectionDuration);
711         }
712 
713         crow::connections::systemBus->async_method_call(
714             [asyncResp, id = args.id, uriToDbus](
715                 const boost::system::error_code& ec, const std::string&) {
716             if (ec == boost::system::errc::file_exists)
717             {
718                 messages::resourceAlreadyExists(
719                     asyncResp->res, "MetricReportDefinition", "Id", id);
720                 return;
721             }
722             if (ec == boost::system::errc::too_many_files_open)
723             {
724                 messages::createLimitReachedForResource(asyncResp->res);
725                 return;
726             }
727             if (ec == boost::system::errc::argument_list_too_long)
728             {
729                 nlohmann::json metricProperties = nlohmann::json::array();
730                 for (const auto& [uri, _] : uriToDbus)
731                 {
732                     metricProperties.emplace_back(uri);
733                 }
734                 messages::propertyValueIncorrect(
735                     asyncResp->res, "MetricProperties", metricProperties);
736                 return;
737             }
738             if (ec)
739             {
740                 messages::internalError(asyncResp->res);
741                 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
742                 return;
743             }
744 
745             messages::created(asyncResp->res);
746         },
747             telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
748             "xyz.openbmc_project.Telemetry.ReportManager", "AddReport",
749             "TelemetryService/" + args.id, args.name, args.reportingType,
750             args.reportUpdates, args.appendLimit, args.reportActions,
751             args.interval, readingParams, args.metricReportDefinitionEnabled);
752     }
753 
754     AddReport(const AddReport&) = delete;
755     AddReport(AddReport&&) = delete;
756     AddReport& operator=(const AddReport&) = delete;
757     AddReport& operator=(AddReport&&) = delete;
758 
759     void insert(const std::map<std::string, std::string>& el)
760     {
761         uriToDbus.insert(el.begin(), el.end());
762     }
763 
764   private:
765     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
766     AddReportArgs args;
767     boost::container::flat_map<std::string, std::string> uriToDbus;
768 };
769 
770 class UpdateMetrics
771 {
772   public:
773     UpdateMetrics(std::string_view idIn,
774                   const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
775         id(idIn),
776         asyncResp(asyncRespIn)
777     {}
778 
779     ~UpdateMetrics()
780     {
781         try
782         {
783             setReadingParams();
784         }
785         catch (const std::exception& e)
786         {
787             BMCWEB_LOG_ERROR("{}", e.what());
788         }
789         catch (...)
790         {
791             BMCWEB_LOG_ERROR("Unknown error");
792         }
793     }
794 
795     UpdateMetrics(const UpdateMetrics&) = delete;
796     UpdateMetrics(UpdateMetrics&&) = delete;
797     UpdateMetrics& operator=(const UpdateMetrics&) = delete;
798     UpdateMetrics& operator=(UpdateMetrics&&) = delete;
799 
800     std::string id;
801     std::map<std::string, std::string> metricPropertyToDbusPaths;
802 
803     void insert(const std::map<std::string, std::string>&
804                     additionalMetricPropertyToDbusPaths)
805     {
806         metricPropertyToDbusPaths.insert(
807             additionalMetricPropertyToDbusPaths.begin(),
808             additionalMetricPropertyToDbusPaths.end());
809     }
810 
811     void emplace(std::span<const std::tuple<sdbusplus::message::object_path,
812                                             std::string>>
813                      pathAndUri,
814                  const AddReportArgs::MetricArgs& metricArgs)
815     {
816         readingParamsUris.emplace_back(metricArgs.uris);
817         readingParams.emplace_back(
818             std::vector(pathAndUri.begin(), pathAndUri.end()),
819             metricArgs.collectionFunction, metricArgs.collectionTimeScope,
820             metricArgs.collectionDuration);
821     }
822 
823     void setReadingParams()
824     {
825         if (asyncResp->res.result() != boost::beast::http::status::ok)
826         {
827             return;
828         }
829 
830         for (size_t index = 0; index < readingParamsUris.size(); ++index)
831         {
832             std::span<const std::string> newUris = readingParamsUris[index];
833 
834             const std::optional<std::vector<
835                 std::tuple<sdbusplus::message::object_path, std::string>>>
836                 readingParam = sensorPathToUri(newUris);
837 
838             if (!readingParam)
839             {
840                 return;
841             }
842 
843             std::get<0>(readingParams[index]) = *readingParam;
844         }
845 
846         crow::connections::systemBus->async_method_call(
847             [asyncResp(this->asyncResp),
848              reportId = id](const boost::system::error_code& ec) {
849             if (!verifyCommonErrors(asyncResp->res, reportId, ec))
850             {
851                 return;
852             }
853         },
854             "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
855             "org.freedesktop.DBus.Properties", "Set",
856             "xyz.openbmc_project.Telemetry.Report", "ReadingParameters",
857             dbus::utility::DbusVariantType{readingParams});
858     }
859 
860   private:
861     std::optional<
862         std::vector<std::tuple<sdbusplus::message::object_path, std::string>>>
863         sensorPathToUri(std::span<const std::string> uris) const
864     {
865         std::vector<std::tuple<sdbusplus::message::object_path, std::string>>
866             result;
867 
868         for (const std::string& uri : uris)
869         {
870             auto it = metricPropertyToDbusPaths.find(uri);
871             if (it == metricPropertyToDbusPaths.end())
872             {
873                 messages::propertyValueNotInList(asyncResp->res, uri,
874                                                  "MetricProperties");
875                 return {};
876             }
877             result.emplace_back(it->second, uri);
878         }
879 
880         return result;
881     }
882 
883     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
884     std::vector<std::vector<std::string>> readingParamsUris;
885     ReadingParameters readingParams;
886 };
887 
888 inline void
889     setReportEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
890                      std::string_view id, bool enabled)
891 {
892     crow::connections::systemBus->async_method_call(
893         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
894         if (!verifyCommonErrors(asyncResp->res, id, ec))
895         {
896             return;
897         }
898     },
899         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
900         "org.freedesktop.DBus.Properties", "Set",
901         "xyz.openbmc_project.Telemetry.Report", "Enabled",
902         dbus::utility::DbusVariantType{enabled});
903 }
904 
905 inline void setReportTypeAndInterval(
906     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id,
907     const std::string& reportingType, uint64_t recurrenceInterval)
908 {
909     crow::connections::systemBus->async_method_call(
910         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
911         if (!verifyCommonErrors(asyncResp->res, id, ec))
912         {
913             return;
914         }
915     },
916         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
917         "xyz.openbmc_project.Telemetry.Report", "SetReportingProperties",
918         reportingType, recurrenceInterval);
919 }
920 
921 inline void
922     setReportUpdates(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
923                      std::string_view id, const std::string& reportUpdates)
924 {
925     crow::connections::systemBus->async_method_call(
926         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
927         if (!verifyCommonErrors(asyncResp->res, id, ec))
928         {
929             return;
930         }
931     },
932         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
933         "org.freedesktop.DBus.Properties", "Set",
934         "xyz.openbmc_project.Telemetry.Report", "ReportUpdates",
935         dbus::utility::DbusVariantType{reportUpdates});
936 }
937 
938 inline void
939     setReportActions(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
940                      std::string_view id,
941                      const std::vector<std::string>& dbusReportActions)
942 {
943     crow::connections::systemBus->async_method_call(
944         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
945         if (!verifyCommonErrors(asyncResp->res, id, ec))
946         {
947             return;
948         }
949     },
950         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
951         "org.freedesktop.DBus.Properties", "Set",
952         "xyz.openbmc_project.Telemetry.Report", "ReportActions",
953         dbus::utility::DbusVariantType{dbusReportActions});
954 }
955 
956 inline void
957     setReportMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
958                      std::string_view id,
959                      std::span<nlohmann::json::object_t> metrics)
960 {
961     sdbusplus::asio::getAllProperties(
962         *crow::connections::systemBus, telemetry::service,
963         telemetry::getDbusReportPath(id), telemetry::reportInterface,
964         [asyncResp, id = std::string(id),
965          redfishMetrics = std::vector<nlohmann::json::object_t>(metrics.begin(),
966                                                                 metrics.end())](
967             boost::system::error_code ec,
968             const dbus::utility::DBusPropertiesMap& properties) mutable {
969         if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec))
970         {
971             return;
972         }
973 
974         ReadingParameters readingParams;
975 
976         const bool success = sdbusplus::unpackPropertiesNoThrow(
977             dbus_utils::UnpackErrorPrinter(), properties, "ReadingParameters",
978             readingParams);
979 
980         if (!success)
981         {
982             messages::internalError(asyncResp->res);
983             return;
984         }
985 
986         auto updateMetricsReq = std::make_shared<UpdateMetrics>(id, asyncResp);
987 
988         boost::container::flat_set<std::pair<std::string, std::string>>
989             chassisSensors;
990 
991         size_t index = 0;
992         for (nlohmann::json::object_t& metric : redfishMetrics)
993         {
994             AddReportArgs::MetricArgs metricArgs;
995             std::vector<
996                 std::tuple<sdbusplus::message::object_path, std::string>>
997                 pathAndUri;
998 
999             if (index < readingParams.size())
1000             {
1001                 const ReadingParameters::value_type& existing =
1002                     readingParams[index];
1003 
1004                 pathAndUri = std::get<0>(existing);
1005                 metricArgs.collectionFunction = std::get<1>(existing);
1006                 metricArgs.collectionTimeScope = std::get<2>(existing);
1007                 metricArgs.collectionDuration = std::get<3>(existing);
1008             }
1009 
1010             if (!getUserMetric(asyncResp->res, metric, metricArgs))
1011             {
1012                 return;
1013             }
1014 
1015             std::optional<IncorrectMetricUri> error =
1016                 getChassisSensorNode(metricArgs.uris, chassisSensors);
1017 
1018             if (error)
1019             {
1020                 messages::propertyValueIncorrect(
1021                     asyncResp->res, error->uri,
1022                     "MetricProperties/" + std::to_string(error->index));
1023                 return;
1024             }
1025 
1026             updateMetricsReq->emplace(pathAndUri, metricArgs);
1027             index++;
1028         }
1029 
1030         for (const auto& [chassis, sensorType] : chassisSensors)
1031         {
1032             retrieveUriToDbusMap(
1033                 chassis, sensorType,
1034                 [asyncResp, updateMetricsReq](
1035                     const boost::beast::http::status status,
1036                     const std::map<std::string, std::string>& uriToDbus) {
1037                 if (status != boost::beast::http::status::ok)
1038                 {
1039                     BMCWEB_LOG_ERROR(
1040                         "Failed to retrieve URI to dbus sensors map with err {}",
1041                         static_cast<unsigned>(status));
1042                     return;
1043                 }
1044                 updateMetricsReq->insert(uriToDbus);
1045             });
1046         }
1047     });
1048 }
1049 
1050 inline void handleMetricReportDefinitionCollectionHead(
1051     App& app, const crow::Request& req,
1052     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1053 {
1054     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1055     {
1056         return;
1057     }
1058     asyncResp->res.addHeader(
1059         boost::beast::http::field::link,
1060         "</redfish/v1/JsonSchemas/MetricReportDefinitionCollection/MetricReportDefinitionCollection.json>; rel=describedby");
1061 }
1062 
1063 inline void handleMetricReportDefinitionCollectionGet(
1064     App& app, const crow::Request& req,
1065     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1066 {
1067     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1068     {
1069         return;
1070     }
1071     asyncResp->res.addHeader(
1072         boost::beast::http::field::link,
1073         "</redfish/v1/JsonSchemas/MetricReportDefinition/MetricReportDefinition.json>; rel=describedby");
1074 
1075     asyncResp->res.jsonValue["@odata.type"] =
1076         "#MetricReportDefinitionCollection."
1077         "MetricReportDefinitionCollection";
1078     asyncResp->res.jsonValue["@odata.id"] =
1079         "/redfish/v1/TelemetryService/MetricReportDefinitions";
1080     asyncResp->res.jsonValue["Name"] = "Metric Definition Collection";
1081     constexpr std::array<std::string_view, 1> interfaces{
1082         telemetry::reportInterface};
1083     collection_util::getCollectionMembers(
1084         asyncResp,
1085         boost::urls::url(
1086             "/redfish/v1/TelemetryService/MetricReportDefinitions"),
1087         interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService");
1088 }
1089 
1090 inline void
1091     handleReportPatch(App& app, const crow::Request& req,
1092                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1093                       std::string_view id)
1094 {
1095     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1096     {
1097         return;
1098     }
1099 
1100     std::optional<std::string> reportingTypeStr;
1101     std::optional<std::string> reportUpdatesStr;
1102     std::optional<bool> metricReportDefinitionEnabled;
1103     std::optional<std::vector<nlohmann::json::object_t>> metrics;
1104     std::optional<std::vector<std::string>> reportActionsStr;
1105     std::optional<std::string> scheduleDurationStr;
1106 
1107     if (!json_util::readJsonPatch(
1108             req, asyncResp->res, "Metrics", metrics,
1109             "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates",
1110             reportUpdatesStr, "ReportActions", reportActionsStr,
1111             "Schedule/RecurrenceInterval", scheduleDurationStr,
1112             "MetricReportDefinitionEnabled", metricReportDefinitionEnabled))
1113     {
1114         return;
1115     }
1116 
1117     if (metricReportDefinitionEnabled)
1118     {
1119         setReportEnabled(asyncResp, id, *metricReportDefinitionEnabled);
1120     }
1121 
1122     if (reportUpdatesStr)
1123     {
1124         std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr);
1125         if (dbusReportUpdates.empty())
1126         {
1127             messages::propertyValueNotInList(asyncResp->res, *reportUpdatesStr,
1128                                              "ReportUpdates");
1129             return;
1130         }
1131         setReportUpdates(asyncResp, id, dbusReportUpdates);
1132     }
1133 
1134     if (reportActionsStr)
1135     {
1136         std::vector<std::string> dbusReportActions;
1137         if (!toDbusReportActions(asyncResp->res, *reportActionsStr,
1138                                  dbusReportActions))
1139         {
1140             return;
1141         }
1142         setReportActions(asyncResp, id, dbusReportActions);
1143     }
1144 
1145     if (reportingTypeStr || scheduleDurationStr)
1146     {
1147         std::string dbusReportingType;
1148         if (reportingTypeStr)
1149         {
1150             dbusReportingType = toDbusReportingType(*reportingTypeStr);
1151             if (dbusReportingType.empty())
1152             {
1153                 messages::propertyValueNotInList(asyncResp->res,
1154                                                  *reportingTypeStr,
1155                                                  "MetricReportDefinitionType");
1156                 return;
1157             }
1158         }
1159 
1160         uint64_t recurrenceInterval = std::numeric_limits<uint64_t>::max();
1161         if (scheduleDurationStr)
1162         {
1163             std::optional<std::chrono::milliseconds> durationNum =
1164                 time_utils::fromDurationString(*scheduleDurationStr);
1165             if (!durationNum || durationNum->count() < 0)
1166             {
1167                 messages::propertyValueIncorrect(
1168                     asyncResp->res, "RecurrenceInterval", *scheduleDurationStr);
1169                 return;
1170             }
1171 
1172             recurrenceInterval = static_cast<uint64_t>(durationNum->count());
1173         }
1174 
1175         setReportTypeAndInterval(asyncResp, id, dbusReportingType,
1176                                  recurrenceInterval);
1177     }
1178 
1179     if (metrics)
1180     {
1181         setReportMetrics(asyncResp, id, *metrics);
1182     }
1183 }
1184 
1185 inline void
1186     handleReportDelete(App& app, const crow::Request& req,
1187                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1188                        std::string_view id)
1189 {
1190     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1191     {
1192         return;
1193     }
1194 
1195     const std::string reportPath = getDbusReportPath(id);
1196 
1197     crow::connections::systemBus->async_method_call(
1198         [asyncResp,
1199          reportId = std::string(id)](const boost::system::error_code& ec) {
1200         if (!verifyCommonErrors(asyncResp->res, reportId, ec))
1201         {
1202             return;
1203         }
1204         asyncResp->res.result(boost::beast::http::status::no_content);
1205     },
1206         service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete");
1207 }
1208 } // namespace telemetry
1209 
1210 inline void afterRetrieveUriToDbusMap(
1211     const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/,
1212     const std::shared_ptr<telemetry::AddReport>& addReportReq,
1213     const boost::beast::http::status status,
1214     const std::map<std::string, std::string>& uriToDbus)
1215 {
1216     if (status != boost::beast::http::status::ok)
1217     {
1218         BMCWEB_LOG_ERROR(
1219             "Failed to retrieve URI to dbus sensors map with err {}",
1220             static_cast<unsigned>(status));
1221         return;
1222     }
1223     addReportReq->insert(uriToDbus);
1224 }
1225 
1226 inline void handleMetricReportDefinitionsPost(
1227     App& app, const crow::Request& req,
1228     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1229 {
1230     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1231     {
1232         return;
1233     }
1234 
1235     telemetry::AddReportArgs args;
1236     if (!telemetry::getUserParameters(asyncResp->res, req, args))
1237     {
1238         return;
1239     }
1240 
1241     boost::container::flat_set<std::pair<std::string, std::string>>
1242         chassisSensors;
1243     if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics,
1244                                                     chassisSensors))
1245     {
1246         return;
1247     }
1248 
1249     auto addReportReq = std::make_shared<telemetry::AddReport>(std::move(args),
1250                                                                asyncResp);
1251     for (const auto& [chassis, sensorType] : chassisSensors)
1252     {
1253         retrieveUriToDbusMap(chassis, sensorType,
1254                              std::bind_front(afterRetrieveUriToDbusMap,
1255                                              asyncResp, addReportReq));
1256     }
1257 }
1258 
1259 inline void
1260     handleMetricReportHead(App& app, const crow::Request& req,
1261                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1262                            const std::string& /*id*/)
1263 {
1264     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1265     {
1266         return;
1267     }
1268     asyncResp->res.addHeader(
1269         boost::beast::http::field::link,
1270         "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby");
1271 }
1272 
1273 inline void
1274     handleMetricReportGet(App& app, const crow::Request& req,
1275                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1276                           const std::string& id)
1277 {
1278     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1279     {
1280         return;
1281     }
1282     asyncResp->res.addHeader(
1283         boost::beast::http::field::link,
1284         "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby");
1285 
1286     sdbusplus::asio::getAllProperties(
1287         *crow::connections::systemBus, telemetry::service,
1288         telemetry::getDbusReportPath(id), telemetry::reportInterface,
1289         [asyncResp, id](const boost::system::error_code& ec,
1290                         const dbus::utility::DBusPropertiesMap& properties) {
1291         if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec))
1292         {
1293             return;
1294         }
1295 
1296         telemetry::fillReportDefinition(asyncResp, id, properties);
1297     });
1298 }
1299 
1300 inline void handleMetricReportDelete(
1301     App& app, const crow::Request& req,
1302     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1303 
1304 {
1305     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1306     {
1307         return;
1308     }
1309 
1310     const std::string reportPath = telemetry::getDbusReportPath(id);
1311 
1312     crow::connections::systemBus->async_method_call(
1313         [asyncResp, id](const boost::system::error_code& ec) {
1314         /*
1315          * boost::system::errc and std::errc are missing value
1316          * for EBADR error that is defined in Linux.
1317          */
1318         if (ec.value() == EBADR)
1319         {
1320             messages::resourceNotFound(asyncResp->res, "MetricReportDefinition",
1321                                        id);
1322             return;
1323         }
1324 
1325         if (ec)
1326         {
1327             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1328             messages::internalError(asyncResp->res);
1329             return;
1330         }
1331 
1332         asyncResp->res.result(boost::beast::http::status::no_content);
1333     },
1334         telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete",
1335         "Delete");
1336 }
1337 
1338 inline void requestRoutesMetricReportDefinitionCollection(App& app)
1339 {
1340     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1341         .privileges(redfish::privileges::headMetricReportDefinitionCollection)
1342         .methods(boost::beast::http::verb::head)(std::bind_front(
1343             telemetry::handleMetricReportDefinitionCollectionHead,
1344             std::ref(app)));
1345 
1346     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1347         .privileges(redfish::privileges::getMetricReportDefinitionCollection)
1348         .methods(boost::beast::http::verb::get)(std::bind_front(
1349             telemetry::handleMetricReportDefinitionCollectionGet,
1350             std::ref(app)));
1351 
1352     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1353         .privileges(redfish::privileges::postMetricReportDefinitionCollection)
1354         .methods(boost::beast::http::verb::post)(
1355             std::bind_front(handleMetricReportDefinitionsPost, std::ref(app)));
1356 }
1357 
1358 inline void requestRoutesMetricReportDefinition(App& app)
1359 {
1360     BMCWEB_ROUTE(app,
1361                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1362         .privileges(redfish::privileges::getMetricReportDefinition)
1363         .methods(boost::beast::http::verb::head)(
1364             std::bind_front(handleMetricReportHead, std::ref(app)));
1365 
1366     BMCWEB_ROUTE(app,
1367                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1368         .privileges(redfish::privileges::getMetricReportDefinition)
1369         .methods(boost::beast::http::verb::get)(
1370             std::bind_front(handleMetricReportGet, std::ref(app)));
1371 
1372     BMCWEB_ROUTE(app,
1373                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1374         .privileges(redfish::privileges::deleteMetricReportDefinition)
1375         .methods(boost::beast::http::verb::delete_)(
1376             std::bind_front(handleMetricReportDelete, std::ref(app)));
1377 
1378     BMCWEB_ROUTE(app,
1379                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1380         .privileges(redfish::privileges::patchMetricReportDefinition)
1381         .methods(boost::beast::http::verb::patch)(
1382             std::bind_front(telemetry::handleReportPatch, std::ref(app)));
1383 }
1384 } // namespace redfish
1385