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 104 triggers.push_back({ 105 {"Name", name}, 106 {"Severity", severity}, 107 {"DwellTime", *duration}, 108 {"Value", value}, 109 }); 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"] = telemetry::triggerUri; 307 asyncResp->res.jsonValue["Name"] = "Triggers Collection"; 308 const std::vector<const char*> interfaces{telemetry::triggerInterface}; 309 collection_util::getCollectionMembers( 310 asyncResp, telemetry::triggerUri, interfaces, 311 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); 312 }); 313 } 314 315 inline void requestRoutesTrigger(App& app) 316 { 317 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 318 .privileges(redfish::privileges::getTriggers) 319 .methods(boost::beast::http::verb::get)( 320 [&app](const crow::Request& req, 321 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 322 const std::string& id) { 323 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 324 { 325 return; 326 } 327 sdbusplus::asio::getAllProperties( 328 *crow::connections::systemBus, telemetry::service, 329 telemetry::getDbusTriggerPath(id), telemetry::triggerInterface, 330 [asyncResp, 331 id](const boost::system::error_code ec, 332 const std::vector<std::pair< 333 std::string, telemetry::TriggerGetParamsVariant>>& ret) { 334 if (ec.value() == EBADR || 335 ec == boost::system::errc::host_unreachable) 336 { 337 messages::resourceNotFound(asyncResp->res, "Triggers", id); 338 return; 339 } 340 if (ec) 341 { 342 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 343 messages::internalError(asyncResp->res); 344 return; 345 } 346 347 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, id, ret)) 348 { 349 messages::internalError(asyncResp->res); 350 } 351 }); 352 }); 353 354 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 355 .privileges(redfish::privileges::deleteTriggers) 356 .methods(boost::beast::http::verb::delete_)( 357 [&app](const crow::Request& req, 358 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 359 const std::string& id) { 360 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 361 { 362 return; 363 } 364 const std::string triggerPath = telemetry::getDbusTriggerPath(id); 365 366 crow::connections::systemBus->async_method_call( 367 [asyncResp, id](const boost::system::error_code ec) { 368 if (ec.value() == EBADR) 369 { 370 messages::resourceNotFound(asyncResp->res, "Triggers", id); 371 return; 372 } 373 374 if (ec) 375 { 376 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 377 messages::internalError(asyncResp->res); 378 return; 379 } 380 381 asyncResp->res.result(boost::beast::http::status::no_content); 382 }, 383 telemetry::service, triggerPath, 384 "xyz.openbmc_project.Object.Delete", "Delete"); 385 }); 386 } 387 388 } // namespace redfish 389