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 <map> 17 #include <tuple> 18 #include <variant> 19 20 namespace redfish 21 { 22 23 namespace telemetry 24 { 25 26 constexpr const char* metricReportDefinitionUri = 27 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 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"] = 41 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 42 "MetricReportDefinitions", id) 43 .string(); 44 asyncResp->res.jsonValue["Id"] = id; 45 asyncResp->res.jsonValue["Name"] = id; 46 asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = 47 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 48 "MetricReports", id) 49 .string(); 50 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 51 asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; 52 53 const bool* emitsReadingsUpdate = nullptr; 54 const bool* logToMetricReportsCollection = nullptr; 55 const ReadingParameters* readingParameters = nullptr; 56 const std::string* reportingType = nullptr; 57 const uint64_t* interval = nullptr; 58 59 const bool success = sdbusplus::unpackPropertiesNoThrow( 60 dbus_utils::UnpackErrorPrinter(), ret, "EmitsReadingsUpdate", 61 emitsReadingsUpdate, "LogToMetricReportsCollection", 62 logToMetricReportsCollection, "ReadingParameters", readingParameters, 63 "ReportingType", reportingType, "Interval", interval); 64 65 if (!success) 66 { 67 messages::internalError(asyncResp->res); 68 return; 69 } 70 71 std::vector<std::string> redfishReportActions; 72 redfishReportActions.reserve(2); 73 if (emitsReadingsUpdate != nullptr && *emitsReadingsUpdate) 74 { 75 redfishReportActions.emplace_back("RedfishEvent"); 76 } 77 78 if (logToMetricReportsCollection != nullptr && 79 *logToMetricReportsCollection) 80 { 81 redfishReportActions.emplace_back("LogToMetricReportsCollection"); 82 } 83 84 nlohmann::json metrics = nlohmann::json::array(); 85 if (readingParameters != nullptr) 86 { 87 for (const auto& [sensorPath, operationType, metricId, metadata] : 88 *readingParameters) 89 { 90 nlohmann::json::object_t metric; 91 metric["MetricId"] = metricId; 92 metric["MetricProperties"] = nlohmann::json::array_t({metadata}); 93 metrics.push_back(std::move(metric)); 94 } 95 } 96 97 if (reportingType != nullptr) 98 { 99 asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; 100 } 101 102 if (interval != nullptr) 103 { 104 asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = 105 time_utils::toDurationString(std::chrono::milliseconds(*interval)); 106 } 107 108 asyncResp->res.jsonValue["Metrics"] = metrics; 109 asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; 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>& asyncRespIn) : 257 asyncResp(asyncRespIn), 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 std::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)) 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)) 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