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