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