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