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 <registries/privilege_registry.hpp> 11 12 #include <tuple> 13 #include <variant> 14 15 namespace redfish 16 { 17 18 namespace telemetry 19 { 20 21 using ReadingParameters = 22 std::vector<std::tuple<sdbusplus::message::object_path, std::string, 23 std::string, std::string>>; 24 25 inline void fillReportDefinition( 26 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id, 27 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 28 ret) 29 { 30 asyncResp->res.jsonValue["@odata.type"] = 31 "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; 32 asyncResp->res.jsonValue["@odata.id"] = 33 telemetry::metricReportDefinitionUri + std::string("/") + id; 34 asyncResp->res.jsonValue["Id"] = id; 35 asyncResp->res.jsonValue["Name"] = id; 36 asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = 37 telemetry::metricReportUri + std::string("/") + id; 38 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 39 asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; 40 41 const bool* emitsReadingsUpdate = nullptr; 42 const bool* logToMetricReportsCollection = nullptr; 43 const ReadingParameters* readingParams = nullptr; 44 const std::string* reportingType = nullptr; 45 const uint64_t* interval = nullptr; 46 for (const auto& [key, var] : ret) 47 { 48 if (key == "EmitsReadingsUpdate") 49 { 50 emitsReadingsUpdate = std::get_if<bool>(&var); 51 } 52 else if (key == "LogToMetricReportsCollection") 53 { 54 logToMetricReportsCollection = std::get_if<bool>(&var); 55 } 56 else if (key == "ReadingParameters") 57 { 58 readingParams = std::get_if<ReadingParameters>(&var); 59 } 60 else if (key == "ReportingType") 61 { 62 reportingType = std::get_if<std::string>(&var); 63 } 64 else if (key == "Interval") 65 { 66 interval = std::get_if<uint64_t>(&var); 67 } 68 } 69 if (!emitsReadingsUpdate || !logToMetricReportsCollection || 70 !readingParams || !reportingType || !interval) 71 { 72 BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; 73 messages::internalError(asyncResp->res); 74 return; 75 } 76 77 std::vector<std::string> redfishReportActions; 78 redfishReportActions.reserve(2); 79 if (*emitsReadingsUpdate) 80 { 81 redfishReportActions.emplace_back("RedfishEvent"); 82 } 83 if (*logToMetricReportsCollection) 84 { 85 redfishReportActions.emplace_back("LogToMetricReportsCollection"); 86 } 87 88 nlohmann::json metrics = nlohmann::json::array(); 89 for (const auto& [sensorPath, operationType, id, metadata] : *readingParams) 90 { 91 metrics.push_back({ 92 {"MetricId", id}, 93 {"MetricProperties", {metadata}}, 94 }); 95 } 96 asyncResp->res.jsonValue["Metrics"] = metrics; 97 asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; 98 asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; 99 asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = 100 time_utils::toDurationString(std::chrono::milliseconds(*interval)); 101 } 102 103 struct AddReportArgs 104 { 105 std::string name; 106 std::string reportingType; 107 bool emitsReadingsUpdate = false; 108 bool logToMetricReportsCollection = false; 109 uint64_t interval = 0; 110 std::vector<std::pair<std::string, std::vector<std::string>>> metrics; 111 }; 112 113 inline bool toDbusReportActions(crow::Response& res, 114 std::vector<std::string>& actions, 115 AddReportArgs& args) 116 { 117 size_t index = 0; 118 for (auto& action : actions) 119 { 120 if (action == "RedfishEvent") 121 { 122 args.emitsReadingsUpdate = true; 123 } 124 else if (action == "LogToMetricReportsCollection") 125 { 126 args.logToMetricReportsCollection = true; 127 } 128 else 129 { 130 messages::propertyValueNotInList( 131 res, action, "ReportActions/" + std::to_string(index)); 132 return false; 133 } 134 index++; 135 } 136 return true; 137 } 138 139 inline bool getUserParameters(crow::Response& res, const crow::Request& req, 140 AddReportArgs& args) 141 { 142 std::vector<nlohmann::json> metrics; 143 std::vector<std::string> reportActions; 144 std::optional<nlohmann::json> schedule; 145 if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, 146 "MetricReportDefinitionType", args.reportingType, 147 "ReportActions", reportActions, "Schedule", 148 schedule)) 149 { 150 return false; 151 } 152 153 constexpr const char* allowedCharactersInName = 154 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 155 if (args.name.empty() || args.name.find_first_not_of( 156 allowedCharactersInName) != std::string::npos) 157 { 158 BMCWEB_LOG_ERROR << "Failed to match " << args.name 159 << " with allowed character " 160 << allowedCharactersInName; 161 messages::propertyValueIncorrect(res, "Id", args.name); 162 return false; 163 } 164 165 if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") 166 { 167 messages::propertyValueNotInList(res, args.reportingType, 168 "MetricReportDefinitionType"); 169 return false; 170 } 171 172 if (!toDbusReportActions(res, reportActions, args)) 173 { 174 return false; 175 } 176 177 if (args.reportingType == "Periodic") 178 { 179 if (!schedule) 180 { 181 messages::createFailedMissingReqProperties(res, "Schedule"); 182 return false; 183 } 184 185 std::string durationStr; 186 if (!json_util::readJson(*schedule, res, "RecurrenceInterval", 187 durationStr)) 188 { 189 return false; 190 } 191 192 std::optional<std::chrono::milliseconds> durationNum = 193 time_utils::fromDurationString(durationStr); 194 if (!durationNum) 195 { 196 messages::propertyValueIncorrect(res, "RecurrenceInterval", 197 durationStr); 198 return false; 199 } 200 args.interval = static_cast<uint64_t>(durationNum->count()); 201 } 202 203 args.metrics.reserve(metrics.size()); 204 for (auto& m : metrics) 205 { 206 std::string id; 207 std::vector<std::string> uris; 208 if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", 209 uris)) 210 { 211 return false; 212 } 213 214 args.metrics.emplace_back(std::move(id), std::move(uris)); 215 } 216 217 return true; 218 } 219 220 inline bool getChassisSensorNode( 221 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 222 const std::vector<std::pair<std::string, std::vector<std::string>>>& 223 metrics, 224 boost::container::flat_set<std::pair<std::string, std::string>>& matched) 225 { 226 for (const auto& [id, uris] : metrics) 227 { 228 for (size_t i = 0; i < uris.size(); i++) 229 { 230 const std::string& uri = uris[i]; 231 std::string chassis; 232 std::string node; 233 234 if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || 235 !dbus::utility::getNthStringFromPath(uri, 3, chassis) || 236 !dbus::utility::getNthStringFromPath(uri, 4, node)) 237 { 238 BMCWEB_LOG_ERROR 239 << "Failed to get chassis and sensor Node from " << 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 284 << "Failed to find DBus sensor 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 AddReport(const AddReport&) = delete; 339 AddReport(AddReport&&) = delete; 340 AddReport& operator=(const AddReport&) = delete; 341 AddReport& operator=(AddReport&&) = delete; 342 343 void insert(const boost::container::flat_map<std::string, std::string>& el) 344 { 345 uriToDbus.insert(el.begin(), el.end()); 346 } 347 348 private: 349 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 350 AddReportArgs args; 351 boost::container::flat_map<std::string, std::string> uriToDbus{}; 352 }; 353 } // namespace telemetry 354 355 inline void requestRoutesMetricReportDefinitionCollection(App& app) 356 { 357 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 358 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 359 .methods(boost::beast::http::verb::get)( 360 [](const crow::Request&, 361 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 362 asyncResp->res.jsonValue["@odata.type"] = 363 "#MetricReportDefinitionCollection." 364 "MetricReportDefinitionCollection"; 365 asyncResp->res.jsonValue["@odata.id"] = 366 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 367 asyncResp->res.jsonValue["Name"] = 368 "Metric Definition Collection"; 369 const std::vector<const char*> interfaces{ 370 telemetry::reportInterface}; 371 collection_util::getCollectionMembers( 372 asyncResp, telemetry::metricReportDefinitionUri, interfaces, 373 "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); 374 }); 375 376 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 377 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 378 .methods( 379 boost::beast::http::verb:: 380 post)([](const crow::Request& req, 381 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 382 telemetry::AddReportArgs args; 383 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 384 { 385 return; 386 } 387 388 boost::container::flat_set<std::pair<std::string, std::string>> 389 chassisSensors; 390 if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, 391 chassisSensors)) 392 { 393 return; 394 } 395 396 auto addReportReq = std::make_shared<telemetry::AddReport>( 397 std::move(args), asyncResp); 398 for (const auto& [chassis, sensorType] : chassisSensors) 399 { 400 retrieveUriToDbusMap( 401 chassis, sensorType, 402 [asyncResp, 403 addReportReq](const boost::beast::http::status status, 404 const boost::container::flat_map< 405 std::string, std::string>& uriToDbus) { 406 if (status != boost::beast::http::status::ok) 407 { 408 BMCWEB_LOG_ERROR 409 << "Failed to retrieve URI to dbus sensors map with err " 410 << static_cast<unsigned>(status); 411 return; 412 } 413 addReportReq->insert(uriToDbus); 414 }); 415 } 416 }); 417 } 418 419 inline void requestRoutesMetricReportDefinition(App& app) 420 { 421 BMCWEB_ROUTE(app, 422 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 423 .privileges(redfish::privileges::getMetricReportDefinition) 424 .methods(boost::beast::http::verb::get)( 425 [](const crow::Request&, 426 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 427 const std::string& id) { 428 crow::connections::systemBus->async_method_call( 429 [asyncResp, 430 id](const boost::system::error_code ec, 431 const std::vector<std::pair< 432 std::string, dbus::utility::DbusVariantType>>& 433 ret) { 434 if (ec.value() == EBADR || 435 ec == boost::system::errc::host_unreachable) 436 { 437 messages::resourceNotFound( 438 asyncResp->res, "MetricReportDefinition", id); 439 return; 440 } 441 if (ec) 442 { 443 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 444 messages::internalError(asyncResp->res); 445 return; 446 } 447 448 telemetry::fillReportDefinition(asyncResp, id, ret); 449 }, 450 telemetry::service, telemetry::getDbusReportPath(id), 451 "org.freedesktop.DBus.Properties", "GetAll", 452 telemetry::reportInterface); 453 }); 454 BMCWEB_ROUTE(app, 455 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 456 .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) 457 .methods(boost::beast::http::verb::delete_)( 458 [](const crow::Request&, 459 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 460 const std::string& id) 461 462 { 463 const std::string reportPath = telemetry::getDbusReportPath(id); 464 465 crow::connections::systemBus->async_method_call( 466 [asyncResp, id](const boost::system::error_code ec) { 467 /* 468 * boost::system::errc and std::errc are missing value 469 * for EBADR error that is defined in Linux. 470 */ 471 if (ec.value() == EBADR) 472 { 473 messages::resourceNotFound( 474 asyncResp->res, "MetricReportDefinition", id); 475 return; 476 } 477 478 if (ec) 479 { 480 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 481 messages::internalError(asyncResp->res); 482 return; 483 } 484 485 asyncResp->res.result( 486 boost::beast::http::status::no_content); 487 }, 488 telemetry::service, reportPath, 489 "xyz.openbmc_project.Object.Delete", "Delete"); 490 }); 491 } 492 } // namespace redfish 493