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