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