xref: /openbmc/bmcweb/features/redfish/lib/trigger.hpp (revision 7a1dbc4803bf78bfc0c574e6676b3c5def4cdae3)
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 <array>
14 #include <string_view>
15 #include <tuple>
16 #include <variant>
17 #include <vector>
18 
19 namespace redfish
20 {
21 namespace telemetry
22 {
23 constexpr const char* triggerInterface =
24     "xyz.openbmc_project.Telemetry.Trigger";
25 
26 using NumericThresholdParams =
27     std::tuple<std::string, uint64_t, std::string, double>;
28 
29 using DiscreteThresholdParams =
30     std::tuple<std::string, std::string, uint64_t, std::string>;
31 
32 using TriggerThresholdParamsExt =
33     std::variant<std::monostate, std::vector<NumericThresholdParams>,
34                  std::vector<DiscreteThresholdParams>>;
35 
36 using TriggerSensorsParams =
37     std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
38 
39 using TriggerGetParamsVariant =
40     std::variant<std::monostate, bool, std::string, TriggerThresholdParamsExt,
41                  TriggerSensorsParams, std::vector<std::string>,
42                  std::vector<sdbusplus::message::object_path>>;
43 
44 inline std::optional<std::string>
45     getRedfishFromDbusAction(const std::string& dbusAction)
46 {
47     std::optional<std::string> redfishAction = std::nullopt;
48     if (dbusAction == "UpdateReport")
49     {
50         redfishAction = "RedfishMetricReport";
51     }
52     if (dbusAction == "LogToRedfishEventLog")
53     {
54         redfishAction = "RedfishEvent";
55     }
56     if (dbusAction == "LogToJournal")
57     {
58         redfishAction = "LogToLogService";
59     }
60     return redfishAction;
61 }
62 
63 inline std::optional<std::vector<std::string>>
64     getTriggerActions(const std::vector<std::string>& dbusActions)
65 {
66     std::vector<std::string> triggerActions;
67     for (const std::string& dbusAction : dbusActions)
68     {
69         std::optional<std::string> redfishAction =
70             getRedfishFromDbusAction(dbusAction);
71 
72         if (!redfishAction)
73         {
74             return std::nullopt;
75         }
76 
77         triggerActions.push_back(*redfishAction);
78     }
79 
80     return {std::move(triggerActions)};
81 }
82 
83 inline std::optional<nlohmann::json::array_t>
84     getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams)
85 {
86     const std::vector<DiscreteThresholdParams>* discreteParams =
87         std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
88 
89     if (discreteParams == nullptr)
90     {
91         return std::nullopt;
92     }
93 
94     nlohmann::json::array_t triggers;
95     for (const auto& [name, severity, dwellTime, value] : *discreteParams)
96     {
97         std::optional<std::string> duration =
98             time_utils::toDurationStringFromUint(dwellTime);
99 
100         if (!duration)
101         {
102             return std::nullopt;
103         }
104         nlohmann::json::object_t trigger;
105         trigger["Name"] = name;
106         trigger["Severity"] = severity;
107         trigger["DwellTime"] = *duration;
108         trigger["Value"] = value;
109         triggers.push_back(std::move(trigger));
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"] =
307             "/redfish/v1/TelemetryService/Triggers";
308         asyncResp->res.jsonValue["Name"] = "Triggers Collection";
309         constexpr std::array<std::string_view, 1> interfaces{
310             telemetry::triggerInterface};
311         collection_util::getCollectionMembers(
312             asyncResp,
313             boost::urls::url("/redfish/v1/TelemetryService/Triggers"),
314             interfaces,
315             "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
316         });
317 }
318 
319 inline void requestRoutesTrigger(App& app)
320 {
321     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
322         .privileges(redfish::privileges::getTriggers)
323         .methods(boost::beast::http::verb::get)(
324             [&app](const crow::Request& req,
325                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326                    const std::string& id) {
327         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
328         {
329             return;
330         }
331         sdbusplus::asio::getAllProperties(
332             *crow::connections::systemBus, telemetry::service,
333             telemetry::getDbusTriggerPath(id), telemetry::triggerInterface,
334             [asyncResp,
335              id](const boost::system::error_code ec,
336                  const std::vector<std::pair<
337                      std::string, telemetry::TriggerGetParamsVariant>>& ret) {
338             if (ec.value() == EBADR ||
339                 ec == boost::system::errc::host_unreachable)
340             {
341                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
342                 return;
343             }
344             if (ec)
345             {
346                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
347                 messages::internalError(asyncResp->res);
348                 return;
349             }
350 
351             if (!telemetry::fillTrigger(asyncResp->res.jsonValue, id, ret))
352             {
353                 messages::internalError(asyncResp->res);
354             }
355             });
356         });
357 
358     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
359         .privileges(redfish::privileges::deleteTriggers)
360         .methods(boost::beast::http::verb::delete_)(
361             [&app](const crow::Request& req,
362                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
363                    const std::string& id) {
364         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
365         {
366             return;
367         }
368         const std::string triggerPath = telemetry::getDbusTriggerPath(id);
369 
370         crow::connections::systemBus->async_method_call(
371             [asyncResp, id](const boost::system::error_code ec) {
372             if (ec.value() == EBADR)
373             {
374                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
375                 return;
376             }
377 
378             if (ec)
379             {
380                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
381                 messages::internalError(asyncResp->res);
382                 return;
383             }
384 
385             asyncResp->res.result(boost::beast::http::status::no_content);
386             },
387             telemetry::service, triggerPath,
388             "xyz.openbmc_project.Object.Delete", "Delete");
389         });
390 }
391 
392 } // namespace redfish
393