1 #pragma once
2 
3 #include "node.hpp"
4 #include "sensors.hpp"
5 #include "utils/telemetry_utils.hpp"
6 #include "utils/time_utils.hpp"
7 
8 #include <boost/container/flat_map.hpp>
9 
10 #include <tuple>
11 #include <variant>
12 
13 namespace redfish
14 {
15 
16 namespace telemetry
17 {
18 
19 using ReadingParameters =
20     std::vector<std::tuple<sdbusplus::message::object_path, std::string,
21                            std::string, std::string>>;
22 
23 inline void fillReportDefinition(
24     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id,
25     const std::vector<
26         std::pair<std::string, std::variant<std::string, bool, uint64_t,
27                                             ReadingParameters>>>& ret)
28 {
29     asyncResp->res.jsonValue["@odata.type"] =
30         "#MetricReportDefinition.v1_3_0.MetricReportDefinition";
31     asyncResp->res.jsonValue["@odata.id"] =
32         telemetry::metricReportDefinitionUri + id;
33     asyncResp->res.jsonValue["Id"] = id;
34     asyncResp->res.jsonValue["Name"] = id;
35     asyncResp->res.jsonValue["MetricReport"]["@odata.id"] =
36         telemetry::metricReportUri + id;
37     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
38     asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite";
39 
40     const bool* emitsReadingsUpdate = nullptr;
41     const bool* logToMetricReportsCollection = nullptr;
42     const ReadingParameters* readingParams = nullptr;
43     const std::string* reportingType = nullptr;
44     const uint64_t* interval = nullptr;
45     for (const auto& [key, var] : ret)
46     {
47         if (key == "EmitsReadingsUpdate")
48         {
49             emitsReadingsUpdate = std::get_if<bool>(&var);
50         }
51         else if (key == "LogToMetricReportsCollection")
52         {
53             logToMetricReportsCollection = std::get_if<bool>(&var);
54         }
55         else if (key == "ReadingParameters")
56         {
57             readingParams = std::get_if<ReadingParameters>(&var);
58         }
59         else if (key == "ReportingType")
60         {
61             reportingType = std::get_if<std::string>(&var);
62         }
63         else if (key == "Interval")
64         {
65             interval = std::get_if<uint64_t>(&var);
66         }
67     }
68     if (!emitsReadingsUpdate || !logToMetricReportsCollection ||
69         !readingParams || !reportingType || !interval)
70     {
71         BMCWEB_LOG_ERROR << "Property type mismatch or property is missing";
72         messages::internalError(asyncResp->res);
73         return;
74     }
75 
76     std::vector<std::string> redfishReportActions;
77     redfishReportActions.reserve(2);
78     if (*emitsReadingsUpdate)
79     {
80         redfishReportActions.emplace_back("RedfishEvent");
81     }
82     if (*logToMetricReportsCollection)
83     {
84         redfishReportActions.emplace_back("LogToMetricReportsCollection");
85     }
86 
87     nlohmann::json metrics = nlohmann::json::array();
88     for (auto& [sensorPath, operationType, id, metadata] : *readingParams)
89     {
90         metrics.push_back({
91             {"MetricId", id},
92             {"MetricProperties", {metadata}},
93         });
94     }
95     asyncResp->res.jsonValue["Metrics"] = metrics;
96     asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType;
97     asyncResp->res.jsonValue["ReportActions"] = redfishReportActions;
98     asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
99         time_utils::toDurationString(std::chrono::milliseconds(*interval));
100 }
101 
102 struct AddReportArgs
103 {
104     std::string name;
105     std::string reportingType;
106     bool emitsReadingsUpdate = false;
107     bool logToMetricReportsCollection = false;
108     uint64_t interval = 0;
109     std::vector<std::pair<std::string, std::vector<std::string>>> metrics;
110 };
111 
112 inline bool toDbusReportActions(crow::Response& res,
113                                 std::vector<std::string>& actions,
114                                 AddReportArgs& args)
115 {
116     size_t index = 0;
117     for (auto& action : actions)
118     {
119         if (action == "RedfishEvent")
120         {
121             args.emitsReadingsUpdate = true;
122         }
123         else if (action == "LogToMetricReportsCollection")
124         {
125             args.logToMetricReportsCollection = true;
126         }
127         else
128         {
129             messages::propertyValueNotInList(
130                 res, action, "ReportActions/" + std::to_string(index));
131             return false;
132         }
133         index++;
134     }
135     return true;
136 }
137 
138 inline bool getUserParameters(crow::Response& res, const crow::Request& req,
139                               AddReportArgs& args)
140 {
141     std::vector<nlohmann::json> metrics;
142     std::vector<std::string> reportActions;
143     std::optional<nlohmann::json> schedule;
144     if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics,
145                              "MetricReportDefinitionType", args.reportingType,
146                              "ReportActions", reportActions, "Schedule",
147                              schedule))
148     {
149         return false;
150     }
151 
152     constexpr const char* allowedCharactersInName =
153         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
154     if (args.name.empty() || args.name.find_first_not_of(
155                                  allowedCharactersInName) != std::string::npos)
156     {
157         BMCWEB_LOG_ERROR << "Failed to match " << args.name
158                          << " with allowed character "
159                          << allowedCharactersInName;
160         messages::propertyValueIncorrect(res, "Id", args.name);
161         return false;
162     }
163 
164     if (args.reportingType != "Periodic" && args.reportingType != "OnRequest")
165     {
166         messages::propertyValueNotInList(res, args.reportingType,
167                                          "MetricReportDefinitionType");
168         return false;
169     }
170 
171     if (!toDbusReportActions(res, reportActions, args))
172     {
173         return false;
174     }
175 
176     if (args.reportingType == "Periodic")
177     {
178         if (!schedule)
179         {
180             messages::createFailedMissingReqProperties(res, "Schedule");
181             return false;
182         }
183 
184         std::string durationStr;
185         if (!json_util::readJson(*schedule, res, "RecurrenceInterval",
186                                  durationStr))
187         {
188             return false;
189         }
190 
191         std::optional<std::chrono::milliseconds> durationNum =
192             time_utils::fromDurationString(durationStr);
193         if (!durationNum)
194         {
195             messages::propertyValueIncorrect(res, "RecurrenceInterval",
196                                              durationStr);
197             return false;
198         }
199         args.interval = static_cast<uint64_t>(durationNum->count());
200     }
201 
202     args.metrics.reserve(metrics.size());
203     for (auto& m : metrics)
204     {
205         std::string id;
206         std::vector<std::string> uris;
207         if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties",
208                                  uris))
209         {
210             return false;
211         }
212 
213         args.metrics.emplace_back(std::move(id), std::move(uris));
214     }
215 
216     return true;
217 }
218 
219 inline bool getChassisSensorNode(
220     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
221     const std::vector<std::pair<std::string, std::vector<std::string>>>&
222         metrics,
223     boost::container::flat_set<std::pair<std::string, std::string>>& matched)
224 {
225     for (const auto& [id, uris] : metrics)
226     {
227         for (size_t i = 0; i < uris.size(); i++)
228         {
229             const std::string& uri = uris[i];
230             std::string chassis;
231             std::string node;
232 
233             if (!boost::starts_with(uri, "/redfish/v1/Chassis/") ||
234                 !dbus::utility::getNthStringFromPath(uri, 3, chassis) ||
235                 !dbus::utility::getNthStringFromPath(uri, 4, node))
236             {
237                 BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node "
238                                     "from "
239                                  << 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 << "Failed to find DBus sensor "
284                                         "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     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 class MetricReportDefinitionCollection : public Node
351 {
352   public:
353     MetricReportDefinitionCollection(App& app) :
354         Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
355     {
356         entityPrivileges = {
357             {boost::beast::http::verb::get, {{"Login"}}},
358             {boost::beast::http::verb::head, {{"Login"}}},
359             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
360             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
361             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
362             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
363     }
364 
365   private:
366     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
367                const crow::Request&, const std::vector<std::string>&) override
368     {
369         asyncResp->res.jsonValue["@odata.type"] =
370             "#MetricReportDefinitionCollection."
371             "MetricReportDefinitionCollection";
372         asyncResp->res.jsonValue["@odata.id"] =
373             "/redfish/v1/TelemetryService/MetricReportDefinitions";
374         asyncResp->res.jsonValue["Name"] = "Metric Definition Collection";
375 
376         telemetry::getReportCollection(asyncResp,
377                                        telemetry::metricReportDefinitionUri);
378     }
379 
380     void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
381                 const crow::Request& req,
382                 const std::vector<std::string>&) override
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::getChassisSensorNode(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 << "Failed to retrieve URI to dbus "
411                                             "sensors map with err "
412                                          << static_cast<unsigned>(status);
413                         return;
414                     }
415                     addReportReq->insert(uriToDbus);
416                 });
417         }
418     }
419 };
420 
421 class MetricReportDefinition : public Node
422 {
423   public:
424     MetricReportDefinition(App& app) :
425         Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/",
426              std::string())
427     {
428         entityPrivileges = {
429             {boost::beast::http::verb::get, {{"Login"}}},
430             {boost::beast::http::verb::head, {{"Login"}}},
431             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
432             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
433             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
434             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
435     }
436 
437   private:
438     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
439                const crow::Request&,
440                const std::vector<std::string>& params) override
441     {
442 
443         if (params.size() != 1)
444         {
445             messages::internalError(asyncResp->res);
446             return;
447         }
448 
449         const std::string& id = params[0];
450         crow::connections::systemBus->async_method_call(
451             [asyncResp,
452              id](const boost::system::error_code ec,
453                  const std::vector<std::pair<
454                      std::string, std::variant<std::string, bool, uint64_t,
455                                                telemetry::ReadingParameters>>>&
456                      ret) {
457                 if (ec.value() == EBADR ||
458                     ec == boost::system::errc::host_unreachable)
459                 {
460                     messages::resourceNotFound(asyncResp->res,
461                                                "MetricReportDefinition", id);
462                     return;
463                 }
464                 if (ec)
465                 {
466                     BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
467                     messages::internalError(asyncResp->res);
468                     return;
469                 }
470 
471                 telemetry::fillReportDefinition(asyncResp, id, ret);
472             },
473             telemetry::service, telemetry::getDbusReportPath(id),
474             "org.freedesktop.DBus.Properties", "GetAll",
475             telemetry::reportInterface);
476     }
477 
478     void doDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
479                   const crow::Request&,
480                   const std::vector<std::string>& params) override
481     {
482         if (params.size() != 1)
483         {
484             messages::internalError(asyncResp->res);
485             return;
486         }
487 
488         const std::string& id = params[0];
489         const std::string reportPath = telemetry::getDbusReportPath(id);
490 
491         crow::connections::systemBus->async_method_call(
492             [asyncResp, id](const boost::system::error_code ec) {
493                 /*
494                  * boost::system::errc and std::errc are missing value for
495                  * EBADR error that is defined in Linux.
496                  */
497                 if (ec.value() == EBADR)
498                 {
499                     messages::resourceNotFound(asyncResp->res,
500                                                "MetricReportDefinition", id);
501                     return;
502                 }
503 
504                 if (ec)
505                 {
506                     BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
507                     messages::internalError(asyncResp->res);
508                     return;
509                 }
510 
511                 asyncResp->res.result(boost::beast::http::status::no_content);
512             },
513             telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete",
514             "Delete");
515     }
516 };
517 } // namespace redfish
518