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