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