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