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