xref: /openbmc/bmcweb/features/redfish/lib/trigger.hpp (revision 45ca1b868e47978a4d2e8ebb680cb384e804c97e)
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 
133         thresholds[type] = {{"Reading", reading},
134                             {"Activation", activation},
135                             {"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         reports.push_back(
148             {{"@odata.id",
149               crow::utility::urlFromPieces("redfish", "v1", "TelemetryService",
150                                            "MetricReportDefinitions", name)
151                   .string()}});
152     }
153 
154     return reports;
155 }
156 
157 inline std::vector<std::string>
158     getMetricProperties(const TriggerSensorsParams& sensors)
159 {
160     std::vector<std::string> metricProperties;
161     metricProperties.reserve(sensors.size());
162     for (const auto& [_, metadata] : sensors)
163     {
164         metricProperties.emplace_back(metadata);
165     }
166 
167     return metricProperties;
168 }
169 
170 inline bool fillTrigger(
171     nlohmann::json& json, const std::string& id,
172     const std::vector<std::pair<std::string, TriggerGetParamsVariant>>&
173         properties)
174 {
175     const std::string* name = nullptr;
176     const bool* discrete = nullptr;
177     const TriggerSensorsParams* sensors = nullptr;
178     const std::vector<std::string>* reports = nullptr;
179     const std::vector<std::string>* actions = nullptr;
180     const TriggerThresholdParamsExt* thresholds = nullptr;
181 
182     for (const auto& [key, var] : properties)
183     {
184         if (key == "Name")
185         {
186             name = std::get_if<std::string>(&var);
187         }
188         else if (key == "Discrete")
189         {
190             discrete = std::get_if<bool>(&var);
191         }
192         else if (key == "Sensors")
193         {
194             sensors = std::get_if<TriggerSensorsParams>(&var);
195         }
196         else if (key == "ReportNames")
197         {
198             reports = std::get_if<std::vector<std::string>>(&var);
199         }
200         else if (key == "TriggerActions")
201         {
202             actions = std::get_if<std::vector<std::string>>(&var);
203         }
204         else if (key == "Thresholds")
205         {
206             thresholds = std::get_if<TriggerThresholdParamsExt>(&var);
207         }
208     }
209 
210     if (name == nullptr || discrete == nullptr || sensors == nullptr ||
211         reports == nullptr || actions == nullptr || thresholds == nullptr)
212     {
213         BMCWEB_LOG_ERROR
214             << "Property type mismatch or property is missing in Trigger: "
215             << id;
216         return false;
217     }
218 
219     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
220     json["@odata.id"] = crow::utility::urlFromPieces(
221                             "redfish", "v1", "TelemetryService", "Triggers", id)
222                             .string();
223     json["Id"] = id;
224     json["Name"] = *name;
225 
226     if (*discrete)
227     {
228         std::optional<nlohmann::json> discreteTriggers =
229             getDiscreteTriggers(*thresholds);
230 
231         if (!discreteTriggers)
232         {
233             BMCWEB_LOG_ERROR << "Property Thresholds is invalid for discrete "
234                                 "triggers in Trigger: "
235                              << id;
236             return false;
237         }
238 
239         json["DiscreteTriggers"] = *discreteTriggers;
240         json["DiscreteTriggerCondition"] =
241             discreteTriggers->empty() ? "Changed" : "Specified";
242         json["MetricType"] = "Discrete";
243     }
244     else
245     {
246         std::optional<nlohmann::json> numericThresholds =
247             getNumericThresholds(*thresholds);
248 
249         if (!numericThresholds)
250         {
251             BMCWEB_LOG_ERROR << "Property Thresholds is invalid for numeric "
252                                 "thresholds in Trigger: "
253                              << id;
254             return false;
255         }
256 
257         json["NumericThresholds"] = *numericThresholds;
258         json["MetricType"] = "Numeric";
259     }
260 
261     std::optional<std::vector<std::string>> triggerActions =
262         getTriggerActions(*actions);
263 
264     if (!triggerActions)
265     {
266         BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: "
267                          << id;
268         return false;
269     }
270 
271     json["TriggerActions"] = *triggerActions;
272     json["MetricProperties"] = getMetricProperties(*sensors);
273     json["Links"]["MetricReportDefinitions"] =
274         getMetricReportDefinitions(*reports);
275 
276     return true;
277 }
278 
279 } // namespace telemetry
280 
281 inline void requestRoutesTriggerCollection(App& app)
282 {
283     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
284         .privileges(redfish::privileges::getTriggersCollection)
285         .methods(boost::beast::http::verb::get)(
286             [&app](const crow::Request& req,
287                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
288                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
289                 {
290                     return;
291                 }
292                 asyncResp->res.jsonValue["@odata.type"] =
293                     "#TriggersCollection.TriggersCollection";
294                 asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri;
295                 asyncResp->res.jsonValue["Name"] = "Triggers Collection";
296                 const std::vector<const char*> interfaces{
297                     telemetry::triggerInterface};
298                 collection_util::getCollectionMembers(
299                     asyncResp, telemetry::triggerUri, interfaces,
300                     "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
301             });
302 }
303 
304 inline void requestRoutesTrigger(App& app)
305 {
306     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
307         .privileges(redfish::privileges::getTriggers)
308         .methods(boost::beast::http::verb::get)(
309             [&app](const crow::Request& req,
310                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
311                    const std::string& id) {
312                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
313                 {
314                     return;
315                 }
316                 crow::connections::systemBus->async_method_call(
317                     [asyncResp,
318                      id](const boost::system::error_code ec,
319                          const std::vector<std::pair<
320                              std::string, telemetry::TriggerGetParamsVariant>>&
321                              ret) {
322                         if (ec.value() == EBADR ||
323                             ec == boost::system::errc::host_unreachable)
324                         {
325                             messages::resourceNotFound(asyncResp->res,
326                                                        "Triggers", id);
327                             return;
328                         }
329                         if (ec)
330                         {
331                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
332                             messages::internalError(asyncResp->res);
333                             return;
334                         }
335 
336                         if (!telemetry::fillTrigger(asyncResp->res.jsonValue,
337                                                     id, ret))
338                         {
339                             messages::internalError(asyncResp->res);
340                         }
341                     },
342                     telemetry::service, telemetry::getDbusTriggerPath(id),
343                     "org.freedesktop.DBus.Properties", "GetAll",
344                     telemetry::triggerInterface);
345             });
346 
347     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
348         .privileges(redfish::privileges::deleteTriggers)
349         .methods(boost::beast::http::verb::delete_)(
350             [&app](const crow::Request& req,
351                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
352                    const std::string& id) {
353                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
354                 {
355                     return;
356                 }
357                 const std::string triggerPath =
358                     telemetry::getDbusTriggerPath(id);
359 
360                 crow::connections::systemBus->async_method_call(
361                     [asyncResp, id](const boost::system::error_code ec) {
362                         if (ec.value() == EBADR)
363                         {
364                             messages::resourceNotFound(asyncResp->res,
365                                                        "Triggers", id);
366                             return;
367                         }
368 
369                         if (ec)
370                         {
371                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
372                             messages::internalError(asyncResp->res);
373                             return;
374                         }
375 
376                         asyncResp->res.result(
377                             boost::beast::http::status::no_content);
378                     },
379                     telemetry::service, triggerPath,
380                     "xyz.openbmc_project.Object.Delete", "Delete");
381             });
382 }
383 
384 } // namespace redfish
385