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(
314                     aResp->res, metricProperties.dump(), "MetricProperties");
315                 return;
316             }
317             if (ec)
318             {
319                 messages::internalError(aResp->res);
320                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
321                 return;
322             }
323 
324             messages::created(aResp->res);
325             },
326             telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
327             "xyz.openbmc_project.Telemetry.ReportManager", "AddReport",
328             "TelemetryService/" + args.name, args.reportingType,
329             args.emitsReadingsUpdate, args.logToMetricReportsCollection,
330             args.interval, readingParams);
331     }
332 
333     AddReport(const AddReport&) = delete;
334     AddReport(AddReport&&) = delete;
335     AddReport& operator=(const AddReport&) = delete;
336     AddReport& operator=(AddReport&&) = delete;
337 
338     void insert(const boost::container::flat_map<std::string, std::string>& el)
339     {
340         uriToDbus.insert(el.begin(), el.end());
341     }
342 
343   private:
344     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
345     AddReportArgs args;
346     boost::container::flat_map<std::string, std::string> uriToDbus{};
347 };
348 } // namespace telemetry
349 
350 inline void requestRoutesMetricReportDefinitionCollection(App& app)
351 {
352     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
353         .privileges(redfish::privileges::getMetricReportDefinitionCollection)
354         .methods(boost::beast::http::verb::get)(
355             [&app](const crow::Request& req,
356                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
357         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
358         {
359             return;
360         }
361 
362         asyncResp->res.jsonValue["@odata.type"] =
363             "#MetricReportDefinitionCollection."
364             "MetricReportDefinitionCollection";
365         asyncResp->res.jsonValue["@odata.id"] =
366             telemetry::metricReportDefinitionUri;
367         asyncResp->res.jsonValue["Name"] = "Metric Definition Collection";
368         const std::vector<const char*> interfaces{telemetry::reportInterface};
369         collection_util::getCollectionMembers(
370             asyncResp, telemetry::metricReportDefinitionUri, interfaces,
371             "/xyz/openbmc_project/Telemetry/Reports/TelemetryService");
372         });
373 
374     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
375         .privileges(redfish::privileges::postMetricReportDefinitionCollection)
376         .methods(boost::beast::http::verb::post)(
377             [&app](const crow::Request& req,
378                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
379         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
380         {
381             return;
382         }
383 
384         telemetry::AddReportArgs args;
385         if (!telemetry::getUserParameters(asyncResp->res, req, args))
386         {
387             return;
388         }
389 
390         boost::container::flat_set<std::pair<std::string, std::string>>
391             chassisSensors;
392         if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics,
393                                                         chassisSensors))
394         {
395             return;
396         }
397 
398         auto addReportReq =
399             std::make_shared<telemetry::AddReport>(std::move(args), asyncResp);
400         for (const auto& [chassis, sensorType] : chassisSensors)
401         {
402             retrieveUriToDbusMap(
403                 chassis, sensorType,
404                 [asyncResp, addReportReq](
405                     const boost::beast::http::status status,
406                     const boost::container::flat_map<std::string, std::string>&
407                         uriToDbus) {
408                 if (status != boost::beast::http::status::ok)
409                 {
410                     BMCWEB_LOG_ERROR
411                         << "Failed to retrieve URI to dbus sensors map with err "
412                         << static_cast<unsigned>(status);
413                     return;
414                 }
415                 addReportReq->insert(uriToDbus);
416                 });
417         }
418         });
419 }
420 
421 inline void requestRoutesMetricReportDefinition(App& app)
422 {
423     BMCWEB_ROUTE(app,
424                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
425         .privileges(redfish::privileges::getMetricReportDefinition)
426         .methods(boost::beast::http::verb::get)(
427             [&app](const crow::Request& req,
428                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
429                    const std::string& id) {
430         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
431         {
432             return;
433         }
434 
435         crow::connections::systemBus->async_method_call(
436             [asyncResp,
437              id](const boost::system::error_code ec,
438                  const std::vector<std::pair<
439                      std::string, dbus::utility::DbusVariantType>>& ret) {
440             if (ec.value() == EBADR ||
441                 ec == boost::system::errc::host_unreachable)
442             {
443                 messages::resourceNotFound(asyncResp->res,
444                                            "MetricReportDefinition", id);
445                 return;
446             }
447             if (ec)
448             {
449                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
450                 messages::internalError(asyncResp->res);
451                 return;
452             }
453 
454             telemetry::fillReportDefinition(asyncResp, id, ret);
455             },
456             telemetry::service, telemetry::getDbusReportPath(id),
457             "org.freedesktop.DBus.Properties", "GetAll",
458             telemetry::reportInterface);
459         });
460     BMCWEB_ROUTE(app,
461                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
462         .privileges(redfish::privileges::deleteMetricReportDefinitionCollection)
463         .methods(boost::beast::http::verb::delete_)(
464             [&app](const crow::Request& req,
465                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
466                    const std::string& id)
467 
468             {
469         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
470         {
471             return;
472         }
473 
474         const std::string reportPath = telemetry::getDbusReportPath(id);
475 
476         crow::connections::systemBus->async_method_call(
477             [asyncResp, id](const boost::system::error_code ec) {
478             /*
479              * boost::system::errc and std::errc are missing value
480              * for EBADR error that is defined in Linux.
481              */
482             if (ec.value() == EBADR)
483             {
484                 messages::resourceNotFound(asyncResp->res,
485                                            "MetricReportDefinition", id);
486                 return;
487             }
488 
489             if (ec)
490             {
491                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
492                 messages::internalError(asyncResp->res);
493                 return;
494             }
495 
496             asyncResp->res.result(boost::beast::http::status::no_content);
497             },
498             telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete",
499             "Delete");
500         });
501 }
502 } // namespace redfish
503