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