xref: /openbmc/bmcweb/redfish-core/lib/trigger.hpp (revision 079360ae)
1 #pragma once
2 
3 #include "utils/collection.hpp"
4 #include "utils/telemetry_utils.hpp"
5 
6 #include <app.hpp>
7 #include <query.hpp>
8 #include <registries/privilege_registry.hpp>
9 #include <sdbusplus/asio/property.hpp>
10 #include <sdbusplus/unpack_properties.hpp>
11 #include <utils/dbus_utils.hpp>
12 
13 #include <tuple>
14 #include <variant>
15 #include <vector>
16 
17 namespace redfish
18 {
19 namespace telemetry
20 {
21 constexpr const char* triggerInterface =
22     "xyz.openbmc_project.Telemetry.Trigger";
23 
24 using NumericThresholdParams =
25     std::tuple<std::string, uint64_t, std::string, double>;
26 
27 using DiscreteThresholdParams =
28     std::tuple<std::string, std::string, uint64_t, std::string>;
29 
30 using TriggerThresholdParamsExt =
31     std::variant<std::monostate, std::vector<NumericThresholdParams>,
32                  std::vector<DiscreteThresholdParams>>;
33 
34 using TriggerSensorsParams =
35     std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
36 
37 using TriggerGetParamsVariant =
38     std::variant<std::monostate, bool, std::string, TriggerThresholdParamsExt,
39                  TriggerSensorsParams, std::vector<std::string>,
40                  std::vector<sdbusplus::message::object_path>>;
41 
42 inline std::optional<std::string>
43     getRedfishFromDbusAction(const std::string& dbusAction)
44 {
45     std::optional<std::string> redfishAction = std::nullopt;
46     if (dbusAction == "UpdateReport")
47     {
48         redfishAction = "RedfishMetricReport";
49     }
50     if (dbusAction == "LogToRedfishEventLog")
51     {
52         redfishAction = "RedfishEvent";
53     }
54     if (dbusAction == "LogToJournal")
55     {
56         redfishAction = "LogToLogService";
57     }
58     return redfishAction;
59 }
60 
61 inline std::optional<std::vector<std::string>>
62     getTriggerActions(const std::vector<std::string>& dbusActions)
63 {
64     std::vector<std::string> triggerActions;
65     for (const std::string& dbusAction : dbusActions)
66     {
67         std::optional<std::string> redfishAction =
68             getRedfishFromDbusAction(dbusAction);
69 
70         if (!redfishAction)
71         {
72             return std::nullopt;
73         }
74 
75         triggerActions.push_back(*redfishAction);
76     }
77 
78     return {std::move(triggerActions)};
79 }
80 
81 inline std::optional<nlohmann::json::array_t>
82     getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams)
83 {
84     const std::vector<DiscreteThresholdParams>* discreteParams =
85         std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
86 
87     if (discreteParams == nullptr)
88     {
89         return std::nullopt;
90     }
91 
92     nlohmann::json::array_t triggers;
93     for (const auto& [name, severity, dwellTime, value] : *discreteParams)
94     {
95         std::optional<std::string> duration =
96             time_utils::toDurationStringFromUint(dwellTime);
97 
98         if (!duration)
99         {
100             return std::nullopt;
101         }
102         nlohmann::json::object_t trigger;
103         trigger["Name"] = name;
104         trigger["Severity"] = severity;
105         trigger["DwellTime"] = *duration;
106         trigger["Value"] = value;
107         triggers.push_back(std::move(trigger));
108     }
109 
110     return {std::move(triggers)};
111 }
112 
113 inline std::optional<nlohmann::json>
114     getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams)
115 {
116     const std::vector<NumericThresholdParams>* numericParams =
117         std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams);
118 
119     if (numericParams == nullptr)
120     {
121         return std::nullopt;
122     }
123 
124     nlohmann::json::object_t thresholds;
125     for (const auto& [type, dwellTime, activation, reading] : *numericParams)
126     {
127         std::optional<std::string> duration =
128             time_utils::toDurationStringFromUint(dwellTime);
129 
130         if (!duration)
131         {
132             return std::nullopt;
133         }
134         nlohmann::json& threshold = thresholds[type];
135         threshold["Reading"] = reading;
136         threshold["Activation"] = activation;
137         threshold["DwellTime"] = *duration;
138     }
139 
140     return {std::move(thresholds)};
141 }
142 
143 inline std::optional<nlohmann::json> getMetricReportDefinitions(
144     const std::vector<sdbusplus::message::object_path>& reportPaths)
145 {
146     nlohmann::json reports = nlohmann::json::array();
147 
148     for (const sdbusplus::message::object_path& path : reportPaths)
149     {
150         std::string reportId = path.filename();
151         if (reportId.empty())
152         {
153             {
154                 BMCWEB_LOG_ERROR << "Property Reports contains invalid value: "
155                                  << path.str;
156                 return std::nullopt;
157             }
158         }
159 
160         nlohmann::json::object_t report;
161         report["@odata.id"] =
162             crow::utility::urlFromPieces("redfish", "v1", "TelemetryService",
163                                          "MetricReportDefinitions", reportId);
164         reports.push_back(std::move(report));
165     }
166 
167     return {std::move(reports)};
168 }
169 
170 inline std::vector<std::string>
171     getMetricProperties(const TriggerSensorsParams& sensors)
172 {
173     std::vector<std::string> metricProperties;
174     metricProperties.reserve(sensors.size());
175     for (const auto& [_, metadata] : sensors)
176     {
177         metricProperties.emplace_back(metadata);
178     }
179 
180     return metricProperties;
181 }
182 
183 inline bool fillTrigger(
184     nlohmann::json& json, const std::string& id,
185     const std::vector<std::pair<std::string, TriggerGetParamsVariant>>&
186         properties)
187 {
188     const std::string* name = nullptr;
189     const bool* discrete = nullptr;
190     const TriggerSensorsParams* sensors = nullptr;
191     const std::vector<sdbusplus::message::object_path>* reports = nullptr;
192     const std::vector<std::string>* triggerActions = nullptr;
193     const TriggerThresholdParamsExt* thresholds = nullptr;
194 
195     const bool success = sdbusplus::unpackPropertiesNoThrow(
196         dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete",
197         discrete, "Sensors", sensors, "Reports", reports, "TriggerActions",
198         triggerActions, "Thresholds", thresholds);
199 
200     if (!success)
201     {
202         return false;
203     }
204 
205     if (triggerActions != nullptr)
206     {
207         std::optional<std::vector<std::string>> redfishTriggerActions =
208             getTriggerActions(*triggerActions);
209         if (!redfishTriggerActions)
210         {
211             BMCWEB_LOG_ERROR
212                 << "Property TriggerActions is invalid in Trigger: " << id;
213             return false;
214         }
215         json["TriggerActions"] = *triggerActions;
216     }
217 
218     if (reports != nullptr)
219     {
220         std::optional<nlohmann::json> linkedReports =
221             getMetricReportDefinitions(*reports);
222         if (!linkedReports)
223         {
224             BMCWEB_LOG_ERROR << "Property Reports is invalid in Trigger: "
225                              << id;
226             return false;
227         }
228         json["Links"]["MetricReportDefinitions"] = *linkedReports;
229     }
230 
231     if (discrete != nullptr)
232     {
233         if (*discrete)
234         {
235             std::optional<nlohmann::json::array_t> discreteTriggers =
236                 getDiscreteTriggers(*thresholds);
237 
238             if (!discreteTriggers)
239             {
240                 BMCWEB_LOG_ERROR
241                     << "Property Thresholds is invalid for discrete "
242                        "triggers in Trigger: "
243                     << id;
244                 return false;
245             }
246 
247             json["DiscreteTriggers"] = *discreteTriggers;
248             json["DiscreteTriggerCondition"] =
249                 discreteTriggers->empty() ? "Changed" : "Specified";
250             json["MetricType"] = "Discrete";
251         }
252         else
253         {
254             std::optional<nlohmann::json> numericThresholds =
255                 getNumericThresholds(*thresholds);
256 
257             if (!numericThresholds)
258             {
259                 BMCWEB_LOG_ERROR
260                     << "Property Thresholds is invalid for numeric "
261                        "thresholds in Trigger: "
262                     << id;
263                 return false;
264             }
265 
266             json["NumericThresholds"] = *numericThresholds;
267             json["MetricType"] = "Numeric";
268         }
269     }
270 
271     if (name != nullptr)
272     {
273         json["Name"] = *name;
274     }
275 
276     if (sensors != nullptr)
277     {
278         json["MetricProperties"] = getMetricProperties(*sensors);
279     }
280 
281     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
282     json["@odata.id"] = crow::utility::urlFromPieces(
283         "redfish", "v1", "TelemetryService", "Triggers", id);
284     json["Id"] = id;
285 
286     return true;
287 }
288 
289 } // namespace telemetry
290 
291 inline void requestRoutesTriggerCollection(App& app)
292 {
293     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
294         .privileges(redfish::privileges::getTriggersCollection)
295         .methods(boost::beast::http::verb::get)(
296             [&app](const crow::Request& req,
297                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
298         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
299         {
300             return;
301         }
302         asyncResp->res.jsonValue["@odata.type"] =
303             "#TriggersCollection.TriggersCollection";
304         asyncResp->res.jsonValue["@odata.id"] =
305             "/redfish/v1/TelemetryService/Triggers";
306         asyncResp->res.jsonValue["Name"] = "Triggers Collection";
307         const std::vector<const char*> interfaces{telemetry::triggerInterface};
308         collection_util::getCollectionMembers(
309             asyncResp,
310             boost::urls::url("/redfish/v1/TelemetryService/Triggers"),
311             interfaces,
312             "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
313         });
314 }
315 
316 inline void requestRoutesTrigger(App& app)
317 {
318     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
319         .privileges(redfish::privileges::getTriggers)
320         .methods(boost::beast::http::verb::get)(
321             [&app](const crow::Request& req,
322                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
323                    const std::string& id) {
324         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
325         {
326             return;
327         }
328         sdbusplus::asio::getAllProperties(
329             *crow::connections::systemBus, telemetry::service,
330             telemetry::getDbusTriggerPath(id), telemetry::triggerInterface,
331             [asyncResp,
332              id](const boost::system::error_code ec,
333                  const std::vector<std::pair<
334                      std::string, telemetry::TriggerGetParamsVariant>>& ret) {
335             if (ec.value() == EBADR ||
336                 ec == boost::system::errc::host_unreachable)
337             {
338                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
339                 return;
340             }
341             if (ec)
342             {
343                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
344                 messages::internalError(asyncResp->res);
345                 return;
346             }
347 
348             if (!telemetry::fillTrigger(asyncResp->res.jsonValue, id, ret))
349             {
350                 messages::internalError(asyncResp->res);
351             }
352             });
353         });
354 
355     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
356         .privileges(redfish::privileges::deleteTriggers)
357         .methods(boost::beast::http::verb::delete_)(
358             [&app](const crow::Request& req,
359                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
360                    const std::string& id) {
361         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
362         {
363             return;
364         }
365         const std::string triggerPath = telemetry::getDbusTriggerPath(id);
366 
367         crow::connections::systemBus->async_method_call(
368             [asyncResp, id](const boost::system::error_code ec) {
369             if (ec.value() == EBADR)
370             {
371                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
372                 return;
373             }
374 
375             if (ec)
376             {
377                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
378                 messages::internalError(asyncResp->res);
379                 return;
380             }
381 
382             asyncResp->res.result(boost::beast::http::status::no_content);
383             },
384             telemetry::service, triggerPath,
385             "xyz.openbmc_project.Object.Delete", "Delete");
386         });
387 }
388 
389 } // namespace redfish
390