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