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 133 thresholds[type] = {{"Reading", reading}, 134 {"Activation", activation}, 135 {"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 reports.push_back( 148 {{"@odata.id", 149 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 150 "MetricReportDefinitions", name) 151 .string()}}); 152 } 153 154 return reports; 155 } 156 157 inline std::vector<std::string> 158 getMetricProperties(const TriggerSensorsParams& sensors) 159 { 160 std::vector<std::string> metricProperties; 161 metricProperties.reserve(sensors.size()); 162 for (const auto& [_, metadata] : sensors) 163 { 164 metricProperties.emplace_back(metadata); 165 } 166 167 return metricProperties; 168 } 169 170 inline bool fillTrigger( 171 nlohmann::json& json, const std::string& id, 172 const std::vector<std::pair<std::string, TriggerGetParamsVariant>>& 173 properties) 174 { 175 const std::string* name = nullptr; 176 const bool* discrete = nullptr; 177 const TriggerSensorsParams* sensors = nullptr; 178 const std::vector<std::string>* reports = nullptr; 179 const std::vector<std::string>* actions = nullptr; 180 const TriggerThresholdParamsExt* thresholds = nullptr; 181 182 for (const auto& [key, var] : properties) 183 { 184 if (key == "Name") 185 { 186 name = std::get_if<std::string>(&var); 187 } 188 else if (key == "Discrete") 189 { 190 discrete = std::get_if<bool>(&var); 191 } 192 else if (key == "Sensors") 193 { 194 sensors = std::get_if<TriggerSensorsParams>(&var); 195 } 196 else if (key == "ReportNames") 197 { 198 reports = std::get_if<std::vector<std::string>>(&var); 199 } 200 else if (key == "TriggerActions") 201 { 202 actions = std::get_if<std::vector<std::string>>(&var); 203 } 204 else if (key == "Thresholds") 205 { 206 thresholds = std::get_if<TriggerThresholdParamsExt>(&var); 207 } 208 } 209 210 if (name == nullptr || discrete == nullptr || sensors == nullptr || 211 reports == nullptr || actions == nullptr || thresholds == nullptr) 212 { 213 BMCWEB_LOG_ERROR 214 << "Property type mismatch or property is missing in Trigger: " 215 << id; 216 return false; 217 } 218 219 json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; 220 json["@odata.id"] = crow::utility::urlFromPieces( 221 "redfish", "v1", "TelemetryService", "Triggers", id) 222 .string(); 223 json["Id"] = id; 224 json["Name"] = *name; 225 226 if (*discrete) 227 { 228 std::optional<nlohmann::json> discreteTriggers = 229 getDiscreteTriggers(*thresholds); 230 231 if (!discreteTriggers) 232 { 233 BMCWEB_LOG_ERROR << "Property Thresholds is invalid for discrete " 234 "triggers in Trigger: " 235 << id; 236 return false; 237 } 238 239 json["DiscreteTriggers"] = *discreteTriggers; 240 json["DiscreteTriggerCondition"] = 241 discreteTriggers->empty() ? "Changed" : "Specified"; 242 json["MetricType"] = "Discrete"; 243 } 244 else 245 { 246 std::optional<nlohmann::json> numericThresholds = 247 getNumericThresholds(*thresholds); 248 249 if (!numericThresholds) 250 { 251 BMCWEB_LOG_ERROR << "Property Thresholds is invalid for numeric " 252 "thresholds in Trigger: " 253 << id; 254 return false; 255 } 256 257 json["NumericThresholds"] = *numericThresholds; 258 json["MetricType"] = "Numeric"; 259 } 260 261 std::optional<std::vector<std::string>> triggerActions = 262 getTriggerActions(*actions); 263 264 if (!triggerActions) 265 { 266 BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: " 267 << id; 268 return false; 269 } 270 271 json["TriggerActions"] = *triggerActions; 272 json["MetricProperties"] = getMetricProperties(*sensors); 273 json["Links"]["MetricReportDefinitions"] = 274 getMetricReportDefinitions(*reports); 275 276 return true; 277 } 278 279 } // namespace telemetry 280 281 inline void requestRoutesTriggerCollection(App& app) 282 { 283 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 284 .privileges(redfish::privileges::getTriggersCollection) 285 .methods(boost::beast::http::verb::get)( 286 [&app](const crow::Request& req, 287 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 288 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 289 { 290 return; 291 } 292 asyncResp->res.jsonValue["@odata.type"] = 293 "#TriggersCollection.TriggersCollection"; 294 asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri; 295 asyncResp->res.jsonValue["Name"] = "Triggers Collection"; 296 const std::vector<const char*> interfaces{ 297 telemetry::triggerInterface}; 298 collection_util::getCollectionMembers( 299 asyncResp, telemetry::triggerUri, interfaces, 300 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); 301 }); 302 } 303 304 inline void requestRoutesTrigger(App& app) 305 { 306 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 307 .privileges(redfish::privileges::getTriggers) 308 .methods(boost::beast::http::verb::get)( 309 [&app](const crow::Request& req, 310 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 311 const std::string& id) { 312 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 313 { 314 return; 315 } 316 crow::connections::systemBus->async_method_call( 317 [asyncResp, 318 id](const boost::system::error_code ec, 319 const std::vector<std::pair< 320 std::string, telemetry::TriggerGetParamsVariant>>& 321 ret) { 322 if (ec.value() == EBADR || 323 ec == boost::system::errc::host_unreachable) 324 { 325 messages::resourceNotFound(asyncResp->res, 326 "Triggers", id); 327 return; 328 } 329 if (ec) 330 { 331 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 332 messages::internalError(asyncResp->res); 333 return; 334 } 335 336 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, 337 id, ret)) 338 { 339 messages::internalError(asyncResp->res); 340 } 341 }, 342 telemetry::service, telemetry::getDbusTriggerPath(id), 343 "org.freedesktop.DBus.Properties", "GetAll", 344 telemetry::triggerInterface); 345 }); 346 347 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 348 .privileges(redfish::privileges::deleteTriggers) 349 .methods(boost::beast::http::verb::delete_)( 350 [&app](const crow::Request& req, 351 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 352 const std::string& id) { 353 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 354 { 355 return; 356 } 357 const std::string triggerPath = 358 telemetry::getDbusTriggerPath(id); 359 360 crow::connections::systemBus->async_method_call( 361 [asyncResp, id](const boost::system::error_code ec) { 362 if (ec.value() == EBADR) 363 { 364 messages::resourceNotFound(asyncResp->res, 365 "Triggers", id); 366 return; 367 } 368 369 if (ec) 370 { 371 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 372 messages::internalError(asyncResp->res); 373 return; 374 } 375 376 asyncResp->res.result( 377 boost::beast::http::status::no_content); 378 }, 379 telemetry::service, triggerPath, 380 "xyz.openbmc_project.Object.Delete", "Delete"); 381 }); 382 } 383 384 } // namespace redfish 385