1 #pragma once 2 3 #include "app.hpp" 4 #include "dbus_utility.hpp" 5 #include "generated/enums/metric_report_definition.hpp" 6 #include "query.hpp" 7 #include "registries/privilege_registry.hpp" 8 #include "sensors.hpp" 9 #include "utils/collection.hpp" 10 #include "utils/dbus_utils.hpp" 11 #include "utils/json_utils.hpp" 12 #include "utils/telemetry_utils.hpp" 13 #include "utils/time_utils.hpp" 14 15 #include <boost/container/flat_map.hpp> 16 #include <boost/url/format.hpp> 17 #include <sdbusplus/asio/property.hpp> 18 #include <sdbusplus/unpack_properties.hpp> 19 20 #include <array> 21 #include <map> 22 #include <optional> 23 #include <span> 24 #include <string> 25 #include <string_view> 26 #include <tuple> 27 #include <utility> 28 #include <variant> 29 #include <vector> 30 31 namespace redfish 32 { 33 34 namespace telemetry 35 { 36 37 using ReadingParameters = std::vector<std::tuple< 38 std::vector<std::tuple<sdbusplus::message::object_path, std::string>>, 39 std::string, std::string, uint64_t>>; 40 41 inline bool verifyCommonErrors(crow::Response& res, const std::string& id, 42 const boost::system::error_code& ec) 43 { 44 if (ec.value() == EBADR || ec == boost::system::errc::host_unreachable) 45 { 46 messages::resourceNotFound(res, "MetricReportDefinition", id); 47 return false; 48 } 49 50 if (ec == boost::system::errc::file_exists) 51 { 52 messages::resourceAlreadyExists(res, "MetricReportDefinition", "Id", 53 id); 54 return false; 55 } 56 57 if (ec == boost::system::errc::too_many_files_open) 58 { 59 messages::createLimitReachedForResource(res); 60 return false; 61 } 62 63 if (ec) 64 { 65 BMCWEB_LOG_ERROR("DBUS response error {}", ec); 66 messages::internalError(res); 67 return false; 68 } 69 70 return true; 71 } 72 73 inline metric_report_definition::ReportActionsEnum 74 toRedfishReportAction(std::string_view dbusValue) 75 { 76 if (dbusValue == 77 "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate") 78 { 79 return metric_report_definition::ReportActionsEnum::RedfishEvent; 80 } 81 if (dbusValue == 82 "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection") 83 { 84 return metric_report_definition::ReportActionsEnum:: 85 LogToMetricReportsCollection; 86 } 87 return metric_report_definition::ReportActionsEnum::Invalid; 88 } 89 90 inline std::string toDbusReportAction(std::string_view redfishValue) 91 { 92 if (redfishValue == "RedfishEvent") 93 { 94 return "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate"; 95 } 96 if (redfishValue == "LogToMetricReportsCollection") 97 { 98 return "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection"; 99 } 100 return ""; 101 } 102 103 inline metric_report_definition::MetricReportDefinitionType 104 toRedfishReportingType(std::string_view dbusValue) 105 { 106 if (dbusValue == 107 "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange") 108 { 109 return metric_report_definition::MetricReportDefinitionType::OnChange; 110 } 111 if (dbusValue == 112 "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest") 113 { 114 return metric_report_definition::MetricReportDefinitionType::OnRequest; 115 } 116 if (dbusValue == 117 "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic") 118 { 119 return metric_report_definition::MetricReportDefinitionType::Periodic; 120 } 121 return metric_report_definition::MetricReportDefinitionType::Invalid; 122 } 123 124 inline std::string toDbusReportingType(std::string_view redfishValue) 125 { 126 if (redfishValue == "OnChange") 127 { 128 return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange"; 129 } 130 if (redfishValue == "OnRequest") 131 { 132 return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest"; 133 } 134 if (redfishValue == "Periodic") 135 { 136 return "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic"; 137 } 138 return ""; 139 } 140 141 inline metric_report_definition::CollectionTimeScope 142 toRedfishCollectionTimeScope(std::string_view dbusValue) 143 { 144 if (dbusValue == 145 "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point") 146 { 147 return metric_report_definition::CollectionTimeScope::Point; 148 } 149 if (dbusValue == 150 "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval") 151 { 152 return metric_report_definition::CollectionTimeScope::Interval; 153 } 154 if (dbusValue == 155 "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval") 156 { 157 return metric_report_definition::CollectionTimeScope::StartupInterval; 158 } 159 return metric_report_definition::CollectionTimeScope::Invalid; 160 } 161 162 inline std::string toDbusCollectionTimeScope(std::string_view redfishValue) 163 { 164 if (redfishValue == "Point") 165 { 166 return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point"; 167 } 168 if (redfishValue == "Interval") 169 { 170 return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval"; 171 } 172 if (redfishValue == "StartupInterval") 173 { 174 return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval"; 175 } 176 return ""; 177 } 178 179 inline metric_report_definition::ReportUpdatesEnum 180 toRedfishReportUpdates(std::string_view dbusValue) 181 { 182 if (dbusValue == 183 "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite") 184 { 185 return metric_report_definition::ReportUpdatesEnum::Overwrite; 186 } 187 if (dbusValue == 188 "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull") 189 { 190 return metric_report_definition::ReportUpdatesEnum::AppendWrapsWhenFull; 191 } 192 if (dbusValue == 193 "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull") 194 { 195 return metric_report_definition::ReportUpdatesEnum::AppendStopsWhenFull; 196 } 197 return metric_report_definition::ReportUpdatesEnum::Invalid; 198 } 199 200 inline std::string toDbusReportUpdates(std::string_view redfishValue) 201 { 202 if (redfishValue == "Overwrite") 203 { 204 return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite"; 205 } 206 if (redfishValue == "AppendWrapsWhenFull") 207 { 208 return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull"; 209 } 210 if (redfishValue == "AppendStopsWhenFull") 211 { 212 return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull"; 213 } 214 return ""; 215 } 216 217 inline std::optional<nlohmann::json::array_t> getLinkedTriggers( 218 std::span<const sdbusplus::message::object_path> triggerPaths) 219 { 220 nlohmann::json::array_t triggers; 221 222 for (const sdbusplus::message::object_path& path : triggerPaths) 223 { 224 if (path.parent_path() != 225 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService") 226 { 227 BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}", 228 path.str); 229 return std::nullopt; 230 } 231 232 std::string id = path.filename(); 233 if (id.empty()) 234 { 235 BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}", 236 path.str); 237 return std::nullopt; 238 } 239 nlohmann::json::object_t trigger; 240 trigger["@odata.id"] = 241 boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id); 242 triggers.emplace_back(std::move(trigger)); 243 } 244 245 return triggers; 246 } 247 248 inline void 249 fillReportDefinition(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 250 const std::string& id, 251 const dbus::utility::DBusPropertiesMap& properties) 252 { 253 std::vector<std::string> reportActions; 254 ReadingParameters readingParams; 255 std::string reportingType; 256 std::string reportUpdates; 257 std::string name; 258 uint64_t appendLimit = 0; 259 uint64_t interval = 0; 260 bool enabled = false; 261 std::vector<sdbusplus::message::object_path> triggers; 262 263 const bool success = sdbusplus::unpackPropertiesNoThrow( 264 dbus_utils::UnpackErrorPrinter(), properties, "ReportingType", 265 reportingType, "Interval", interval, "ReportActions", reportActions, 266 "ReportUpdates", reportUpdates, "AppendLimit", appendLimit, 267 "ReadingParameters", readingParams, "Name", name, "Enabled", enabled, 268 "Triggers", triggers); 269 270 if (!success) 271 { 272 messages::internalError(asyncResp->res); 273 return; 274 } 275 276 metric_report_definition::MetricReportDefinitionType redfishReportingType = 277 toRedfishReportingType(reportingType); 278 if (redfishReportingType == 279 metric_report_definition::MetricReportDefinitionType::Invalid) 280 { 281 messages::internalError(asyncResp->res); 282 return; 283 } 284 285 asyncResp->res.jsonValue["MetricReportDefinitionType"] = 286 redfishReportingType; 287 288 std::optional<nlohmann::json::array_t> linkedTriggers = 289 getLinkedTriggers(triggers); 290 if (!linkedTriggers) 291 { 292 messages::internalError(asyncResp->res); 293 return; 294 } 295 296 asyncResp->res.jsonValue["Links"]["Triggers"] = std::move(*linkedTriggers); 297 298 nlohmann::json::array_t redfishReportActions; 299 for (const std::string& action : reportActions) 300 { 301 metric_report_definition::ReportActionsEnum redfishAction = 302 toRedfishReportAction(action); 303 if (redfishAction == 304 metric_report_definition::ReportActionsEnum::Invalid) 305 { 306 messages::internalError(asyncResp->res); 307 return; 308 } 309 310 redfishReportActions.emplace_back(redfishAction); 311 } 312 313 asyncResp->res.jsonValue["ReportActions"] = std::move(redfishReportActions); 314 315 nlohmann::json::array_t metrics = nlohmann::json::array(); 316 for (const auto& [sensorData, collectionFunction, collectionTimeScope, 317 collectionDuration] : readingParams) 318 { 319 nlohmann::json::array_t metricProperties; 320 321 for (const auto& [sensorPath, sensorMetadata] : sensorData) 322 { 323 metricProperties.emplace_back(sensorMetadata); 324 } 325 326 nlohmann::json::object_t metric; 327 328 metric_report_definition::CalculationAlgorithmEnum 329 redfishCollectionFunction = 330 telemetry::toRedfishCollectionFunction(collectionFunction); 331 if (redfishCollectionFunction == 332 metric_report_definition::CalculationAlgorithmEnum::Invalid) 333 { 334 messages::internalError(asyncResp->res); 335 return; 336 } 337 metric["CollectionFunction"] = redfishCollectionFunction; 338 339 metric_report_definition::CollectionTimeScope 340 redfishCollectionTimeScope = 341 toRedfishCollectionTimeScope(collectionTimeScope); 342 if (redfishCollectionTimeScope == 343 metric_report_definition::CollectionTimeScope::Invalid) 344 { 345 messages::internalError(asyncResp->res); 346 return; 347 } 348 metric["CollectionTimeScope"] = redfishCollectionTimeScope; 349 350 metric["MetricProperties"] = std::move(metricProperties); 351 metric["CollectionDuration"] = time_utils::toDurationString( 352 std::chrono::milliseconds(collectionDuration)); 353 metrics.emplace_back(std::move(metric)); 354 } 355 asyncResp->res.jsonValue["Metrics"] = std::move(metrics); 356 357 if (enabled) 358 { 359 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 360 } 361 else 362 { 363 asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; 364 } 365 366 metric_report_definition::ReportUpdatesEnum redfishReportUpdates = 367 toRedfishReportUpdates(reportUpdates); 368 if (redfishReportUpdates == 369 metric_report_definition::ReportUpdatesEnum::Invalid) 370 { 371 messages::internalError(asyncResp->res); 372 return; 373 } 374 asyncResp->res.jsonValue["ReportUpdates"] = redfishReportUpdates; 375 376 asyncResp->res.jsonValue["MetricReportDefinitionEnabled"] = enabled; 377 asyncResp->res.jsonValue["AppendLimit"] = appendLimit; 378 asyncResp->res.jsonValue["Name"] = name; 379 asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = 380 time_utils::toDurationString(std::chrono::milliseconds(interval)); 381 asyncResp->res.jsonValue["@odata.type"] = 382 "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; 383 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 384 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", id); 385 asyncResp->res.jsonValue["Id"] = id; 386 asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = boost::urls::format( 387 "/redfish/v1/TelemetryService/MetricReports/{}", id); 388 } 389 390 struct AddReportArgs 391 { 392 struct MetricArgs 393 { 394 std::vector<std::string> uris; 395 std::string collectionFunction; 396 std::string collectionTimeScope; 397 uint64_t collectionDuration = 0; 398 }; 399 400 std::string id; 401 std::string name; 402 std::string reportingType; 403 std::string reportUpdates; 404 uint64_t appendLimit = std::numeric_limits<uint64_t>::max(); 405 std::vector<std::string> reportActions; 406 uint64_t interval = std::numeric_limits<uint64_t>::max(); 407 std::vector<MetricArgs> metrics; 408 bool metricReportDefinitionEnabled = true; 409 }; 410 411 inline bool toDbusReportActions(crow::Response& res, 412 const std::vector<std::string>& actions, 413 std::vector<std::string>& outReportActions) 414 { 415 size_t index = 0; 416 for (const std::string& action : actions) 417 { 418 std::string dbusReportAction = toDbusReportAction(action); 419 if (dbusReportAction.empty()) 420 { 421 messages::propertyValueNotInList( 422 res, action, "ReportActions/" + std::to_string(index)); 423 return false; 424 } 425 426 outReportActions.emplace_back(std::move(dbusReportAction)); 427 index++; 428 } 429 return true; 430 } 431 432 inline bool getUserMetric(crow::Response& res, nlohmann::json::object_t& metric, 433 AddReportArgs::MetricArgs& metricArgs) 434 { 435 std::optional<std::vector<std::string>> uris; 436 std::optional<std::string> collectionDurationStr; 437 std::optional<std::string> collectionFunction; 438 std::optional<std::string> collectionTimeScopeStr; 439 440 if (!json_util::readJsonObject( 441 metric, res, "MetricProperties", uris, "CollectionFunction", 442 collectionFunction, "CollectionTimeScope", collectionTimeScopeStr, 443 "CollectionDuration", collectionDurationStr)) 444 { 445 return false; 446 } 447 448 if (uris) 449 { 450 metricArgs.uris = std::move(*uris); 451 } 452 453 if (collectionFunction) 454 { 455 std::string dbusCollectionFunction = 456 telemetry::toDbusCollectionFunction(*collectionFunction); 457 if (dbusCollectionFunction.empty()) 458 { 459 messages::propertyValueIncorrect(res, "CollectionFunction", 460 *collectionFunction); 461 return false; 462 } 463 metricArgs.collectionFunction = std::move(dbusCollectionFunction); 464 } 465 466 if (collectionTimeScopeStr) 467 { 468 std::string dbusCollectionTimeScope = 469 toDbusCollectionTimeScope(*collectionTimeScopeStr); 470 if (dbusCollectionTimeScope.empty()) 471 { 472 messages::propertyValueIncorrect(res, "CollectionTimeScope", 473 *collectionTimeScopeStr); 474 return false; 475 } 476 metricArgs.collectionTimeScope = std::move(dbusCollectionTimeScope); 477 } 478 479 if (collectionDurationStr) 480 { 481 std::optional<std::chrono::milliseconds> duration = 482 time_utils::fromDurationString(*collectionDurationStr); 483 484 if (!duration || duration->count() < 0) 485 { 486 messages::propertyValueIncorrect(res, "CollectionDuration", 487 *collectionDurationStr); 488 return false; 489 } 490 491 metricArgs.collectionDuration = 492 static_cast<uint64_t>(duration->count()); 493 } 494 495 return true; 496 } 497 498 inline bool getUserMetrics(crow::Response& res, 499 std::span<nlohmann::json::object_t> metrics, 500 std::vector<AddReportArgs::MetricArgs>& result) 501 { 502 result.reserve(metrics.size()); 503 504 for (nlohmann::json::object_t& m : metrics) 505 { 506 AddReportArgs::MetricArgs metricArgs; 507 508 if (!getUserMetric(res, m, metricArgs)) 509 { 510 return false; 511 } 512 513 result.emplace_back(std::move(metricArgs)); 514 } 515 516 return true; 517 } 518 519 inline bool getUserParameters(crow::Response& res, const crow::Request& req, 520 AddReportArgs& args) 521 { 522 std::optional<std::string> id; 523 std::optional<std::string> name; 524 std::optional<std::string> reportingTypeStr; 525 std::optional<std::string> reportUpdatesStr; 526 std::optional<uint64_t> appendLimit; 527 std::optional<bool> metricReportDefinitionEnabled; 528 std::optional<std::vector<nlohmann::json::object_t>> metrics; 529 std::optional<std::vector<std::string>> reportActionsStr; 530 std::optional<std::string> scheduleDurationStr; 531 532 if (!json_util::readJsonPatch( 533 req, res, "Id", id, "Name", name, "Metrics", metrics, 534 "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates", 535 reportUpdatesStr, "AppendLimit", appendLimit, "ReportActions", 536 reportActionsStr, "Schedule/RecurrenceInterval", 537 scheduleDurationStr, "MetricReportDefinitionEnabled", 538 metricReportDefinitionEnabled)) 539 { 540 return false; 541 } 542 543 if (id) 544 { 545 constexpr const char* allowedCharactersInId = 546 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 547 if (id->empty() || 548 id->find_first_not_of(allowedCharactersInId) != std::string::npos) 549 { 550 messages::propertyValueIncorrect(res, "Id", *id); 551 return false; 552 } 553 args.id = *id; 554 } 555 556 if (name) 557 { 558 args.name = *name; 559 } 560 561 if (reportingTypeStr) 562 { 563 std::string dbusReportingType = toDbusReportingType(*reportingTypeStr); 564 if (dbusReportingType.empty()) 565 { 566 messages::propertyValueNotInList(res, *reportingTypeStr, 567 "MetricReportDefinitionType"); 568 return false; 569 } 570 args.reportingType = dbusReportingType; 571 } 572 573 if (reportUpdatesStr) 574 { 575 std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr); 576 if (dbusReportUpdates.empty()) 577 { 578 messages::propertyValueNotInList(res, *reportUpdatesStr, 579 "ReportUpdates"); 580 return false; 581 } 582 args.reportUpdates = dbusReportUpdates; 583 } 584 585 if (appendLimit) 586 { 587 args.appendLimit = *appendLimit; 588 } 589 590 if (metricReportDefinitionEnabled) 591 { 592 args.metricReportDefinitionEnabled = *metricReportDefinitionEnabled; 593 } 594 595 if (reportActionsStr) 596 { 597 if (!toDbusReportActions(res, *reportActionsStr, args.reportActions)) 598 { 599 return false; 600 } 601 } 602 603 if (reportingTypeStr == "Periodic") 604 { 605 if (!scheduleDurationStr) 606 { 607 messages::createFailedMissingReqProperties(res, "Schedule"); 608 return false; 609 } 610 611 std::optional<std::chrono::milliseconds> durationNum = 612 time_utils::fromDurationString(*scheduleDurationStr); 613 if (!durationNum || durationNum->count() < 0) 614 { 615 messages::propertyValueIncorrect(res, "RecurrenceInterval", 616 *scheduleDurationStr); 617 return false; 618 } 619 args.interval = static_cast<uint64_t>(durationNum->count()); 620 } 621 622 if (metrics) 623 { 624 if (!getUserMetrics(res, *metrics, args.metrics)) 625 { 626 return false; 627 } 628 } 629 630 return true; 631 } 632 633 inline bool getChassisSensorNodeFromMetrics( 634 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 635 std::span<const AddReportArgs::MetricArgs> metrics, 636 boost::container::flat_set<std::pair<std::string, std::string>>& matched) 637 { 638 for (const auto& metric : metrics) 639 { 640 std::optional<IncorrectMetricUri> error = 641 getChassisSensorNode(metric.uris, matched); 642 if (error) 643 { 644 messages::propertyValueIncorrect(asyncResp->res, error->uri, 645 "MetricProperties/" + 646 std::to_string(error->index)); 647 return false; 648 } 649 } 650 return true; 651 } 652 653 class AddReport 654 { 655 public: 656 AddReport(AddReportArgs&& argsIn, 657 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) : 658 asyncResp(asyncRespIn), 659 args(std::move(argsIn)) 660 {} 661 662 ~AddReport() 663 { 664 boost::asio::post(crow::connections::systemBus->get_io_context(), 665 std::bind_front(&performAddReport, asyncResp, args, 666 std::move(uriToDbus))); 667 } 668 669 static void performAddReport( 670 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 671 const AddReportArgs& args, 672 const boost::container::flat_map<std::string, std::string>& uriToDbus) 673 { 674 if (asyncResp->res.result() != boost::beast::http::status::ok) 675 { 676 return; 677 } 678 679 telemetry::ReadingParameters readingParams; 680 readingParams.reserve(args.metrics.size()); 681 682 for (const auto& metric : args.metrics) 683 { 684 std::vector< 685 std::tuple<sdbusplus::message::object_path, std::string>> 686 sensorParams; 687 sensorParams.reserve(metric.uris.size()); 688 689 for (size_t i = 0; i < metric.uris.size(); i++) 690 { 691 const std::string& uri = metric.uris[i]; 692 auto el = uriToDbus.find(uri); 693 if (el == uriToDbus.end()) 694 { 695 BMCWEB_LOG_ERROR( 696 "Failed to find DBus sensor corresponding to URI {}", 697 uri); 698 messages::propertyValueNotInList(asyncResp->res, uri, 699 "MetricProperties/" + 700 std::to_string(i)); 701 return; 702 } 703 704 const std::string& dbusPath = el->second; 705 sensorParams.emplace_back(dbusPath, uri); 706 } 707 708 readingParams.emplace_back( 709 std::move(sensorParams), metric.collectionFunction, 710 metric.collectionTimeScope, metric.collectionDuration); 711 } 712 713 crow::connections::systemBus->async_method_call( 714 [asyncResp, id = args.id, uriToDbus]( 715 const boost::system::error_code& ec, const std::string&) { 716 if (ec == boost::system::errc::file_exists) 717 { 718 messages::resourceAlreadyExists( 719 asyncResp->res, "MetricReportDefinition", "Id", id); 720 return; 721 } 722 if (ec == boost::system::errc::too_many_files_open) 723 { 724 messages::createLimitReachedForResource(asyncResp->res); 725 return; 726 } 727 if (ec == boost::system::errc::argument_list_too_long) 728 { 729 nlohmann::json metricProperties = nlohmann::json::array(); 730 for (const auto& [uri, _] : uriToDbus) 731 { 732 metricProperties.emplace_back(uri); 733 } 734 messages::propertyValueIncorrect( 735 asyncResp->res, "MetricProperties", metricProperties); 736 return; 737 } 738 if (ec) 739 { 740 messages::internalError(asyncResp->res); 741 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 742 return; 743 } 744 745 messages::created(asyncResp->res); 746 }, 747 telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", 748 "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", 749 "TelemetryService/" + args.id, args.name, args.reportingType, 750 args.reportUpdates, args.appendLimit, args.reportActions, 751 args.interval, readingParams, args.metricReportDefinitionEnabled); 752 } 753 754 AddReport(const AddReport&) = delete; 755 AddReport(AddReport&&) = delete; 756 AddReport& operator=(const AddReport&) = delete; 757 AddReport& operator=(AddReport&&) = delete; 758 759 void insert(const std::map<std::string, std::string>& el) 760 { 761 uriToDbus.insert(el.begin(), el.end()); 762 } 763 764 private: 765 std::shared_ptr<bmcweb::AsyncResp> asyncResp; 766 AddReportArgs args; 767 boost::container::flat_map<std::string, std::string> uriToDbus; 768 }; 769 770 class UpdateMetrics 771 { 772 public: 773 UpdateMetrics(std::string_view idIn, 774 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) : 775 id(idIn), 776 asyncResp(asyncRespIn) 777 {} 778 779 ~UpdateMetrics() 780 { 781 try 782 { 783 setReadingParams(); 784 } 785 catch (const std::exception& e) 786 { 787 BMCWEB_LOG_ERROR("{}", e.what()); 788 } 789 catch (...) 790 { 791 BMCWEB_LOG_ERROR("Unknown error"); 792 } 793 } 794 795 UpdateMetrics(const UpdateMetrics&) = delete; 796 UpdateMetrics(UpdateMetrics&&) = delete; 797 UpdateMetrics& operator=(const UpdateMetrics&) = delete; 798 UpdateMetrics& operator=(UpdateMetrics&&) = delete; 799 800 std::string id; 801 std::map<std::string, std::string> metricPropertyToDbusPaths; 802 803 void insert(const std::map<std::string, std::string>& 804 additionalMetricPropertyToDbusPaths) 805 { 806 metricPropertyToDbusPaths.insert( 807 additionalMetricPropertyToDbusPaths.begin(), 808 additionalMetricPropertyToDbusPaths.end()); 809 } 810 811 void emplace(std::span<const std::tuple<sdbusplus::message::object_path, 812 std::string>> 813 pathAndUri, 814 const AddReportArgs::MetricArgs& metricArgs) 815 { 816 readingParamsUris.emplace_back(metricArgs.uris); 817 readingParams.emplace_back( 818 std::vector(pathAndUri.begin(), pathAndUri.end()), 819 metricArgs.collectionFunction, metricArgs.collectionTimeScope, 820 metricArgs.collectionDuration); 821 } 822 823 void setReadingParams() 824 { 825 if (asyncResp->res.result() != boost::beast::http::status::ok) 826 { 827 return; 828 } 829 830 for (size_t index = 0; index < readingParamsUris.size(); ++index) 831 { 832 std::span<const std::string> newUris = readingParamsUris[index]; 833 834 const std::optional<std::vector< 835 std::tuple<sdbusplus::message::object_path, std::string>>> 836 readingParam = sensorPathToUri(newUris); 837 838 if (!readingParam) 839 { 840 return; 841 } 842 843 std::get<0>(readingParams[index]) = *readingParam; 844 } 845 846 crow::connections::systemBus->async_method_call( 847 [asyncResp(this->asyncResp), 848 reportId = id](const boost::system::error_code& ec) { 849 if (!verifyCommonErrors(asyncResp->res, reportId, ec)) 850 { 851 return; 852 } 853 }, 854 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 855 "org.freedesktop.DBus.Properties", "Set", 856 "xyz.openbmc_project.Telemetry.Report", "ReadingParameters", 857 dbus::utility::DbusVariantType{readingParams}); 858 } 859 860 private: 861 std::optional< 862 std::vector<std::tuple<sdbusplus::message::object_path, std::string>>> 863 sensorPathToUri(std::span<const std::string> uris) const 864 { 865 std::vector<std::tuple<sdbusplus::message::object_path, std::string>> 866 result; 867 868 for (const std::string& uri : uris) 869 { 870 auto it = metricPropertyToDbusPaths.find(uri); 871 if (it == metricPropertyToDbusPaths.end()) 872 { 873 messages::propertyValueNotInList(asyncResp->res, uri, 874 "MetricProperties"); 875 return {}; 876 } 877 result.emplace_back(it->second, uri); 878 } 879 880 return result; 881 } 882 883 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 884 std::vector<std::vector<std::string>> readingParamsUris; 885 ReadingParameters readingParams; 886 }; 887 888 inline void 889 setReportEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 890 std::string_view id, bool enabled) 891 { 892 crow::connections::systemBus->async_method_call( 893 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 894 if (!verifyCommonErrors(asyncResp->res, id, ec)) 895 { 896 return; 897 } 898 }, 899 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 900 "org.freedesktop.DBus.Properties", "Set", 901 "xyz.openbmc_project.Telemetry.Report", "Enabled", 902 dbus::utility::DbusVariantType{enabled}); 903 } 904 905 inline void setReportTypeAndInterval( 906 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id, 907 const std::string& reportingType, uint64_t recurrenceInterval) 908 { 909 crow::connections::systemBus->async_method_call( 910 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 911 if (!verifyCommonErrors(asyncResp->res, id, ec)) 912 { 913 return; 914 } 915 }, 916 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 917 "xyz.openbmc_project.Telemetry.Report", "SetReportingProperties", 918 reportingType, recurrenceInterval); 919 } 920 921 inline void 922 setReportUpdates(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 923 std::string_view id, const std::string& reportUpdates) 924 { 925 crow::connections::systemBus->async_method_call( 926 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 927 if (!verifyCommonErrors(asyncResp->res, id, ec)) 928 { 929 return; 930 } 931 }, 932 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 933 "org.freedesktop.DBus.Properties", "Set", 934 "xyz.openbmc_project.Telemetry.Report", "ReportUpdates", 935 dbus::utility::DbusVariantType{reportUpdates}); 936 } 937 938 inline void 939 setReportActions(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 940 std::string_view id, 941 const std::vector<std::string>& dbusReportActions) 942 { 943 crow::connections::systemBus->async_method_call( 944 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 945 if (!verifyCommonErrors(asyncResp->res, id, ec)) 946 { 947 return; 948 } 949 }, 950 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 951 "org.freedesktop.DBus.Properties", "Set", 952 "xyz.openbmc_project.Telemetry.Report", "ReportActions", 953 dbus::utility::DbusVariantType{dbusReportActions}); 954 } 955 956 inline void 957 setReportMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 958 std::string_view id, 959 std::span<nlohmann::json::object_t> metrics) 960 { 961 sdbusplus::asio::getAllProperties( 962 *crow::connections::systemBus, telemetry::service, 963 telemetry::getDbusReportPath(id), telemetry::reportInterface, 964 [asyncResp, id = std::string(id), 965 redfishMetrics = std::vector<nlohmann::json::object_t>(metrics.begin(), 966 metrics.end())]( 967 boost::system::error_code ec, 968 const dbus::utility::DBusPropertiesMap& properties) mutable { 969 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 970 { 971 return; 972 } 973 974 ReadingParameters readingParams; 975 976 const bool success = sdbusplus::unpackPropertiesNoThrow( 977 dbus_utils::UnpackErrorPrinter(), properties, "ReadingParameters", 978 readingParams); 979 980 if (!success) 981 { 982 messages::internalError(asyncResp->res); 983 return; 984 } 985 986 auto updateMetricsReq = std::make_shared<UpdateMetrics>(id, asyncResp); 987 988 boost::container::flat_set<std::pair<std::string, std::string>> 989 chassisSensors; 990 991 size_t index = 0; 992 for (nlohmann::json::object_t& metric : redfishMetrics) 993 { 994 AddReportArgs::MetricArgs metricArgs; 995 std::vector< 996 std::tuple<sdbusplus::message::object_path, std::string>> 997 pathAndUri; 998 999 if (index < readingParams.size()) 1000 { 1001 const ReadingParameters::value_type& existing = 1002 readingParams[index]; 1003 1004 pathAndUri = std::get<0>(existing); 1005 metricArgs.collectionFunction = std::get<1>(existing); 1006 metricArgs.collectionTimeScope = std::get<2>(existing); 1007 metricArgs.collectionDuration = std::get<3>(existing); 1008 } 1009 1010 if (!getUserMetric(asyncResp->res, metric, metricArgs)) 1011 { 1012 return; 1013 } 1014 1015 std::optional<IncorrectMetricUri> error = 1016 getChassisSensorNode(metricArgs.uris, chassisSensors); 1017 1018 if (error) 1019 { 1020 messages::propertyValueIncorrect( 1021 asyncResp->res, error->uri, 1022 "MetricProperties/" + std::to_string(error->index)); 1023 return; 1024 } 1025 1026 updateMetricsReq->emplace(pathAndUri, metricArgs); 1027 index++; 1028 } 1029 1030 for (const auto& [chassis, sensorType] : chassisSensors) 1031 { 1032 retrieveUriToDbusMap( 1033 chassis, sensorType, 1034 [asyncResp, updateMetricsReq]( 1035 const boost::beast::http::status status, 1036 const std::map<std::string, std::string>& uriToDbus) { 1037 if (status != boost::beast::http::status::ok) 1038 { 1039 BMCWEB_LOG_ERROR( 1040 "Failed to retrieve URI to dbus sensors map with err {}", 1041 static_cast<unsigned>(status)); 1042 return; 1043 } 1044 updateMetricsReq->insert(uriToDbus); 1045 }); 1046 } 1047 }); 1048 } 1049 1050 inline void handleMetricReportDefinitionCollectionHead( 1051 App& app, const crow::Request& req, 1052 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1053 { 1054 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1055 { 1056 return; 1057 } 1058 asyncResp->res.addHeader( 1059 boost::beast::http::field::link, 1060 "</redfish/v1/JsonSchemas/MetricReportDefinitionCollection/MetricReportDefinitionCollection.json>; rel=describedby"); 1061 } 1062 1063 inline void handleMetricReportDefinitionCollectionGet( 1064 App& app, const crow::Request& req, 1065 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1066 { 1067 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1068 { 1069 return; 1070 } 1071 asyncResp->res.addHeader( 1072 boost::beast::http::field::link, 1073 "</redfish/v1/JsonSchemas/MetricReportDefinition/MetricReportDefinition.json>; rel=describedby"); 1074 1075 asyncResp->res.jsonValue["@odata.type"] = 1076 "#MetricReportDefinitionCollection." 1077 "MetricReportDefinitionCollection"; 1078 asyncResp->res.jsonValue["@odata.id"] = 1079 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 1080 asyncResp->res.jsonValue["Name"] = "Metric Definition Collection"; 1081 constexpr std::array<std::string_view, 1> interfaces{ 1082 telemetry::reportInterface}; 1083 collection_util::getCollectionMembers( 1084 asyncResp, 1085 boost::urls::url( 1086 "/redfish/v1/TelemetryService/MetricReportDefinitions"), 1087 interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); 1088 } 1089 1090 inline void 1091 handleReportPatch(App& app, const crow::Request& req, 1092 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1093 std::string_view id) 1094 { 1095 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1096 { 1097 return; 1098 } 1099 1100 std::optional<std::string> reportingTypeStr; 1101 std::optional<std::string> reportUpdatesStr; 1102 std::optional<bool> metricReportDefinitionEnabled; 1103 std::optional<std::vector<nlohmann::json::object_t>> metrics; 1104 std::optional<std::vector<std::string>> reportActionsStr; 1105 std::optional<std::string> scheduleDurationStr; 1106 1107 if (!json_util::readJsonPatch( 1108 req, asyncResp->res, "Metrics", metrics, 1109 "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates", 1110 reportUpdatesStr, "ReportActions", reportActionsStr, 1111 "Schedule/RecurrenceInterval", scheduleDurationStr, 1112 "MetricReportDefinitionEnabled", metricReportDefinitionEnabled)) 1113 { 1114 return; 1115 } 1116 1117 if (metricReportDefinitionEnabled) 1118 { 1119 setReportEnabled(asyncResp, id, *metricReportDefinitionEnabled); 1120 } 1121 1122 if (reportUpdatesStr) 1123 { 1124 std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr); 1125 if (dbusReportUpdates.empty()) 1126 { 1127 messages::propertyValueNotInList(asyncResp->res, *reportUpdatesStr, 1128 "ReportUpdates"); 1129 return; 1130 } 1131 setReportUpdates(asyncResp, id, dbusReportUpdates); 1132 } 1133 1134 if (reportActionsStr) 1135 { 1136 std::vector<std::string> dbusReportActions; 1137 if (!toDbusReportActions(asyncResp->res, *reportActionsStr, 1138 dbusReportActions)) 1139 { 1140 return; 1141 } 1142 setReportActions(asyncResp, id, dbusReportActions); 1143 } 1144 1145 if (reportingTypeStr || scheduleDurationStr) 1146 { 1147 std::string dbusReportingType; 1148 if (reportingTypeStr) 1149 { 1150 dbusReportingType = toDbusReportingType(*reportingTypeStr); 1151 if (dbusReportingType.empty()) 1152 { 1153 messages::propertyValueNotInList(asyncResp->res, 1154 *reportingTypeStr, 1155 "MetricReportDefinitionType"); 1156 return; 1157 } 1158 } 1159 1160 uint64_t recurrenceInterval = std::numeric_limits<uint64_t>::max(); 1161 if (scheduleDurationStr) 1162 { 1163 std::optional<std::chrono::milliseconds> durationNum = 1164 time_utils::fromDurationString(*scheduleDurationStr); 1165 if (!durationNum || durationNum->count() < 0) 1166 { 1167 messages::propertyValueIncorrect( 1168 asyncResp->res, "RecurrenceInterval", *scheduleDurationStr); 1169 return; 1170 } 1171 1172 recurrenceInterval = static_cast<uint64_t>(durationNum->count()); 1173 } 1174 1175 setReportTypeAndInterval(asyncResp, id, dbusReportingType, 1176 recurrenceInterval); 1177 } 1178 1179 if (metrics) 1180 { 1181 setReportMetrics(asyncResp, id, *metrics); 1182 } 1183 } 1184 1185 inline void 1186 handleReportDelete(App& app, const crow::Request& req, 1187 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1188 std::string_view id) 1189 { 1190 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1191 { 1192 return; 1193 } 1194 1195 const std::string reportPath = getDbusReportPath(id); 1196 1197 crow::connections::systemBus->async_method_call( 1198 [asyncResp, 1199 reportId = std::string(id)](const boost::system::error_code& ec) { 1200 if (!verifyCommonErrors(asyncResp->res, reportId, ec)) 1201 { 1202 return; 1203 } 1204 asyncResp->res.result(boost::beast::http::status::no_content); 1205 }, 1206 service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); 1207 } 1208 } // namespace telemetry 1209 1210 inline void afterRetrieveUriToDbusMap( 1211 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 1212 const std::shared_ptr<telemetry::AddReport>& addReportReq, 1213 const boost::beast::http::status status, 1214 const std::map<std::string, std::string>& uriToDbus) 1215 { 1216 if (status != boost::beast::http::status::ok) 1217 { 1218 BMCWEB_LOG_ERROR( 1219 "Failed to retrieve URI to dbus sensors map with err {}", 1220 static_cast<unsigned>(status)); 1221 return; 1222 } 1223 addReportReq->insert(uriToDbus); 1224 } 1225 1226 inline void handleMetricReportDefinitionsPost( 1227 App& app, const crow::Request& req, 1228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1229 { 1230 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1231 { 1232 return; 1233 } 1234 1235 telemetry::AddReportArgs args; 1236 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 1237 { 1238 return; 1239 } 1240 1241 boost::container::flat_set<std::pair<std::string, std::string>> 1242 chassisSensors; 1243 if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics, 1244 chassisSensors)) 1245 { 1246 return; 1247 } 1248 1249 auto addReportReq = std::make_shared<telemetry::AddReport>(std::move(args), 1250 asyncResp); 1251 for (const auto& [chassis, sensorType] : chassisSensors) 1252 { 1253 retrieveUriToDbusMap(chassis, sensorType, 1254 std::bind_front(afterRetrieveUriToDbusMap, 1255 asyncResp, addReportReq)); 1256 } 1257 } 1258 1259 inline void 1260 handleMetricReportHead(App& app, const crow::Request& req, 1261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1262 const std::string& /*id*/) 1263 { 1264 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1265 { 1266 return; 1267 } 1268 asyncResp->res.addHeader( 1269 boost::beast::http::field::link, 1270 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1271 } 1272 1273 inline void 1274 handleMetricReportGet(App& app, const crow::Request& req, 1275 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1276 const std::string& id) 1277 { 1278 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1279 { 1280 return; 1281 } 1282 asyncResp->res.addHeader( 1283 boost::beast::http::field::link, 1284 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1285 1286 sdbusplus::asio::getAllProperties( 1287 *crow::connections::systemBus, telemetry::service, 1288 telemetry::getDbusReportPath(id), telemetry::reportInterface, 1289 [asyncResp, id](const boost::system::error_code& ec, 1290 const dbus::utility::DBusPropertiesMap& properties) { 1291 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 1292 { 1293 return; 1294 } 1295 1296 telemetry::fillReportDefinition(asyncResp, id, properties); 1297 }); 1298 } 1299 1300 inline void handleMetricReportDelete( 1301 App& app, const crow::Request& req, 1302 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1303 1304 { 1305 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1306 { 1307 return; 1308 } 1309 1310 const std::string reportPath = telemetry::getDbusReportPath(id); 1311 1312 crow::connections::systemBus->async_method_call( 1313 [asyncResp, id](const boost::system::error_code& ec) { 1314 /* 1315 * boost::system::errc and std::errc are missing value 1316 * for EBADR error that is defined in Linux. 1317 */ 1318 if (ec.value() == EBADR) 1319 { 1320 messages::resourceNotFound(asyncResp->res, "MetricReportDefinition", 1321 id); 1322 return; 1323 } 1324 1325 if (ec) 1326 { 1327 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1328 messages::internalError(asyncResp->res); 1329 return; 1330 } 1331 1332 asyncResp->res.result(boost::beast::http::status::no_content); 1333 }, 1334 telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", 1335 "Delete"); 1336 } 1337 1338 inline void requestRoutesMetricReportDefinitionCollection(App& app) 1339 { 1340 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1341 .privileges(redfish::privileges::headMetricReportDefinitionCollection) 1342 .methods(boost::beast::http::verb::head)(std::bind_front( 1343 telemetry::handleMetricReportDefinitionCollectionHead, 1344 std::ref(app))); 1345 1346 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1347 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 1348 .methods(boost::beast::http::verb::get)(std::bind_front( 1349 telemetry::handleMetricReportDefinitionCollectionGet, 1350 std::ref(app))); 1351 1352 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1353 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 1354 .methods(boost::beast::http::verb::post)( 1355 std::bind_front(handleMetricReportDefinitionsPost, std::ref(app))); 1356 } 1357 1358 inline void requestRoutesMetricReportDefinition(App& app) 1359 { 1360 BMCWEB_ROUTE(app, 1361 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1362 .privileges(redfish::privileges::getMetricReportDefinition) 1363 .methods(boost::beast::http::verb::head)( 1364 std::bind_front(handleMetricReportHead, std::ref(app))); 1365 1366 BMCWEB_ROUTE(app, 1367 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1368 .privileges(redfish::privileges::getMetricReportDefinition) 1369 .methods(boost::beast::http::verb::get)( 1370 std::bind_front(handleMetricReportGet, std::ref(app))); 1371 1372 BMCWEB_ROUTE(app, 1373 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1374 .privileges(redfish::privileges::deleteMetricReportDefinition) 1375 .methods(boost::beast::http::verb::delete_)( 1376 std::bind_front(handleMetricReportDelete, std::ref(app))); 1377 1378 BMCWEB_ROUTE(app, 1379 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1380 .privileges(redfish::privileges::patchMetricReportDefinition) 1381 .methods(boost::beast::http::verb::patch)( 1382 std::bind_front(telemetry::handleReportPatch, std::ref(app))); 1383 } 1384 } // namespace redfish 1385