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"] = 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* 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 const std::vector<const char*> interfaces{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