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