xref: /openbmc/bmcweb/redfish-core/lib/trigger.hpp (revision 141d9431)
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)
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)
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 || !discrete || !sensors || !reports || !actions || !thresholds)
208     {
209         BMCWEB_LOG_ERROR
210             << "Property type mismatch or property is missing in Trigger: "
211             << id;
212         return false;
213     }
214 
215     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
216     json["@odata.id"] = triggerUri + std::string("/") + id;
217     json["Id"] = id;
218     json["Name"] = *name;
219 
220     if (*discrete)
221     {
222         std::optional<nlohmann::json> discreteTriggers =
223             getDiscreteTriggers(*thresholds);
224 
225         if (!discreteTriggers)
226         {
227             BMCWEB_LOG_ERROR << "Property Thresholds is invalid for discrete "
228                                 "triggers in Trigger: "
229                              << id;
230             return false;
231         }
232 
233         json["DiscreteTriggers"] = *discreteTriggers;
234         json["DiscreteTriggerCondition"] =
235             discreteTriggers->empty() ? "Changed" : "Specified";
236         json["MetricType"] = "Discrete";
237     }
238     else
239     {
240         std::optional<nlohmann::json> numericThresholds =
241             getNumericThresholds(*thresholds);
242 
243         if (!numericThresholds)
244         {
245             BMCWEB_LOG_ERROR << "Property Thresholds is invalid for numeric "
246                                 "thresholds in Trigger: "
247                              << id;
248             return false;
249         }
250 
251         json["NumericThresholds"] = *numericThresholds;
252         json["MetricType"] = "Numeric";
253     }
254 
255     std::optional<std::vector<std::string>> triggerActions =
256         getTriggerActions(*actions);
257 
258     if (!triggerActions)
259     {
260         BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: "
261                          << id;
262         return false;
263     }
264 
265     json["TriggerActions"] = *triggerActions;
266     json["MetricProperties"] = getMetricProperties(*sensors);
267     json["Links"]["MetricReportDefinitions"] =
268         getMetricReportDefinitions(*reports);
269 
270     return true;
271 }
272 
273 } // namespace telemetry
274 
275 inline void requestRoutesTriggerCollection(App& app)
276 {
277     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
278         .privileges(redfish::privileges::getTriggersCollection)
279         .methods(boost::beast::http::verb::get)(
280             [](const crow::Request&,
281                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
282                 asyncResp->res.jsonValue["@odata.type"] =
283                     "#TriggersCollection.TriggersCollection";
284                 asyncResp->res.jsonValue["@odata.id"] =
285                     "/redfish/v1/TelemetryService/Triggers";
286                 asyncResp->res.jsonValue["Name"] = "Triggers Collection";
287                 const std::vector<const char*> interfaces{
288                     telemetry::triggerInterface};
289                 collection_util::getCollectionMembers(
290                     asyncResp, telemetry::triggerUri, interfaces,
291                     "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
292             });
293 }
294 
295 inline void requestRoutesTrigger(App& app)
296 {
297     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
298         .privileges(redfish::privileges::getTriggers)
299         .methods(boost::beast::http::verb::get)(
300             [](const crow::Request&,
301                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302                const std::string& id) {
303                 crow::connections::systemBus->async_method_call(
304                     [asyncResp,
305                      id](const boost::system::error_code ec,
306                          const std::vector<std::pair<
307                              std::string, telemetry::TriggerGetParamsVariant>>&
308                              ret) {
309                         if (ec.value() == EBADR ||
310                             ec == boost::system::errc::host_unreachable)
311                         {
312                             messages::resourceNotFound(asyncResp->res,
313                                                        "Triggers", id);
314                             return;
315                         }
316                         if (ec)
317                         {
318                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
319                             messages::internalError(asyncResp->res);
320                             return;
321                         }
322 
323                         if (!telemetry::fillTrigger(asyncResp->res.jsonValue,
324                                                     id, ret))
325                         {
326                             messages::internalError(asyncResp->res);
327                         }
328                     },
329                     telemetry::service, telemetry::getDbusTriggerPath(id),
330                     "org.freedesktop.DBus.Properties", "GetAll",
331                     telemetry::triggerInterface);
332             });
333 
334     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
335         .privileges(redfish::privileges::deleteTriggers)
336         .methods(boost::beast::http::verb::delete_)(
337             [](const crow::Request&,
338                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339                const std::string& id) {
340                 const std::string triggerPath =
341                     telemetry::getDbusTriggerPath(id);
342 
343                 crow::connections::systemBus->async_method_call(
344                     [asyncResp, id](const boost::system::error_code ec) {
345                         if (ec.value() == EBADR)
346                         {
347                             messages::resourceNotFound(asyncResp->res,
348                                                        "Triggers", id);
349                             return;
350                         }
351 
352                         if (ec)
353                         {
354                             BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
355                             messages::internalError(asyncResp->res);
356                             return;
357                         }
358 
359                         asyncResp->res.result(
360                             boost::beast::http::status::no_content);
361                     },
362                     telemetry::service, triggerPath,
363                     "xyz.openbmc_project.Object.Delete", "Delete");
364             });
365 }
366 
367 } // namespace redfish
368