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