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