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