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