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