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