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