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