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