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