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