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