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 afterRetrieveUriToDbusMap( 1227 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 1228 const std::shared_ptr<telemetry::AddReport>& addReportReq, 1229 const boost::beast::http::status status, 1230 const std::map<std::string, std::string>& uriToDbus) 1231 { 1232 if (status != boost::beast::http::status::ok) 1233 { 1234 BMCWEB_LOG_ERROR( 1235 "Failed to retrieve URI to dbus sensors map with err {}", 1236 static_cast<unsigned>(status)); 1237 return; 1238 } 1239 addReportReq->insert(uriToDbus); 1240 } 1241 1242 inline void handleMetricReportDefinitionsPost( 1243 App& app, const crow::Request& req, 1244 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1245 { 1246 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1247 { 1248 return; 1249 } 1250 1251 telemetry::AddReportArgs args; 1252 if (!telemetry::getUserParameters(asyncResp->res, req, args)) 1253 { 1254 return; 1255 } 1256 1257 boost::container::flat_set<std::pair<std::string, std::string>> 1258 chassisSensors; 1259 if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics, 1260 chassisSensors)) 1261 { 1262 return; 1263 } 1264 1265 auto addReportReq = std::make_shared<telemetry::AddReport>(std::move(args), 1266 asyncResp); 1267 for (const auto& [chassis, sensorType] : chassisSensors) 1268 { 1269 retrieveUriToDbusMap(chassis, sensorType, 1270 std::bind_front(afterRetrieveUriToDbusMap, 1271 asyncResp, addReportReq)); 1272 } 1273 } 1274 1275 inline void 1276 handleMetricReportHead(App& app, const crow::Request& req, 1277 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1278 const std::string& /*id*/) 1279 { 1280 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1281 { 1282 return; 1283 } 1284 asyncResp->res.addHeader( 1285 boost::beast::http::field::link, 1286 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1287 } 1288 1289 inline void 1290 handleMetricReportGet(App& app, const crow::Request& req, 1291 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1292 const std::string& id) 1293 { 1294 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1295 { 1296 return; 1297 } 1298 asyncResp->res.addHeader( 1299 boost::beast::http::field::link, 1300 "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby"); 1301 1302 sdbusplus::asio::getAllProperties( 1303 *crow::connections::systemBus, telemetry::service, 1304 telemetry::getDbusReportPath(id), telemetry::reportInterface, 1305 [asyncResp, id](const boost::system::error_code& ec, 1306 const dbus::utility::DBusPropertiesMap& properties) { 1307 if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec)) 1308 { 1309 return; 1310 } 1311 1312 telemetry::fillReportDefinition(asyncResp, id, properties); 1313 }); 1314 } 1315 1316 inline void handleMetricReportDelete( 1317 App& app, const crow::Request& req, 1318 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1319 1320 { 1321 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1322 { 1323 return; 1324 } 1325 1326 const std::string reportPath = telemetry::getDbusReportPath(id); 1327 1328 crow::connections::systemBus->async_method_call( 1329 [asyncResp, id](const boost::system::error_code& ec) { 1330 /* 1331 * boost::system::errc and std::errc are missing value 1332 * for EBADR error that is defined in Linux. 1333 */ 1334 if (ec.value() == EBADR) 1335 { 1336 messages::resourceNotFound(asyncResp->res, "MetricReportDefinition", 1337 id); 1338 return; 1339 } 1340 1341 if (ec) 1342 { 1343 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1344 messages::internalError(asyncResp->res); 1345 return; 1346 } 1347 1348 asyncResp->res.result(boost::beast::http::status::no_content); 1349 }, 1350 telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", 1351 "Delete"); 1352 } 1353 1354 inline void requestRoutesMetricReportDefinitionCollection(App& app) 1355 { 1356 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1357 .privileges(redfish::privileges::headMetricReportDefinitionCollection) 1358 .methods(boost::beast::http::verb::head)(std::bind_front( 1359 telemetry::handleMetricReportDefinitionCollectionHead, 1360 std::ref(app))); 1361 1362 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1363 .privileges(redfish::privileges::getMetricReportDefinitionCollection) 1364 .methods(boost::beast::http::verb::get)(std::bind_front( 1365 telemetry::handleMetricReportDefinitionCollectionGet, 1366 std::ref(app))); 1367 1368 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") 1369 .privileges(redfish::privileges::postMetricReportDefinitionCollection) 1370 .methods(boost::beast::http::verb::post)( 1371 std::bind_front(handleMetricReportDefinitionsPost, std::ref(app))); 1372 } 1373 1374 inline void requestRoutesMetricReportDefinition(App& app) 1375 { 1376 BMCWEB_ROUTE(app, 1377 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1378 .privileges(redfish::privileges::getMetricReportDefinition) 1379 .methods(boost::beast::http::verb::head)( 1380 std::bind_front(handleMetricReportHead, std::ref(app))); 1381 1382 BMCWEB_ROUTE(app, 1383 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1384 .privileges(redfish::privileges::getMetricReportDefinition) 1385 .methods(boost::beast::http::verb::get)( 1386 std::bind_front(handleMetricReportGet, std::ref(app))); 1387 1388 BMCWEB_ROUTE(app, 1389 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1390 .privileges(redfish::privileges::deleteMetricReportDefinition) 1391 .methods(boost::beast::http::verb::delete_)( 1392 std::bind_front(handleMetricReportDelete, std::ref(app))); 1393 1394 BMCWEB_ROUTE(app, 1395 "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/") 1396 .privileges(redfish::privileges::patchMetricReportDefinition) 1397 .methods(boost::beast::http::verb::patch)( 1398 std::bind_front(telemetry::handleReportPatch, std::ref(app))); 1399 } 1400 } // namespace redfish 1401