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