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& 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::readJson(metric, res, "MetricProperties", uris, 440 "CollectionFunction", collectionFunction, 441 "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> metrics, 499 std::vector<AddReportArgs::MetricArgs>& result) 500 { 501 result.reserve(metrics.size()); 502 503 for (nlohmann::json& 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>> metrics; 528 std::optional<std::vector<std::string>> reportActionsStr; 529 std::optional<nlohmann::json> schedule; 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", schedule, 536 "MetricReportDefinitionEnabled", metricReportDefinitionEnabled)) 537 { 538 return false; 539 } 540 541 if (id) 542 { 543 constexpr const char* allowedCharactersInId = 544 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 545 if (id->empty() || 546 id->find_first_not_of(allowedCharactersInId) != std::string::npos) 547 { 548 messages::propertyValueIncorrect(res, "Id", *id); 549 return false; 550 } 551 args.id = *id; 552 } 553 554 if (name) 555 { 556 args.name = *name; 557 } 558 559 if (reportingTypeStr) 560 { 561 std::string dbusReportingType = toDbusReportingType(*reportingTypeStr); 562 if (dbusReportingType.empty()) 563 { 564 messages::propertyValueNotInList(res, *reportingTypeStr, 565 "MetricReportDefinitionType"); 566 return false; 567 } 568 args.reportingType = dbusReportingType; 569 } 570 571 if (reportUpdatesStr) 572 { 573 std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr); 574 if (dbusReportUpdates.empty()) 575 { 576 messages::propertyValueNotInList(res, *reportUpdatesStr, 577 "ReportUpdates"); 578 return false; 579 } 580 args.reportUpdates = dbusReportUpdates; 581 } 582 583 if (appendLimit) 584 { 585 args.appendLimit = *appendLimit; 586 } 587 588 if (metricReportDefinitionEnabled) 589 { 590 args.metricReportDefinitionEnabled = *metricReportDefinitionEnabled; 591 } 592 593 if (reportActionsStr) 594 { 595 if (!toDbusReportActions(res, *reportActionsStr, args.reportActions)) 596 { 597 return false; 598 } 599 } 600 601 if (reportingTypeStr == "Periodic") 602 { 603 if (!schedule) 604 { 605 messages::createFailedMissingReqProperties(res, "Schedule"); 606 return false; 607 } 608 609 std::string durationStr; 610 if (!json_util::readJson(*schedule, res, "RecurrenceInterval", 611 durationStr)) 612 { 613 return false; 614 } 615 616 std::optional<std::chrono::milliseconds> durationNum = 617 time_utils::fromDurationString(durationStr); 618 if (!durationNum || durationNum->count() < 0) 619 { 620 messages::propertyValueIncorrect(res, "RecurrenceInterval", 621 durationStr); 622 return false; 623 } 624 args.interval = static_cast<uint64_t>(durationNum->count()); 625 } 626 627 if (metrics) 628 { 629 if (!getUserMetrics(res, *metrics, args.metrics)) 630 { 631 return false; 632 } 633 } 634 635 return true; 636 } 637 638 inline bool getChassisSensorNodeFromMetrics( 639 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 640 std::span<const AddReportArgs::MetricArgs> metrics, 641 boost::container::flat_set<std::pair<std::string, std::string>>& matched) 642 { 643 for (const auto& metric : metrics) 644 { 645 std::optional<IncorrectMetricUri> error = 646 getChassisSensorNode(metric.uris, matched); 647 if (error) 648 { 649 messages::propertyValueIncorrect(asyncResp->res, error->uri, 650 "MetricProperties/" + 651 std::to_string(error->index)); 652 return false; 653 } 654 } 655 return true; 656 } 657 658 class AddReport 659 { 660 public: 661 AddReport(AddReportArgs argsIn, 662 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) : 663 asyncResp(asyncRespIn), 664 args(std::move(argsIn)) 665 {} 666 667 ~AddReport() 668 { 669 boost::asio::post(crow::connections::systemBus->get_io_context(), 670 std::bind_front(&performAddReport, asyncResp, args, 671 std::move(uriToDbus))); 672 } 673 674 static void performAddReport( 675 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 676 const AddReportArgs& args, 677 const boost::container::flat_map<std::string, std::string>& uriToDbus) 678 { 679 if (asyncResp->res.result() != boost::beast::http::status::ok) 680 { 681 return; 682 } 683 684 telemetry::ReadingParameters readingParams; 685 readingParams.reserve(args.metrics.size()); 686 687 for (const auto& metric : args.metrics) 688 { 689 std::vector< 690 std::tuple<sdbusplus::message::object_path, std::string>> 691 sensorParams; 692 sensorParams.reserve(metric.uris.size()); 693 694 for (size_t i = 0; i < metric.uris.size(); i++) 695 { 696 const std::string& uri = metric.uris[i]; 697 auto el = uriToDbus.find(uri); 698 if (el == uriToDbus.end()) 699 { 700 BMCWEB_LOG_ERROR( 701 "Failed to find DBus sensor corresponding to URI {}", 702 uri); 703 messages::propertyValueNotInList(asyncResp->res, uri, 704 "MetricProperties/" + 705 std::to_string(i)); 706 return; 707 } 708 709 const std::string& dbusPath = el->second; 710 sensorParams.emplace_back(dbusPath, uri); 711 } 712 713 readingParams.emplace_back( 714 std::move(sensorParams), metric.collectionFunction, 715 metric.collectionTimeScope, metric.collectionDuration); 716 } 717 718 crow::connections::systemBus->async_method_call( 719 [asyncResp, id = args.id, uriToDbus]( 720 const boost::system::error_code& ec, const std::string&) { 721 if (ec == boost::system::errc::file_exists) 722 { 723 messages::resourceAlreadyExists( 724 asyncResp->res, "MetricReportDefinition", "Id", id); 725 return; 726 } 727 if (ec == boost::system::errc::too_many_files_open) 728 { 729 messages::createLimitReachedForResource(asyncResp->res); 730 return; 731 } 732 if (ec == boost::system::errc::argument_list_too_long) 733 { 734 nlohmann::json metricProperties = nlohmann::json::array(); 735 for (const auto& [uri, _] : uriToDbus) 736 { 737 metricProperties.emplace_back(uri); 738 } 739 messages::propertyValueIncorrect( 740 asyncResp->res, metricProperties, "MetricProperties"); 741 return; 742 } 743 if (ec) 744 { 745 messages::internalError(asyncResp->res); 746 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 747 return; 748 } 749 750 messages::created(asyncResp->res); 751 }, 752 telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", 753 "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", 754 "TelemetryService/" + args.id, args.name, args.reportingType, 755 args.reportUpdates, args.appendLimit, args.reportActions, 756 args.interval, readingParams, args.metricReportDefinitionEnabled); 757 } 758 759 AddReport(const AddReport&) = delete; 760 AddReport(AddReport&&) = delete; 761 AddReport& operator=(const AddReport&) = delete; 762 AddReport& operator=(AddReport&&) = delete; 763 764 void insert(const std::map<std::string, std::string>& el) 765 { 766 uriToDbus.insert(el.begin(), el.end()); 767 } 768 769 private: 770 std::shared_ptr<bmcweb::AsyncResp> asyncResp; 771 AddReportArgs args; 772 boost::container::flat_map<std::string, std::string> uriToDbus{}; 773 }; 774 775 class UpdateMetrics 776 { 777 public: 778 UpdateMetrics(std::string_view idIn, 779 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) : 780 id(idIn), 781 asyncResp(asyncRespIn) 782 {} 783 784 ~UpdateMetrics() 785 { 786 try 787 { 788 setReadingParams(); 789 } 790 catch (const std::exception& e) 791 { 792 BMCWEB_LOG_ERROR("{}", e.what()); 793 } 794 catch (...) 795 { 796 BMCWEB_LOG_ERROR("Unknown error"); 797 } 798 } 799 800 UpdateMetrics(const UpdateMetrics&) = delete; 801 UpdateMetrics(UpdateMetrics&&) = delete; 802 UpdateMetrics& operator=(const UpdateMetrics&) = delete; 803 UpdateMetrics& operator=(UpdateMetrics&&) = delete; 804 805 std::string id; 806 std::map<std::string, std::string> metricPropertyToDbusPaths; 807 808 void insert(const std::map<std::string, std::string>& 809 additionalMetricPropertyToDbusPaths) 810 { 811 metricPropertyToDbusPaths.insert( 812 additionalMetricPropertyToDbusPaths.begin(), 813 additionalMetricPropertyToDbusPaths.end()); 814 } 815 816 void emplace(std::span<const std::tuple<sdbusplus::message::object_path, 817 std::string>> 818 pathAndUri, 819 const AddReportArgs::MetricArgs& metricArgs) 820 { 821 readingParamsUris.emplace_back(metricArgs.uris); 822 readingParams.emplace_back( 823 std::vector(pathAndUri.begin(), pathAndUri.end()), 824 metricArgs.collectionFunction, metricArgs.collectionTimeScope, 825 metricArgs.collectionDuration); 826 } 827 828 void setReadingParams() 829 { 830 if (asyncResp->res.result() != boost::beast::http::status::ok) 831 { 832 return; 833 } 834 835 for (size_t index = 0; index < readingParamsUris.size(); ++index) 836 { 837 std::span<const std::string> newUris = readingParamsUris[index]; 838 839 const std::optional<std::vector< 840 std::tuple<sdbusplus::message::object_path, std::string>>> 841 readingParam = sensorPathToUri(newUris); 842 843 if (!readingParam) 844 { 845 return; 846 } 847 848 std::get<0>(readingParams[index]) = *readingParam; 849 } 850 851 crow::connections::systemBus->async_method_call( 852 [asyncResp(this->asyncResp), 853 reportId = id](const boost::system::error_code& ec) { 854 if (!verifyCommonErrors(asyncResp->res, reportId, ec)) 855 { 856 return; 857 } 858 }, 859 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 860 "org.freedesktop.DBus.Properties", "Set", 861 "xyz.openbmc_project.Telemetry.Report", "ReadingParameters", 862 dbus::utility::DbusVariantType{readingParams}); 863 } 864 865 private: 866 std::optional< 867 std::vector<std::tuple<sdbusplus::message::object_path, std::string>>> 868 sensorPathToUri(std::span<const std::string> uris) const 869 { 870 std::vector<std::tuple<sdbusplus::message::object_path, std::string>> 871 result; 872 873 for (const std::string& uri : uris) 874 { 875 auto it = metricPropertyToDbusPaths.find(uri); 876 if (it == metricPropertyToDbusPaths.end()) 877 { 878 messages::propertyValueNotInList(asyncResp->res, uri, 879 "MetricProperties"); 880 return {}; 881 } 882 result.emplace_back(it->second, uri); 883 } 884 885 return result; 886 } 887 888 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 889 std::vector<std::vector<std::string>> readingParamsUris; 890 ReadingParameters readingParams{}; 891 }; 892 893 inline void 894 setReportEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 895 std::string_view id, bool enabled) 896 { 897 crow::connections::systemBus->async_method_call( 898 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 899 if (!verifyCommonErrors(asyncResp->res, id, ec)) 900 { 901 return; 902 } 903 }, 904 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 905 "org.freedesktop.DBus.Properties", "Set", 906 "xyz.openbmc_project.Telemetry.Report", "Enabled", 907 dbus::utility::DbusVariantType{enabled}); 908 } 909 910 inline void setReportTypeAndInterval( 911 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id, 912 const std::string& reportingType, uint64_t recurrenceInterval) 913 { 914 crow::connections::systemBus->async_method_call( 915 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 916 if (!verifyCommonErrors(asyncResp->res, id, ec)) 917 { 918 return; 919 } 920 }, 921 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 922 "xyz.openbmc_project.Telemetry.Report", "SetReportingProperties", 923 reportingType, recurrenceInterval); 924 } 925 926 inline void 927 setReportUpdates(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 928 std::string_view id, const std::string& reportUpdates) 929 { 930 crow::connections::systemBus->async_method_call( 931 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 932 if (!verifyCommonErrors(asyncResp->res, id, ec)) 933 { 934 return; 935 } 936 }, 937 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 938 "org.freedesktop.DBus.Properties", "Set", 939 "xyz.openbmc_project.Telemetry.Report", "ReportUpdates", 940 dbus::utility::DbusVariantType{reportUpdates}); 941 } 942 943 inline void 944 setReportActions(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 945 std::string_view id, 946 const std::vector<std::string>& dbusReportActions) 947 { 948 crow::connections::systemBus->async_method_call( 949 [asyncResp, id = std::string(id)](const boost::system::error_code& ec) { 950 if (!verifyCommonErrors(asyncResp->res, id, ec)) 951 { 952 return; 953 } 954 }, 955 "xyz.openbmc_project.Telemetry", getDbusReportPath(id), 956 "org.freedesktop.DBus.Properties", "Set", 957 "xyz.openbmc_project.Telemetry.Report", "ReportActions", 958 dbus::utility::DbusVariantType{dbusReportActions}); 959 } 960 961 inline void 962 setReportMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 963 std::string_view id, std::span<nlohmann::json> metrics) 964 { 965 sdbusplus::asio::getAllProperties( 966 *crow::connections::systemBus, telemetry::service, 967 telemetry::getDbusReportPath(id), telemetry::reportInterface, 968 [asyncResp, id = std::string(id), 969 redfishMetrics = std::vector<nlohmann::json>(metrics.begin(), 970 metrics.end())]( 971 boost::system::error_code ec, 972 const dbus::utility::DBusPropertiesMap& properties) mutable { 973 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 974 { 975 return; 976 } 977 978 ReadingParameters readingParams; 979 980 const bool success = sdbusplus::unpackPropertiesNoThrow( 981 dbus_utils::UnpackErrorPrinter(), properties, "ReadingParameters", 982 readingParams); 983 984 if (!success) 985 { 986 messages::internalError(asyncResp->res); 987 return; 988 } 989 990 auto updateMetricsReq = std::make_shared<UpdateMetrics>(id, asyncResp); 991 992 boost::container::flat_set<std::pair<std::string, std::string>> 993 chassisSensors; 994 995 size_t index = 0; 996 for (nlohmann::json& metric : redfishMetrics) 997 { 998 if (metric.is_null()) 999 { 1000 continue; 1001 } 1002 1003 AddReportArgs::MetricArgs metricArgs; 1004 std::vector< 1005 std::tuple<sdbusplus::message::object_path, std::string>> 1006 pathAndUri; 1007 1008 if (index < readingParams.size()) 1009 { 1010 const ReadingParameters::value_type& existing = 1011 readingParams[index]; 1012 1013 pathAndUri = std::get<0>(existing); 1014 metricArgs.collectionFunction = std::get<1>(existing); 1015 metricArgs.collectionTimeScope = std::get<2>(existing); 1016 metricArgs.collectionDuration = std::get<3>(existing); 1017 } 1018 1019 if (!getUserMetric(asyncResp->res, metric, metricArgs)) 1020 { 1021 return; 1022 } 1023 1024 std::optional<IncorrectMetricUri> error = 1025 getChassisSensorNode(metricArgs.uris, chassisSensors); 1026 1027 if (error) 1028 { 1029 messages::propertyValueIncorrect( 1030 asyncResp->res, error->uri, 1031 "MetricProperties/" + std::to_string(error->index)); 1032 return; 1033 } 1034 1035 updateMetricsReq->emplace(pathAndUri, metricArgs); 1036 index++; 1037 } 1038 1039 for (const auto& [chassis, sensorType] : chassisSensors) 1040 { 1041 retrieveUriToDbusMap( 1042 chassis, sensorType, 1043 [asyncResp, updateMetricsReq]( 1044 const boost::beast::http::status status, 1045 const std::map<std::string, std::string>& uriToDbus) { 1046 if (status != boost::beast::http::status::ok) 1047 { 1048 BMCWEB_LOG_ERROR( 1049 "Failed to retrieve URI to dbus sensors map with err {}", 1050 static_cast<unsigned>(status)); 1051 return; 1052 } 1053 updateMetricsReq->insert(uriToDbus); 1054 }); 1055 } 1056 }); 1057 } 1058 1059 inline void handleMetricReportDefinitionCollectionHead( 1060 App& app, const crow::Request& req, 1061 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1062 { 1063 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1064 { 1065 return; 1066 } 1067 asyncResp->res.addHeader( 1068 boost::beast::http::field::link, 1069 "</redfish/v1/JsonSchemas/MetricReportDefinitionCollection/MetricReportDefinitionCollection.json>; rel=describedby"); 1070 } 1071 1072 inline void handleMetricReportDefinitionCollectionGet( 1073 App& app, const crow::Request& req, 1074 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1075 { 1076 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1077 { 1078 return; 1079 } 1080 asyncResp->res.addHeader( 1081 boost::beast::http::field::link, 1082 "</redfish/v1/JsonSchemas/MetricReportDefinition/MetricReportDefinition.json>; rel=describedby"); 1083 1084 asyncResp->res.jsonValue["@odata.type"] = 1085 "#MetricReportDefinitionCollection." 1086 "MetricReportDefinitionCollection"; 1087 asyncResp->res.jsonValue["@odata.id"] = 1088 "/redfish/v1/TelemetryService/MetricReportDefinitions"; 1089 asyncResp->res.jsonValue["Name"] = "Metric Definition Collection"; 1090 constexpr std::array<std::string_view, 1> interfaces{ 1091 telemetry::reportInterface}; 1092 collection_util::getCollectionMembers( 1093 asyncResp, 1094 boost::urls::url( 1095 "/redfish/v1/TelemetryService/MetricReportDefinitions"), 1096 interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); 1097 } 1098 1099 inline void 1100 handleReportPatch(App& app, const crow::Request& req, 1101 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1102 std::string_view id) 1103 { 1104 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1105 { 1106 return; 1107 } 1108 1109 std::optional<std::string> reportingTypeStr; 1110 std::optional<std::string> reportUpdatesStr; 1111 std::optional<bool> metricReportDefinitionEnabled; 1112 std::optional<std::vector<nlohmann::json>> metrics; 1113 std::optional<std::vector<std::string>> reportActionsStr; 1114 std::optional<nlohmann::json> schedule; 1115 1116 if (!json_util::readJsonPatch( 1117 req, asyncResp->res, "Metrics", metrics, 1118 "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates", 1119 reportUpdatesStr, "ReportActions", reportActionsStr, "Schedule", 1120 schedule, "MetricReportDefinitionEnabled", 1121 metricReportDefinitionEnabled)) 1122 { 1123 return; 1124 } 1125 1126 if (metricReportDefinitionEnabled) 1127 { 1128 setReportEnabled(asyncResp, id, *metricReportDefinitionEnabled); 1129 } 1130 1131 if (reportUpdatesStr) 1132 { 1133 std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr); 1134 if (dbusReportUpdates.empty()) 1135 { 1136 messages::propertyValueNotInList(asyncResp->res, *reportUpdatesStr, 1137 "ReportUpdates"); 1138 return; 1139 } 1140 setReportUpdates(asyncResp, id, dbusReportUpdates); 1141 } 1142 1143 if (reportActionsStr) 1144 { 1145 std::vector<std::string> dbusReportActions; 1146 if (!toDbusReportActions(asyncResp->res, *reportActionsStr, 1147 dbusReportActions)) 1148 { 1149 return; 1150 } 1151 setReportActions(asyncResp, id, dbusReportActions); 1152 } 1153 1154 if (reportingTypeStr || schedule) 1155 { 1156 std::string dbusReportingType; 1157 if (reportingTypeStr) 1158 { 1159 dbusReportingType = toDbusReportingType(*reportingTypeStr); 1160 if (dbusReportingType.empty()) 1161 { 1162 messages::propertyValueNotInList(asyncResp->res, 1163 *reportingTypeStr, 1164 "MetricReportDefinitionType"); 1165 return; 1166 } 1167 } 1168 1169 uint64_t recurrenceInterval = std::numeric_limits<uint64_t>::max(); 1170 if (schedule) 1171 { 1172 std::string durationStr; 1173 if (!json_util::readJson(*schedule, asyncResp->res, 1174 "RecurrenceInterval", durationStr)) 1175 { 1176 return; 1177 } 1178 1179 std::optional<std::chrono::milliseconds> durationNum = 1180 time_utils::fromDurationString(durationStr); 1181 if (!durationNum || durationNum->count() < 0) 1182 { 1183 messages::propertyValueIncorrect( 1184 asyncResp->res, "RecurrenceInterval", durationStr); 1185 return; 1186 } 1187 1188 recurrenceInterval = static_cast<uint64_t>(durationNum->count()); 1189 } 1190 1191 setReportTypeAndInterval(asyncResp, id, dbusReportingType, 1192 recurrenceInterval); 1193 } 1194 1195 if (metrics) 1196 { 1197 setReportMetrics(asyncResp, id, *metrics); 1198 } 1199 } 1200 1201 inline void 1202 handleReportDelete(App& app, const crow::Request& req, 1203 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1204 std::string_view id) 1205 { 1206 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1207 { 1208 return; 1209 } 1210 1211 const std::string reportPath = getDbusReportPath(id); 1212 1213 crow::connections::systemBus->async_method_call( 1214 [asyncResp, 1215 reportId = std::string(id)](const boost::system::error_code& ec) { 1216 if (!verifyCommonErrors(asyncResp->res, reportId, ec)) 1217 { 1218 return; 1219 } 1220 asyncResp->res.result(boost::beast::http::status::no_content); 1221 }, 1222 service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); 1223 } 1224 } // namespace telemetry 1225 1226 inline void handleMetricReportDefinitionsPost( 1227 App& app, const crow::Request& req, 1228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1229 { 1230 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1231 { 1232 return; 1233 } 1234 1235 telemetry::AddReportArgs args; 1236 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 1237 { 1238 return; 1239 } 1240 1241 boost::container::flat_set<std::pair<std::string, std::string>> 1242 chassisSensors; 1243 if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics, 1244 chassisSensors)) 1245 { 1246 return; 1247 } 1248 1249 auto addReportReq = std::make_shared<telemetry::AddReport>(std::move(args), 1250 asyncResp); 1251 for (const auto& [chassis, sensorType] : chassisSensors) 1252 { 1253 retrieveUriToDbusMap( 1254 chassis, sensorType, 1255 [asyncResp, addReportReq]( 1256 const boost::beast::http::status status, 1257 const std::map<std::string, std::string>& uriToDbus) { 1258 if (status != boost::beast::http::status::ok) 1259 { 1260 BMCWEB_LOG_ERROR( 1261 "Failed to retrieve URI to dbus sensors map with err {}", 1262 static_cast<unsigned>(status)); 1263 return; 1264 } 1265 addReportReq->insert(uriToDbus); 1266 }); 1267 } 1268 } 1269 1270 inline void 1271 handleMetricReportHead(App& app, const crow::Request& req, 1272 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1273 const std::string& /*id*/) 1274 { 1275 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1276 { 1277 return; 1278 } 1279 asyncResp->res.addHeader( 1280 boost::beast::http::field::link, 1281 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1282 } 1283 1284 inline void 1285 handleMetricReportGet(App& app, const crow::Request& req, 1286 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1287 const std::string& id) 1288 { 1289 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1290 { 1291 return; 1292 } 1293 asyncResp->res.addHeader( 1294 boost::beast::http::field::link, 1295 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1296 1297 sdbusplus::asio::getAllProperties( 1298 *crow::connections::systemBus, telemetry::service, 1299 telemetry::getDbusReportPath(id), telemetry::reportInterface, 1300 [asyncResp, id](const boost::system::error_code& ec, 1301 const dbus::utility::DBusPropertiesMap& properties) { 1302 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 1303 { 1304 return; 1305 } 1306 1307 telemetry::fillReportDefinition(asyncResp, id, properties); 1308 }); 1309 } 1310 1311 inline void handleMetricReportDelete( 1312 App& app, const crow::Request& req, 1313 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1314 1315 { 1316 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1317 { 1318 return; 1319 } 1320 1321 const std::string reportPath = telemetry::getDbusReportPath(id); 1322 1323 crow::connections::systemBus->async_method_call( 1324 [asyncResp, id](const boost::system::error_code& ec) { 1325 /* 1326 * boost::system::errc and std::errc are missing value 1327 * for EBADR error that is defined in Linux. 1328 */ 1329 if (ec.value() == EBADR) 1330 { 1331 messages::resourceNotFound(asyncResp->res, "MetricReportDefinition", 1332 id); 1333 return; 1334 } 1335 1336 if (ec) 1337 { 1338 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1339 messages::internalError(asyncResp->res); 1340 return; 1341 } 1342 1343 asyncResp->res.result(boost::beast::http::status::no_content); 1344 }, 1345 telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", 1346 "Delete"); 1347 } 1348 1349 inline void requestRoutesMetricReportDefinitionCollection(App& app) 1350 { 1351 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1352 .privileges(redfish::privileges::headMetricReportDefinitionCollection) 1353 .methods(boost::beast::http::verb::head)(std::bind_front( 1354 telemetry::handleMetricReportDefinitionCollectionHead, 1355 std::ref(app))); 1356 1357 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1358 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 1359 .methods(boost::beast::http::verb::get)(std::bind_front( 1360 telemetry::handleMetricReportDefinitionCollectionGet, 1361 std::ref(app))); 1362 1363 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1364 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 1365 .methods(boost::beast::http::verb::post)( 1366 std::bind_front(handleMetricReportDefinitionsPost, std::ref(app))); 1367 } 1368 1369 inline void requestRoutesMetricReportDefinition(App& app) 1370 { 1371 BMCWEB_ROUTE(app, 1372 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1373 .privileges(redfish::privileges::getMetricReportDefinition) 1374 .methods(boost::beast::http::verb::head)( 1375 std::bind_front(handleMetricReportHead, std::ref(app))); 1376 1377 BMCWEB_ROUTE(app, 1378 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1379 .privileges(redfish::privileges::getMetricReportDefinition) 1380 .methods(boost::beast::http::verb::get)( 1381 std::bind_front(handleMetricReportGet, std::ref(app))); 1382 1383 BMCWEB_ROUTE(app, 1384 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1385 .privileges(redfish::privileges::deleteMetricReportDefinition) 1386 .methods(boost::beast::http::verb::delete_)( 1387 std::bind_front(handleMetricReportDelete, std::ref(app))); 1388 1389 BMCWEB_ROUTE(app, 1390 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1391 .privileges(redfish::privileges::patchMetricReportDefinition) 1392 .methods(boost::beast::http::verb::patch)( 1393 std::bind_front(telemetry::handleReportPatch, std::ref(app))); 1394 } 1395 } // namespace redfish 1396