1 #pragma once
2 
3 #include "sensors.hpp"
4 #include "utils/telemetry_utils.hpp"
5 #include "utils/time_utils.hpp"
6 
7 #include <app.hpp>
8 #include <boost/container/flat_map.hpp>
9 #include <dbus_utility.hpp>
10 #include <query.hpp>
11 #include <registries/privilege_registry.hpp>
12 
13 #include <tuple>
14 #include <variant>
15 
16 namespace redfish
17 {
18 
19 namespace telemetry
20 {
21 
22 constexpr const char* metricReportDefinitionUri =
23     "/redfish/v1/TelemetryService/MetricReportDefinitions";
24 
25 using ReadingParameters =
26     std::vector<std::tuple<sdbusplus::message::object_path, std::string,
27                            std::string, std::string>>;
28 
29 inline void
30     fillReportDefinition(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
31                          const std::string& id,
32                          const dbus::utility::DBusPropertiesMap& ret)
33 {
34     asyncResp->res.jsonValue["@odata.type"] =
35         "#MetricReportDefinition.v1_3_0.MetricReportDefinition";
36     asyncResp->res.jsonValue["@odata.id"] =
37         crow::utility::urlFromPieces("redfish", "v1", "TelemetryService",
38                                      "MetricReportDefinitions", id)
39             .string();
40     asyncResp->res.jsonValue["Id"] = id;
41     asyncResp->res.jsonValue["Name"] = id;
42     asyncResp->res.jsonValue["MetricReport"]["@odata.id"] =
43         crow::utility::urlFromPieces("redfish", "v1", "TelemetryService",
44                                      "MetricReports", id)
45             .string();
46     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
47     asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite";
48 
49     const bool* emitsReadingsUpdate = nullptr;
50     const bool* logToMetricReportsCollection = nullptr;
51     const ReadingParameters* readingParams = nullptr;
52     const std::string* reportingType = nullptr;
53     const uint64_t* interval = nullptr;
54     for (const auto& [key, var] : ret)
55     {
56         if (key == "EmitsReadingsUpdate")
57         {
58             emitsReadingsUpdate = std::get_if<bool>(&var);
59         }
60         else if (key == "LogToMetricReportsCollection")
61         {
62             logToMetricReportsCollection = std::get_if<bool>(&var);
63         }
64         else if (key == "ReadingParameters")
65         {
66             readingParams = std::get_if<ReadingParameters>(&var);
67         }
68         else if (key == "ReportingType")
69         {
70             reportingType = std::get_if<std::string>(&var);
71         }
72         else if (key == "Interval")
73         {
74             interval = std::get_if<uint64_t>(&var);
75         }
76     }
77     if (emitsReadingsUpdate == nullptr ||
78         logToMetricReportsCollection == nullptr || readingParams == nullptr ||
79         reportingType == nullptr || interval == nullptr)
80     {
81         BMCWEB_LOG_ERROR << "Property type mismatch or property is missing";
82         messages::internalError(asyncResp->res);
83         return;
84     }
85 
86     std::vector<std::string> redfishReportActions;
87     redfishReportActions.reserve(2);
88     if (*emitsReadingsUpdate)
89     {
90         redfishReportActions.emplace_back("RedfishEvent");
91     }
92     if (*logToMetricReportsCollection)
93     {
94         redfishReportActions.emplace_back("LogToMetricReportsCollection");
95     }
96 
97     nlohmann::json metrics = nlohmann::json::array();
98     for (const auto& [sensorPath, operationType, id, metadata] : *readingParams)
99     {
100         metrics.push_back({
101             {"MetricId", id},
102             {"MetricProperties", {metadata}},
103         });
104     }
105     asyncResp->res.jsonValue["Metrics"] = metrics;
106     asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType;
107     asyncResp->res.jsonValue["ReportActions"] = redfishReportActions;
108     asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
109         time_utils::toDurationString(std::chrono::milliseconds(*interval));
110 }
111 
112 struct AddReportArgs
113 {
114     std::string name;
115     std::string reportingType;
116     bool emitsReadingsUpdate = false;
117     bool logToMetricReportsCollection = false;
118     uint64_t interval = 0;
119     std::vector<std::pair<std::string, std::vector<std::string>>> metrics;
120 };
121 
122 inline bool toDbusReportActions(crow::Response& res,
123                                 std::vector<std::string>& actions,
124                                 AddReportArgs& args)
125 {
126     size_t index = 0;
127     for (auto& action : actions)
128     {
129         if (action == "RedfishEvent")
130         {
131             args.emitsReadingsUpdate = true;
132         }
133         else if (action == "LogToMetricReportsCollection")
134         {
135             args.logToMetricReportsCollection = true;
136         }
137         else
138         {
139             messages::propertyValueNotInList(
140                 res, action, "ReportActions/" + std::to_string(index));
141             return false;
142         }
143         index++;
144     }
145     return true;
146 }
147 
148 inline bool getUserParameters(crow::Response& res, const crow::Request& req,
149                               AddReportArgs& args)
150 {
151     std::vector<nlohmann::json> metrics;
152     std::vector<std::string> reportActions;
153     std::optional<nlohmann::json> schedule;
154     if (!json_util::readJsonPatch(req, res, "Id", args.name, "Metrics", metrics,
155                                   "MetricReportDefinitionType",
156                                   args.reportingType, "ReportActions",
157                                   reportActions, "Schedule", schedule))
158     {
159         return false;
160     }
161 
162     constexpr const char* allowedCharactersInName =
163         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
164     if (args.name.empty() || args.name.find_first_not_of(
165                                  allowedCharactersInName) != std::string::npos)
166     {
167         BMCWEB_LOG_ERROR << "Failed to match " << args.name
168                          << " with allowed character "
169                          << allowedCharactersInName;
170         messages::propertyValueIncorrect(res, "Id", args.name);
171         return false;
172     }
173 
174     if (args.reportingType != "Periodic" && args.reportingType != "OnRequest")
175     {
176         messages::propertyValueNotInList(res, args.reportingType,
177                                          "MetricReportDefinitionType");
178         return false;
179     }
180 
181     if (!toDbusReportActions(res, reportActions, args))
182     {
183         return false;
184     }
185 
186     if (args.reportingType == "Periodic")
187     {
188         if (!schedule)
189         {
190             messages::createFailedMissingReqProperties(res, "Schedule");
191             return false;
192         }
193 
194         std::string durationStr;
195         if (!json_util::readJson(*schedule, res, "RecurrenceInterval",
196                                  durationStr))
197         {
198             return false;
199         }
200 
201         std::optional<std::chrono::milliseconds> durationNum =
202             time_utils::fromDurationString(durationStr);
203         if (!durationNum)
204         {
205             messages::propertyValueIncorrect(res, "RecurrenceInterval",
206                                              durationStr);
207             return false;
208         }
209         args.interval = static_cast<uint64_t>(durationNum->count());
210     }
211 
212     args.metrics.reserve(metrics.size());
213     for (auto& m : metrics)
214     {
215         std::string id;
216         std::vector<std::string> uris;
217         if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties",
218                                  uris))
219         {
220             return false;
221         }
222 
223         args.metrics.emplace_back(std::move(id), std::move(uris));
224     }
225 
226     return true;
227 }
228 
229 inline bool getChassisSensorNodeFromMetrics(
230     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
231     const std::vector<std::pair<std::string, std::vector<std::string>>>&
232         metrics,
233     boost::container::flat_set<std::pair<std::string, std::string>>& matched)
234 {
235     for (const auto& metric : metrics)
236     {
237         const std::vector<std::string>& uris = metric.second;
238 
239         std::optional<IncorrectMetricUri> error =
240             getChassisSensorNode(uris, matched);
241         if (error)
242         {
243             messages::propertyValueIncorrect(asyncResp->res, error->uri,
244                                              "MetricProperties/" +
245                                                  std::to_string(error->index));
246             return false;
247         }
248     }
249     return true;
250 }
251 
252 class AddReport
253 {
254   public:
255     AddReport(AddReportArgs argsIn,
256               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) :
257         asyncResp(asyncResp),
258         args{std::move(argsIn)}
259     {}
260     ~AddReport()
261     {
262         if (asyncResp->res.result() != boost::beast::http::status::ok)
263         {
264             return;
265         }
266 
267         telemetry::ReadingParameters readingParams;
268         readingParams.reserve(args.metrics.size());
269 
270         for (const auto& [id, uris] : args.metrics)
271         {
272             for (size_t i = 0; i < uris.size(); i++)
273             {
274                 const std::string& uri = uris[i];
275                 auto el = uriToDbus.find(uri);
276                 if (el == uriToDbus.end())
277                 {
278                     BMCWEB_LOG_ERROR
279                         << "Failed to find DBus sensor corresponding to URI "
280                         << uri;
281                     messages::propertyValueNotInList(asyncResp->res, uri,
282                                                      "MetricProperties/" +
283                                                          std::to_string(i));
284                     return;
285                 }
286 
287                 const std::string& dbusPath = el->second;
288                 readingParams.emplace_back(dbusPath, "SINGLE", id, uri);
289             }
290         }
291         const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp;
292         crow::connections::systemBus->async_method_call(
293             [aResp, name = args.name, uriToDbus = std::move(uriToDbus)](
294                 const boost::system::error_code ec, const std::string&) {
295                 if (ec == boost::system::errc::file_exists)
296                 {
297                     messages::resourceAlreadyExists(
298                         aResp->res, "MetricReportDefinition", "Id", name);
299                     return;
300                 }
301                 if (ec == boost::system::errc::too_many_files_open)
302                 {
303                     messages::createLimitReachedForResource(aResp->res);
304                     return;
305                 }
306                 if (ec == boost::system::errc::argument_list_too_long)
307                 {
308                     nlohmann::json metricProperties = nlohmann::json::array();
309                     for (const auto& [uri, _] : uriToDbus)
310                     {
311                         metricProperties.emplace_back(uri);
312                     }
313                     messages::propertyValueIncorrect(aResp->res,
314                                                      metricProperties.dump(),
315                                                      "MetricProperties");
316                     return;
317                 }
318                 if (ec)
319                 {
320                     messages::internalError(aResp->res);
321                     BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
322                     return;
323                 }
324 
325                 messages::created(aResp->res);
326             },
327             telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
328             "xyz.openbmc_project.Telemetry.ReportManager", "AddReport",
329             "TelemetryService/" + args.name, args.reportingType,
330             args.emitsReadingsUpdate, args.logToMetricReportsCollection,
331             args.interval, readingParams);
332     }
333 
334     AddReport(const AddReport&) = delete;
335     AddReport(AddReport&&) = delete;
336     AddReport& operator=(const AddReport&) = delete;
337     AddReport& operator=(AddReport&&) = delete;
338 
339     void insert(const boost::container::flat_map<std::string, std::string>& el)
340     {
341         uriToDbus.insert(el.begin(), el.end());
342     }
343 
344   private:
345     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
346     AddReportArgs args;
347     boost::container::flat_map<std::string, std::string> uriToDbus{};
348 };
349 } // namespace telemetry
350 
351 inline void requestRoutesMetricReportDefinitionCollection(App& app)
352 {
353     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
354         .privileges(redfish::privileges::getMetricReportDefinitionCollection)
355         .methods(boost::beast::http::verb::get)(
356             [&app](const crow::Request& req,
357                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
358                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
359                 {
360                     return;
361                 }
362 
363                 asyncResp->res.jsonValue["@odata.type"] =
364                     "#MetricReportDefinitionCollection."
365                     "MetricReportDefinitionCollection";
366                 asyncResp->res.jsonValue["@odata.id"] =
367                     telemetry::metricReportDefinitionUri;
368                 asyncResp->res.jsonValue["Name"] =
369                     "Metric Definition Collection";
370                 const std::vector<const char*> interfaces{
371                     telemetry::reportInterface};
372                 collection_util::getCollectionMembers(
373                     asyncResp, telemetry::metricReportDefinitionUri, interfaces,
374                     "/xyz/openbmc_project/Telemetry/Reports/TelemetryService");
375             });
376 
377     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
378         .privileges(redfish::privileges::postMetricReportDefinitionCollection)
379         .methods(
380             boost::beast::http::verb::
381                 post)([&app](
382                           const crow::Request& req,
383                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
384             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
385             {
386                 return;
387             }
388 
389             telemetry::AddReportArgs args;
390             if (!telemetry::getUserParameters(asyncResp->res, req, args))
391             {
392                 return;
393             }
394 
395             boost::container::flat_set<std::pair<std::string, std::string>>
396                 chassisSensors;
397             if (!telemetry::getChassisSensorNodeFromMetrics(
398                     asyncResp, args.metrics, chassisSensors))
399             {
400                 return;
401             }
402 
403             auto addReportReq = std::make_shared<telemetry::AddReport>(
404                 std::move(args), asyncResp);
405             for (const auto& [chassis, sensorType] : chassisSensors)
406             {
407                 retrieveUriToDbusMap(
408                     chassis, sensorType,
409                     [asyncResp,
410                      addReportReq](const boost::beast::http::status status,
411                                    const boost::container::flat_map<
412                                        std::string, std::string>& uriToDbus) {
413                         if (status != boost::beast::http::status::ok)
414                         {
415                             BMCWEB_LOG_ERROR
416                                 << "Failed to retrieve URI to dbus sensors map with err "
417                                 << static_cast<unsigned>(status);
418                             return;
419                         }
420                         addReportReq->insert(uriToDbus);
421                     });
422             }
423         });
424 }
425 
426 inline void requestRoutesMetricReportDefinition(App& app)
427 {
428     BMCWEB_ROUTE(app,
429                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
430         .privileges(redfish::privileges::getMetricReportDefinition)
431         .methods(boost::beast::http::verb::get)(
432             [&app](const crow::Request& req,
433                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
434                    const std::string& id) {
435                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
436                 {
437                     return;
438                 }
439 
440                 crow::connections::systemBus->async_method_call(
441                     [asyncResp,
442                      id](const boost::system::error_code ec,
443                          const std::vector<std::pair<
444                              std::string, dbus::utility::DbusVariantType>>&
445                              ret) {
446                         if (ec.value() == EBADR ||
447                             ec == boost::system::errc::host_unreachable)
448                         {
449                             messages::resourceNotFound(
450                                 asyncResp->res, "MetricReportDefinition", id);
451                             return;
452                         }
453                         if (ec)
454                         {
455                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
456                             messages::internalError(asyncResp->res);
457                             return;
458                         }
459 
460                         telemetry::fillReportDefinition(asyncResp, id, ret);
461                     },
462                     telemetry::service, telemetry::getDbusReportPath(id),
463                     "org.freedesktop.DBus.Properties", "GetAll",
464                     telemetry::reportInterface);
465             });
466     BMCWEB_ROUTE(app,
467                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
468         .privileges(redfish::privileges::deleteMetricReportDefinitionCollection)
469         .methods(boost::beast::http::verb::delete_)(
470             [&app](const crow::Request& req,
471                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
472                    const std::string& id)
473 
474             {
475                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
476                 {
477                     return;
478                 }
479 
480                 const std::string reportPath = telemetry::getDbusReportPath(id);
481 
482                 crow::connections::systemBus->async_method_call(
483                     [asyncResp, id](const boost::system::error_code ec) {
484                         /*
485                          * boost::system::errc and std::errc are missing value
486                          * for EBADR error that is defined in Linux.
487                          */
488                         if (ec.value() == EBADR)
489                         {
490                             messages::resourceNotFound(
491                                 asyncResp->res, "MetricReportDefinition", id);
492                             return;
493                         }
494 
495                         if (ec)
496                         {
497                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
498                             messages::internalError(asyncResp->res);
499                             return;
500                         }
501 
502                         asyncResp->res.result(
503                             boost::beast::http::status::no_content);
504                     },
505                     telemetry::service, reportPath,
506                     "xyz.openbmc_project.Object.Delete", "Delete");
507             });
508 }
509 } // namespace redfish
510