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