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 fillReportDefinition( 250 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 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"] = resource::State::Enabled; 360 } 361 else 362 { 363 asyncResp->res.jsonValue["Status"]["State"] = resource::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( 645 asyncResp->res, error->uri, 646 "MetricProperties/" + 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), 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( 698 asyncResp->res, uri, 699 "MetricProperties/" + 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), asyncResp(asyncRespIn) 775 {} 776 777 ~UpdateMetrics() 778 { 779 try 780 { 781 setReadingParams(); 782 } 783 catch (const std::exception& e) 784 { 785 BMCWEB_LOG_ERROR("{}", e.what()); 786 } 787 catch (...) 788 { 789 BMCWEB_LOG_ERROR("Unknown error"); 790 } 791 } 792 793 UpdateMetrics(const UpdateMetrics&) = delete; 794 UpdateMetrics(UpdateMetrics&&) = delete; 795 UpdateMetrics& operator=(const UpdateMetrics&) = delete; 796 UpdateMetrics& operator=(UpdateMetrics&&) = delete; 797 798 std::string id; 799 std::map<std::string, std::string> metricPropertyToDbusPaths; 800 801 void insert(const std::map<std::string, std::string>& 802 additionalMetricPropertyToDbusPaths) 803 { 804 metricPropertyToDbusPaths.insert( 805 additionalMetricPropertyToDbusPaths.begin(), 806 additionalMetricPropertyToDbusPaths.end()); 807 } 808 809 void emplace( 810 std::span< 811 const std::tuple<sdbusplus::message::object_path, 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 setReportActions( 938 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id, 939 const std::vector<std::string>& dbusReportActions) 940 { 941 crow::connections::systemBus->async_method_call( 942 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 943 if (!verifyCommonErrors(asyncResp->res, id, ec)) 944 { 945 return; 946 } 947 }, 948 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 949 "org.freedesktop.DBus.Properties", "Set", 950 "xyz.openbmc_project.Telemetry.Report", "ReportActions", 951 dbus::utility::DbusVariantType{dbusReportActions}); 952 } 953 954 inline void setReportMetrics( 955 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id, 956 std::span<nlohmann::json::object_t> metrics) 957 { 958 sdbusplus::asio::getAllProperties( 959 *crow::connections::systemBus, telemetry::service, 960 telemetry::getDbusReportPath(id), telemetry::reportInterface, 961 [asyncResp, id = std::string(id), 962 redfishMetrics = std::vector<nlohmann::json::object_t>( 963 metrics.begin(), metrics.end())]( 964 boost::system::error_code ec, 965 const dbus::utility::DBusPropertiesMap& properties) mutable { 966 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 967 { 968 return; 969 } 970 971 ReadingParameters readingParams; 972 973 const bool success = sdbusplus::unpackPropertiesNoThrow( 974 dbus_utils::UnpackErrorPrinter(), properties, 975 "ReadingParameters", readingParams); 976 977 if (!success) 978 { 979 messages::internalError(asyncResp->res); 980 return; 981 } 982 983 auto updateMetricsReq = 984 std::make_shared<UpdateMetrics>(id, asyncResp); 985 986 boost::container::flat_set<std::pair<std::string, std::string>> 987 chassisSensors; 988 989 size_t index = 0; 990 for (nlohmann::json::object_t& metric : redfishMetrics) 991 { 992 AddReportArgs::MetricArgs metricArgs; 993 std::vector< 994 std::tuple<sdbusplus::message::object_path, std::string>> 995 pathAndUri; 996 997 if (index < readingParams.size()) 998 { 999 const ReadingParameters::value_type& existing = 1000 readingParams[index]; 1001 1002 pathAndUri = std::get<0>(existing); 1003 metricArgs.collectionFunction = std::get<1>(existing); 1004 metricArgs.collectionTimeScope = std::get<2>(existing); 1005 metricArgs.collectionDuration = std::get<3>(existing); 1006 } 1007 1008 if (!getUserMetric(asyncResp->res, metric, metricArgs)) 1009 { 1010 return; 1011 } 1012 1013 std::optional<IncorrectMetricUri> error = 1014 getChassisSensorNode(metricArgs.uris, chassisSensors); 1015 1016 if (error) 1017 { 1018 messages::propertyValueIncorrect( 1019 asyncResp->res, error->uri, 1020 "MetricProperties/" + std::to_string(error->index)); 1021 return; 1022 } 1023 1024 updateMetricsReq->emplace(pathAndUri, metricArgs); 1025 index++; 1026 } 1027 1028 for (const auto& [chassis, sensorType] : chassisSensors) 1029 { 1030 retrieveUriToDbusMap( 1031 chassis, sensorType, 1032 [asyncResp, updateMetricsReq]( 1033 const boost::beast::http::status status, 1034 const std::map<std::string, std::string>& uriToDbus) { 1035 if (status != boost::beast::http::status::ok) 1036 { 1037 BMCWEB_LOG_ERROR( 1038 "Failed to retrieve URI to dbus sensors map with err {}", 1039 static_cast<unsigned>(status)); 1040 return; 1041 } 1042 updateMetricsReq->insert(uriToDbus); 1043 }); 1044 } 1045 }); 1046 } 1047 1048 inline void handleMetricReportDefinitionCollectionHead( 1049 App& app, const crow::Request& req, 1050 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1051 { 1052 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1053 { 1054 return; 1055 } 1056 asyncResp->res.addHeader( 1057 boost::beast::http::field::link, 1058 "</redfish/v1/JsonSchemas/MetricReportDefinitionCollection/MetricReportDefinitionCollection.json>; rel=describedby"); 1059 } 1060 1061 inline void handleMetricReportDefinitionCollectionGet( 1062 App& app, const crow::Request& req, 1063 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1064 { 1065 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1066 { 1067 return; 1068 } 1069 asyncResp->res.addHeader( 1070 boost::beast::http::field::link, 1071 "</redfish/v1/JsonSchemas/MetricReportDefinition/MetricReportDefinition.json>; rel=describedby"); 1072 1073 asyncResp->res.jsonValue["@odata.type"] = 1074 "#MetricReportDefinitionCollection." 1075 "MetricReportDefinitionCollection"; 1076 asyncResp->res.jsonValue["@odata.id"] = 1077 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 1078 asyncResp->res.jsonValue["Name"] = "Metric Definition Collection"; 1079 constexpr std::array<std::string_view, 1> interfaces{ 1080 telemetry::reportInterface}; 1081 collection_util::getCollectionMembers( 1082 asyncResp, 1083 boost::urls::url( 1084 "/redfish/v1/TelemetryService/MetricReportDefinitions"), 1085 interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); 1086 } 1087 1088 inline void handleReportPatch( 1089 App& app, const crow::Request& req, 1090 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id) 1091 { 1092 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1093 { 1094 return; 1095 } 1096 1097 std::optional<std::string> reportingTypeStr; 1098 std::optional<std::string> reportUpdatesStr; 1099 std::optional<bool> metricReportDefinitionEnabled; 1100 std::optional<std::vector<nlohmann::json::object_t>> metrics; 1101 std::optional<std::vector<std::string>> reportActionsStr; 1102 std::optional<std::string> scheduleDurationStr; 1103 1104 if (!json_util::readJsonPatch( 1105 req, asyncResp->res, "Metrics", metrics, 1106 "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates", 1107 reportUpdatesStr, "ReportActions", reportActionsStr, 1108 "Schedule/RecurrenceInterval", scheduleDurationStr, 1109 "MetricReportDefinitionEnabled", metricReportDefinitionEnabled)) 1110 { 1111 return; 1112 } 1113 1114 if (metricReportDefinitionEnabled) 1115 { 1116 setReportEnabled(asyncResp, id, *metricReportDefinitionEnabled); 1117 } 1118 1119 if (reportUpdatesStr) 1120 { 1121 std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr); 1122 if (dbusReportUpdates.empty()) 1123 { 1124 messages::propertyValueNotInList(asyncResp->res, *reportUpdatesStr, 1125 "ReportUpdates"); 1126 return; 1127 } 1128 setReportUpdates(asyncResp, id, dbusReportUpdates); 1129 } 1130 1131 if (reportActionsStr) 1132 { 1133 std::vector<std::string> dbusReportActions; 1134 if (!toDbusReportActions(asyncResp->res, *reportActionsStr, 1135 dbusReportActions)) 1136 { 1137 return; 1138 } 1139 setReportActions(asyncResp, id, dbusReportActions); 1140 } 1141 1142 if (reportingTypeStr || scheduleDurationStr) 1143 { 1144 std::string dbusReportingType; 1145 if (reportingTypeStr) 1146 { 1147 dbusReportingType = toDbusReportingType(*reportingTypeStr); 1148 if (dbusReportingType.empty()) 1149 { 1150 messages::propertyValueNotInList(asyncResp->res, 1151 *reportingTypeStr, 1152 "MetricReportDefinitionType"); 1153 return; 1154 } 1155 } 1156 1157 uint64_t recurrenceInterval = std::numeric_limits<uint64_t>::max(); 1158 if (scheduleDurationStr) 1159 { 1160 std::optional<std::chrono::milliseconds> durationNum = 1161 time_utils::fromDurationString(*scheduleDurationStr); 1162 if (!durationNum || durationNum->count() < 0) 1163 { 1164 messages::propertyValueIncorrect( 1165 asyncResp->res, "RecurrenceInterval", *scheduleDurationStr); 1166 return; 1167 } 1168 1169 recurrenceInterval = static_cast<uint64_t>(durationNum->count()); 1170 } 1171 1172 setReportTypeAndInterval(asyncResp, id, dbusReportingType, 1173 recurrenceInterval); 1174 } 1175 1176 if (metrics) 1177 { 1178 setReportMetrics(asyncResp, id, *metrics); 1179 } 1180 } 1181 1182 inline void handleReportDelete( 1183 App& app, const crow::Request& req, 1184 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id) 1185 { 1186 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1187 { 1188 return; 1189 } 1190 1191 const std::string reportPath = getDbusReportPath(id); 1192 1193 crow::connections::systemBus->async_method_call( 1194 [asyncResp, 1195 reportId = std::string(id)](const boost::system::error_code& ec) { 1196 if (!verifyCommonErrors(asyncResp->res, reportId, ec)) 1197 { 1198 return; 1199 } 1200 asyncResp->res.result(boost::beast::http::status::no_content); 1201 }, 1202 service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); 1203 } 1204 } // namespace telemetry 1205 1206 inline void afterRetrieveUriToDbusMap( 1207 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 1208 const std::shared_ptr<telemetry::AddReport>& addReportReq, 1209 const boost::beast::http::status status, 1210 const std::map<std::string, std::string>& uriToDbus) 1211 { 1212 if (status != boost::beast::http::status::ok) 1213 { 1214 BMCWEB_LOG_ERROR( 1215 "Failed to retrieve URI to dbus sensors map with err {}", 1216 static_cast<unsigned>(status)); 1217 return; 1218 } 1219 addReportReq->insert(uriToDbus); 1220 } 1221 1222 inline void handleMetricReportDefinitionsPost( 1223 App& app, const crow::Request& req, 1224 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1225 { 1226 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1227 { 1228 return; 1229 } 1230 1231 telemetry::AddReportArgs args; 1232 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 1233 { 1234 return; 1235 } 1236 1237 boost::container::flat_set<std::pair<std::string, std::string>> 1238 chassisSensors; 1239 if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics, 1240 chassisSensors)) 1241 { 1242 return; 1243 } 1244 1245 auto addReportReq = 1246 std::make_shared<telemetry::AddReport>(std::move(args), asyncResp); 1247 for (const auto& [chassis, sensorType] : chassisSensors) 1248 { 1249 retrieveUriToDbusMap(chassis, sensorType, 1250 std::bind_front(afterRetrieveUriToDbusMap, 1251 asyncResp, addReportReq)); 1252 } 1253 } 1254 1255 inline void 1256 handleMetricReportHead(App& app, const crow::Request& req, 1257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1258 const std::string& /*id*/) 1259 { 1260 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1261 { 1262 return; 1263 } 1264 asyncResp->res.addHeader( 1265 boost::beast::http::field::link, 1266 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1267 } 1268 1269 inline void handleMetricReportGet( 1270 App& app, const crow::Request& req, 1271 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1272 { 1273 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1274 { 1275 return; 1276 } 1277 asyncResp->res.addHeader( 1278 boost::beast::http::field::link, 1279 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1280 1281 sdbusplus::asio::getAllProperties( 1282 *crow::connections::systemBus, telemetry::service, 1283 telemetry::getDbusReportPath(id), telemetry::reportInterface, 1284 [asyncResp, id](const boost::system::error_code& ec, 1285 const dbus::utility::DBusPropertiesMap& properties) { 1286 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 1287 { 1288 return; 1289 } 1290 1291 telemetry::fillReportDefinition(asyncResp, id, properties); 1292 }); 1293 } 1294 1295 inline void handleMetricReportDelete( 1296 App& app, const crow::Request& req, 1297 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1298 1299 { 1300 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1301 { 1302 return; 1303 } 1304 1305 const std::string reportPath = telemetry::getDbusReportPath(id); 1306 1307 crow::connections::systemBus->async_method_call( 1308 [asyncResp, id](const boost::system::error_code& ec) { 1309 /* 1310 * boost::system::errc and std::errc are missing value 1311 * for EBADR error that is defined in Linux. 1312 */ 1313 if (ec.value() == EBADR) 1314 { 1315 messages::resourceNotFound(asyncResp->res, 1316 "MetricReportDefinition", id); 1317 return; 1318 } 1319 1320 if (ec) 1321 { 1322 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1323 messages::internalError(asyncResp->res); 1324 return; 1325 } 1326 1327 asyncResp->res.result(boost::beast::http::status::no_content); 1328 }, 1329 telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", 1330 "Delete"); 1331 } 1332 1333 inline void requestRoutesMetricReportDefinitionCollection(App& app) 1334 { 1335 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1336 .privileges(redfish::privileges::headMetricReportDefinitionCollection) 1337 .methods(boost::beast::http::verb::head)(std::bind_front( 1338 telemetry::handleMetricReportDefinitionCollectionHead, 1339 std::ref(app))); 1340 1341 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1342 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 1343 .methods(boost::beast::http::verb::get)(std::bind_front( 1344 telemetry::handleMetricReportDefinitionCollectionGet, 1345 std::ref(app))); 1346 1347 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1348 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 1349 .methods(boost::beast::http::verb::post)( 1350 std::bind_front(handleMetricReportDefinitionsPost, std::ref(app))); 1351 } 1352 1353 inline void requestRoutesMetricReportDefinition(App& app) 1354 { 1355 BMCWEB_ROUTE(app, 1356 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1357 .privileges(redfish::privileges::getMetricReportDefinition) 1358 .methods(boost::beast::http::verb::head)( 1359 std::bind_front(handleMetricReportHead, std::ref(app))); 1360 1361 BMCWEB_ROUTE(app, 1362 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1363 .privileges(redfish::privileges::getMetricReportDefinition) 1364 .methods(boost::beast::http::verb::get)( 1365 std::bind_front(handleMetricReportGet, std::ref(app))); 1366 1367 BMCWEB_ROUTE(app, 1368 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1369 .privileges(redfish::privileges::deleteMetricReportDefinition) 1370 .methods(boost::beast::http::verb::delete_)( 1371 std::bind_front(handleMetricReportDelete, std::ref(app))); 1372 1373 BMCWEB_ROUTE(app, 1374 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1375 .privileges(redfish::privileges::patchMetricReportDefinition) 1376 .methods(boost::beast::http::verb::patch)( 1377 std::bind_front(telemetry::handleReportPatch, std::ref(app))); 1378 } 1379 } // namespace redfish 1380