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