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