1 #pragma once 2 3 #include "sensors.hpp" 4 #include "utils/telemetry_utils.hpp" 5 #include "utils/time_utils.hpp" 6 7 #include <app.hpp> 8 #include <boost/container/flat_map.hpp> 9 #include <dbus_utility.hpp> 10 #include <registries/privilege_registry.hpp> 11 12 #include <tuple> 13 #include <variant> 14 15 namespace redfish 16 { 17 18 namespace telemetry 19 { 20 21 using ReadingParameters = 22 std::vector<std::tuple<sdbusplus::message::object_path, std::string, 23 std::string, std::string>>; 24 25 inline void fillReportDefinition( 26 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, 27 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 28 ret) 29 { 30 asyncResp->res.jsonValue["@odata.type"] = 31 "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; 32 asyncResp->res.jsonValue["@odata.id"] = 33 telemetry::metricReportDefinitionUri + std::string("/") + id; 34 asyncResp->res.jsonValue["Id"] = id; 35 asyncResp->res.jsonValue["Name"] = id; 36 asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = 37 telemetry::metricReportUri + std::string("/") + id; 38 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 39 asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; 40 41 const bool* emitsReadingsUpdate = nullptr; 42 const bool* logToMetricReportsCollection = nullptr; 43 const ReadingParameters* readingParams = nullptr; 44 const std::string* reportingType = nullptr; 45 const uint64_t* interval = nullptr; 46 for (const auto& [key, var] : ret) 47 { 48 if (key == "EmitsReadingsUpdate") 49 { 50 emitsReadingsUpdate = std::get_if<bool>(&var); 51 } 52 else if (key == "LogToMetricReportsCollection") 53 { 54 logToMetricReportsCollection = std::get_if<bool>(&var); 55 } 56 else if (key == "ReadingParameters") 57 { 58 readingParams = std::get_if<ReadingParameters>(&var); 59 } 60 else if (key == "ReportingType") 61 { 62 reportingType = std::get_if<std::string>(&var); 63 } 64 else if (key == "Interval") 65 { 66 interval = std::get_if<uint64_t>(&var); 67 } 68 } 69 if (emitsReadingsUpdate == nullptr || 70 logToMetricReportsCollection == nullptr || readingParams == nullptr || 71 reportingType == nullptr || interval == nullptr) 72 { 73 BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; 74 messages::internalError(asyncResp->res); 75 return; 76 } 77 78 std::vector<std::string> redfishReportActions; 79 redfishReportActions.reserve(2); 80 if (*emitsReadingsUpdate) 81 { 82 redfishReportActions.emplace_back("RedfishEvent"); 83 } 84 if (*logToMetricReportsCollection) 85 { 86 redfishReportActions.emplace_back("LogToMetricReportsCollection"); 87 } 88 89 nlohmann::json metrics = nlohmann::json::array(); 90 for (const auto& [sensorPath, operationType, id, metadata] : *readingParams) 91 { 92 metrics.push_back({ 93 {"MetricId", id}, 94 {"MetricProperties", {metadata}}, 95 }); 96 } 97 asyncResp->res.jsonValue["Metrics"] = metrics; 98 asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; 99 asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; 100 asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = 101 time_utils::toDurationString(std::chrono::milliseconds(*interval)); 102 } 103 104 struct AddReportArgs 105 { 106 std::string name; 107 std::string reportingType; 108 bool emitsReadingsUpdate = false; 109 bool logToMetricReportsCollection = false; 110 uint64_t interval = 0; 111 std::vector<std::pair<std::string, std::vector<std::string>>> metrics; 112 }; 113 114 inline bool toDbusReportActions(crow::Response& res, 115 std::vector<std::string>& actions, 116 AddReportArgs& args) 117 { 118 size_t index = 0; 119 for (auto& action : actions) 120 { 121 if (action == "RedfishEvent") 122 { 123 args.emitsReadingsUpdate = true; 124 } 125 else if (action == "LogToMetricReportsCollection") 126 { 127 args.logToMetricReportsCollection = true; 128 } 129 else 130 { 131 messages::propertyValueNotInList( 132 res, action, "ReportActions/" + std::to_string(index)); 133 return false; 134 } 135 index++; 136 } 137 return true; 138 } 139 140 inline bool getUserParameters(crow::Response& res, const crow::Request& req, 141 AddReportArgs& args) 142 { 143 std::vector<nlohmann::json> metrics; 144 std::vector<std::string> reportActions; 145 std::optional<nlohmann::json> schedule; 146 if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, 147 "MetricReportDefinitionType", args.reportingType, 148 "ReportActions", reportActions, "Schedule", 149 schedule)) 150 { 151 return false; 152 } 153 154 constexpr const char* allowedCharactersInName = 155 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 156 if (args.name.empty() || args.name.find_first_not_of( 157 allowedCharactersInName) != std::string::npos) 158 { 159 BMCWEB_LOG_ERROR << "Failed to match " << args.name 160 << " with allowed character " 161 << allowedCharactersInName; 162 messages::propertyValueIncorrect(res, "Id", args.name); 163 return false; 164 } 165 166 if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") 167 { 168 messages::propertyValueNotInList(res, args.reportingType, 169 "MetricReportDefinitionType"); 170 return false; 171 } 172 173 if (!toDbusReportActions(res, reportActions, args)) 174 { 175 return false; 176 } 177 178 if (args.reportingType == "Periodic") 179 { 180 if (!schedule) 181 { 182 messages::createFailedMissingReqProperties(res, "Schedule"); 183 return false; 184 } 185 186 std::string durationStr; 187 if (!json_util::readJson(*schedule, res, "RecurrenceInterval", 188 durationStr)) 189 { 190 return false; 191 } 192 193 std::optional<std::chrono::milliseconds> durationNum = 194 time_utils::fromDurationString(durationStr); 195 if (!durationNum) 196 { 197 messages::propertyValueIncorrect(res, "RecurrenceInterval", 198 durationStr); 199 return false; 200 } 201 args.interval = static_cast<uint64_t>(durationNum->count()); 202 } 203 204 args.metrics.reserve(metrics.size()); 205 for (auto& m : metrics) 206 { 207 std::string id; 208 std::vector<std::string> uris; 209 if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", 210 uris)) 211 { 212 return false; 213 } 214 215 args.metrics.emplace_back(std::move(id), std::move(uris)); 216 } 217 218 return true; 219 } 220 221 inline bool getChassisSensorNode( 222 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 223 const std::vector<std::pair<std::string, std::vector<std::string>>>& 224 metrics, 225 boost::container::flat_set<std::pair<std::string, std::string>>& matched) 226 { 227 for (const auto& [id, uris] : metrics) 228 { 229 for (size_t i = 0; i < uris.size(); i++) 230 { 231 const std::string& uri = uris[i]; 232 std::string chassis; 233 std::string node; 234 235 if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || 236 !dbus::utility::getNthStringFromPath(uri, 3, chassis) || 237 !dbus::utility::getNthStringFromPath(uri, 4, node)) 238 { 239 BMCWEB_LOG_ERROR 240 << "Failed to get chassis and sensor Node from " << uri; 241 messages::propertyValueIncorrect(asyncResp->res, uri, 242 "MetricProperties/" + 243 std::to_string(i)); 244 return false; 245 } 246 247 if (boost::ends_with(node, "#")) 248 { 249 node.pop_back(); 250 } 251 252 matched.emplace(std::move(chassis), std::move(node)); 253 } 254 } 255 return true; 256 } 257 258 class AddReport 259 { 260 public: 261 AddReport(AddReportArgs argsIn, 262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) : 263 asyncResp(asyncResp), 264 args{std::move(argsIn)} 265 {} 266 ~AddReport() 267 { 268 if (asyncResp->res.result() != boost::beast::http::status::ok) 269 { 270 return; 271 } 272 273 telemetry::ReadingParameters readingParams; 274 readingParams.reserve(args.metrics.size()); 275 276 for (const auto& [id, uris] : args.metrics) 277 { 278 for (size_t i = 0; i < uris.size(); i++) 279 { 280 const std::string& uri = uris[i]; 281 auto el = uriToDbus.find(uri); 282 if (el == uriToDbus.end()) 283 { 284 BMCWEB_LOG_ERROR 285 << "Failed to find DBus sensor corresponding to URI " 286 << uri; 287 messages::propertyValueNotInList(asyncResp->res, uri, 288 "MetricProperties/" + 289 std::to_string(i)); 290 return; 291 } 292 293 const std::string& dbusPath = el->second; 294 readingParams.emplace_back(dbusPath, "SINGLE", id, uri); 295 } 296 } 297 const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp; 298 crow::connections::systemBus->async_method_call( 299 [aResp, name = args.name, uriToDbus = std::move(uriToDbus)]( 300 const boost::system::error_code ec, const std::string&) { 301 if (ec == boost::system::errc::file_exists) 302 { 303 messages::resourceAlreadyExists( 304 aResp->res, "MetricReportDefinition", "Id", name); 305 return; 306 } 307 if (ec == boost::system::errc::too_many_files_open) 308 { 309 messages::createLimitReachedForResource(aResp->res); 310 return; 311 } 312 if (ec == boost::system::errc::argument_list_too_long) 313 { 314 nlohmann::json metricProperties = nlohmann::json::array(); 315 for (const auto& [uri, _] : uriToDbus) 316 { 317 metricProperties.emplace_back(uri); 318 } 319 messages::propertyValueIncorrect( 320 aResp->res, metricProperties, "MetricProperties"); 321 return; 322 } 323 if (ec) 324 { 325 messages::internalError(aResp->res); 326 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 327 return; 328 } 329 330 messages::created(aResp->res); 331 }, 332 telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", 333 "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", 334 "TelemetryService/" + args.name, args.reportingType, 335 args.emitsReadingsUpdate, args.logToMetricReportsCollection, 336 args.interval, readingParams); 337 } 338 339 AddReport(const AddReport&) = delete; 340 AddReport(AddReport&&) = delete; 341 AddReport& operator=(const AddReport&) = delete; 342 AddReport& operator=(AddReport&&) = delete; 343 344 void insert(const boost::container::flat_map<std::string, std::string>& el) 345 { 346 uriToDbus.insert(el.begin(), el.end()); 347 } 348 349 private: 350 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 351 AddReportArgs args; 352 boost::container::flat_map<std::string, std::string> uriToDbus{}; 353 }; 354 } // namespace telemetry 355 356 inline void requestRoutesMetricReportDefinitionCollection(App& app) 357 { 358 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 359 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 360 .methods(boost::beast::http::verb::get)( 361 [](const crow::Request&, 362 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 363 asyncResp->res.jsonValue["@odata.type"] = 364 "#MetricReportDefinitionCollection." 365 "MetricReportDefinitionCollection"; 366 asyncResp->res.jsonValue["@odata.id"] = 367 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 368 asyncResp->res.jsonValue["Name"] = 369 "Metric Definition Collection"; 370 const std::vector<const char*> interfaces{ 371 telemetry::reportInterface}; 372 collection_util::getCollectionMembers( 373 asyncResp, telemetry::metricReportDefinitionUri, interfaces, 374 "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); 375 }); 376 377 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 378 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 379 .methods( 380 boost::beast::http::verb:: 381 post)([](const crow::Request& req, 382 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 383 telemetry::AddReportArgs args; 384 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 385 { 386 return; 387 } 388 389 boost::container::flat_set<std::pair<std::string, std::string>> 390 chassisSensors; 391 if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, 392 chassisSensors)) 393 { 394 return; 395 } 396 397 auto addReportReq = std::make_shared<telemetry::AddReport>( 398 std::move(args), asyncResp); 399 for (const auto& [chassis, sensorType] : chassisSensors) 400 { 401 retrieveUriToDbusMap( 402 chassis, sensorType, 403 [asyncResp, 404 addReportReq](const boost::beast::http::status status, 405 const boost::container::flat_map< 406 std::string, std::string>& uriToDbus) { 407 if (status != boost::beast::http::status::ok) 408 { 409 BMCWEB_LOG_ERROR 410 << "Failed to retrieve URI to dbus sensors map with err " 411 << static_cast<unsigned>(status); 412 return; 413 } 414 addReportReq->insert(uriToDbus); 415 }); 416 } 417 }); 418 } 419 420 inline void requestRoutesMetricReportDefinition(App& app) 421 { 422 BMCWEB_ROUTE(app, 423 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 424 .privileges(redfish::privileges::getMetricReportDefinition) 425 .methods(boost::beast::http::verb::get)( 426 [](const crow::Request&, 427 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 428 const std::string& id) { 429 crow::connections::systemBus->async_method_call( 430 [asyncResp, 431 id](const boost::system::error_code ec, 432 const std::vector<std::pair< 433 std::string, dbus::utility::DbusVariantType>>& 434 ret) { 435 if (ec.value() == EBADR || 436 ec == boost::system::errc::host_unreachable) 437 { 438 messages::resourceNotFound( 439 asyncResp->res, "MetricReportDefinition", id); 440 return; 441 } 442 if (ec) 443 { 444 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 445 messages::internalError(asyncResp->res); 446 return; 447 } 448 449 telemetry::fillReportDefinition(asyncResp, id, ret); 450 }, 451 telemetry::service, telemetry::getDbusReportPath(id), 452 "org.freedesktop.DBus.Properties", "GetAll", 453 telemetry::reportInterface); 454 }); 455 BMCWEB_ROUTE(app, 456 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 457 .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) 458 .methods(boost::beast::http::verb::delete_)( 459 [](const crow::Request&, 460 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 461 const std::string& id) 462 463 { 464 const std::string reportPath = telemetry::getDbusReportPath(id); 465 466 crow::connections::systemBus->async_method_call( 467 [asyncResp, id](const boost::system::error_code ec) { 468 /* 469 * boost::system::errc and std::errc are missing value 470 * for EBADR error that is defined in Linux. 471 */ 472 if (ec.value() == EBADR) 473 { 474 messages::resourceNotFound( 475 asyncResp->res, "MetricReportDefinition", id); 476 return; 477 } 478 479 if (ec) 480 { 481 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 482 messages::internalError(asyncResp->res); 483 return; 484 } 485 486 asyncResp->res.result( 487 boost::beast::http::status::no_content); 488 }, 489 telemetry::service, reportPath, 490 "xyz.openbmc_project.Object.Delete", "Delete"); 491 }); 492 } 493 } // namespace redfish 494