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