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