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