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