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