1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "app.hpp" 6 #include "generated/enums/metric_definition.hpp" 7 #include "generated/enums/resource.hpp" 8 #include "generated/enums/triggers.hpp" 9 #include "query.hpp" 10 #include "registries/privilege_registry.hpp" 11 #include "utility.hpp" 12 #include "utils/collection.hpp" 13 #include "utils/dbus_utils.hpp" 14 #include "utils/json_utils.hpp" 15 #include "utils/sensor_utils.hpp" 16 #include "utils/telemetry_utils.hpp" 17 #include "utils/time_utils.hpp" 18 19 #include <boost/url/format.hpp> 20 #include <sdbusplus/asio/property.hpp> 21 #include <sdbusplus/unpack_properties.hpp> 22 23 #include <array> 24 #include <string_view> 25 #include <tuple> 26 #include <variant> 27 #include <vector> 28 29 namespace redfish 30 { 31 namespace telemetry 32 { 33 constexpr const char* triggerInterface = 34 "xyz.openbmc_project.Telemetry.Trigger"; 35 36 using NumericThresholdParams = 37 std::tuple<std::string, uint64_t, std::string, double>; 38 39 using DiscreteThresholdParams = 40 std::tuple<std::string, std::string, uint64_t, std::string>; 41 42 using TriggerSensorsParams = 43 std::vector<std::pair<sdbusplus::message::object_path, std::string>>; 44 45 inline triggers::TriggerActionEnum 46 toRedfishTriggerAction(std::string_view dbusValue) 47 { 48 if (dbusValue == 49 "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.UpdateReport") 50 { 51 return triggers::TriggerActionEnum::RedfishMetricReport; 52 } 53 if (dbusValue == 54 "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToRedfishEventLog") 55 { 56 return triggers::TriggerActionEnum::RedfishEvent; 57 } 58 if (dbusValue == 59 "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToJournal") 60 { 61 return triggers::TriggerActionEnum::LogToLogService; 62 } 63 return triggers::TriggerActionEnum::Invalid; 64 } 65 66 inline std::string toDbusTriggerAction(std::string_view redfishValue) 67 { 68 if (redfishValue == "RedfishMetricReport") 69 { 70 return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.UpdateReport"; 71 } 72 if (redfishValue == "RedfishEvent") 73 { 74 return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToRedfishEventLog"; 75 } 76 if (redfishValue == "LogToLogService") 77 { 78 return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToJournal"; 79 } 80 return ""; 81 } 82 83 inline std::string toDbusSeverity(std::string_view redfishValue) 84 { 85 if (redfishValue == "OK") 86 { 87 return "xyz.openbmc_project.Telemetry.Trigger.Severity.OK"; 88 } 89 if (redfishValue == "Warning") 90 { 91 return "xyz.openbmc_project.Telemetry.Trigger.Severity.Warning"; 92 } 93 if (redfishValue == "Critical") 94 { 95 return "xyz.openbmc_project.Telemetry.Trigger.Severity.Critical"; 96 } 97 return ""; 98 } 99 100 inline resource::Health toRedfishSeverity(std::string_view dbusValue) 101 { 102 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.OK") 103 { 104 return resource::Health::OK; 105 } 106 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.Warning") 107 { 108 return resource::Health::Warning; 109 } 110 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.Critical") 111 { 112 return resource::Health::Critical; 113 } 114 return resource::Health::Invalid; 115 } 116 117 inline std::string toRedfishThresholdName(std::string_view dbusValue) 118 { 119 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical") 120 { 121 return "UpperCritical"; 122 } 123 124 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical") 125 { 126 return "LowerCritical"; 127 } 128 129 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning") 130 { 131 return "UpperWarning"; 132 } 133 134 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning") 135 { 136 return "LowerWarning"; 137 } 138 139 return ""; 140 } 141 142 inline std::string toDbusActivation(std::string_view redfishValue) 143 { 144 if (redfishValue == "Either") 145 { 146 return "xyz.openbmc_project.Telemetry.Trigger.Direction.Either"; 147 } 148 149 if (redfishValue == "Decreasing") 150 { 151 return "xyz.openbmc_project.Telemetry.Trigger.Direction.Decreasing"; 152 } 153 154 if (redfishValue == "Increasing") 155 { 156 return "xyz.openbmc_project.Telemetry.Trigger.Direction.Increasing"; 157 } 158 159 return ""; 160 } 161 162 inline triggers::ThresholdActivation 163 toRedfishActivation(std::string_view dbusValue) 164 { 165 if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Direction.Either") 166 { 167 return triggers::ThresholdActivation::Either; 168 } 169 170 if (dbusValue == 171 "xyz.openbmc_project.Telemetry.Trigger.Direction.Decreasing") 172 { 173 return triggers::ThresholdActivation::Decreasing; 174 } 175 176 if (dbusValue == 177 "xyz.openbmc_project.Telemetry.Trigger.Direction.Increasing") 178 { 179 return triggers::ThresholdActivation::Increasing; 180 } 181 182 return triggers::ThresholdActivation::Invalid; 183 } 184 185 enum class MetricType 186 { 187 Discrete, 188 Numeric 189 }; 190 191 enum class DiscreteCondition 192 { 193 Specified, 194 Changed 195 }; 196 197 struct Context 198 { 199 std::string id; 200 std::string name; 201 std::vector<std::string> actions; 202 std::vector<std::pair<sdbusplus::message::object_path, std::string>> 203 sensors; 204 std::vector<sdbusplus::message::object_path> reports; 205 std::vector<NumericThresholdParams> numericThresholds; 206 std::vector<DiscreteThresholdParams> discreteThresholds; 207 std::optional<DiscreteCondition> discreteCondition; 208 std::optional<MetricType> metricType; 209 std::optional<std::vector<std::string>> metricProperties; 210 }; 211 212 inline std::optional<sdbusplus::message::object_path> 213 getReportPathFromReportDefinitionUri(const std::string& uri) 214 { 215 boost::system::result<boost::urls::url_view> parsed = 216 boost::urls::parse_relative_ref(uri); 217 218 if (!parsed) 219 { 220 return std::nullopt; 221 } 222 223 std::string id; 224 if (!crow::utility::readUrlSegments( 225 *parsed, "redfish", "v1", "TelemetryService", 226 "MetricReportDefinitions", std::ref(id))) 227 { 228 return std::nullopt; 229 } 230 231 return sdbusplus::message::object_path( 232 "/xyz/openbmc_project/Telemetry/Reports") / 233 "TelemetryService" / id; 234 } 235 236 inline std::optional<MetricType> getMetricType(const std::string& metricType) 237 { 238 if (metricType == "Discrete") 239 { 240 return MetricType::Discrete; 241 } 242 if (metricType == "Numeric") 243 { 244 return MetricType::Numeric; 245 } 246 return std::nullopt; 247 } 248 249 inline std::optional<DiscreteCondition> 250 getDiscreteCondition(const std::string& discreteTriggerCondition) 251 { 252 if (discreteTriggerCondition == "Specified") 253 { 254 return DiscreteCondition::Specified; 255 } 256 if (discreteTriggerCondition == "Changed") 257 { 258 return DiscreteCondition::Changed; 259 } 260 return std::nullopt; 261 } 262 263 inline bool parseThreshold(crow::Response& res, 264 nlohmann::json::object_t& threshold, 265 std::string_view dbusThresholdName, 266 std::vector<NumericThresholdParams>& parsedParams) 267 { 268 double reading = 0.0; 269 std::string activation; 270 std::string dwellTimeStr; 271 272 if (!json_util::readJsonObject( // 273 threshold, res, // 274 "Activation", activation, // 275 "DwellTime", dwellTimeStr, // 276 "Reading", reading // 277 )) 278 { 279 return false; 280 } 281 282 std::string dbusActivation = toDbusActivation(activation); 283 284 if (dbusActivation.empty()) 285 { 286 messages::propertyValueIncorrect(res, "Activation", activation); 287 return false; 288 } 289 290 std::optional<std::chrono::milliseconds> dwellTime = 291 time_utils::fromDurationString(dwellTimeStr); 292 if (!dwellTime) 293 { 294 messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); 295 return false; 296 } 297 298 parsedParams.emplace_back(dbusThresholdName, 299 static_cast<uint64_t>(dwellTime->count()), 300 dbusActivation, reading); 301 return true; 302 } 303 304 struct NumericThresholds 305 { 306 std::optional<nlohmann::json::object_t> upperCritical; 307 std::optional<nlohmann::json::object_t> upperWarning; 308 std::optional<nlohmann::json::object_t> lowerWarning; 309 std::optional<nlohmann::json::object_t> lowerCritical; 310 311 bool any() const 312 { 313 return upperCritical || upperWarning || lowerWarning || lowerCritical; 314 } 315 }; 316 317 inline bool parseNumericThresholds( 318 crow::Response& res, NumericThresholds& numericThresholds, Context& ctx) 319 { 320 std::vector<NumericThresholdParams> parsedParams; 321 if (numericThresholds.upperCritical) 322 { 323 if (!parseThreshold( 324 res, *numericThresholds.upperCritical, 325 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical", 326 parsedParams)) 327 { 328 return false; 329 } 330 } 331 if (numericThresholds.upperWarning) 332 { 333 if (!parseThreshold( 334 res, *numericThresholds.upperWarning, 335 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning", 336 parsedParams)) 337 { 338 return false; 339 } 340 } 341 if (numericThresholds.lowerWarning) 342 { 343 if (!parseThreshold( 344 res, *numericThresholds.lowerWarning, 345 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning", 346 parsedParams)) 347 { 348 return false; 349 } 350 } 351 if (numericThresholds.lowerCritical) 352 { 353 if (!parseThreshold( 354 res, *numericThresholds.lowerCritical, 355 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical", 356 parsedParams)) 357 { 358 return false; 359 } 360 } 361 362 ctx.numericThresholds = std::move(parsedParams); 363 return true; 364 } 365 366 inline bool parseDiscreteTriggers( 367 crow::Response& res, 368 std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers, 369 Context& ctx) 370 { 371 std::vector<DiscreteThresholdParams> parsedParams; 372 if (!discreteTriggers) 373 { 374 ctx.discreteThresholds = std::move(parsedParams); 375 return true; 376 } 377 378 parsedParams.reserve(discreteTriggers->size()); 379 for (nlohmann::json::object_t& thresholdInfo : *discreteTriggers) 380 { 381 std::optional<std::string> name = ""; 382 std::string value; 383 std::string dwellTimeStr; 384 std::string severity; 385 386 if (!json_util::readJsonObject( // 387 thresholdInfo, res, // 388 "DwellTime", dwellTimeStr, // 389 "Name", name, // 390 "Severity", severity, // 391 "Value", value // 392 )) 393 { 394 return false; 395 } 396 397 std::optional<std::chrono::milliseconds> dwellTime = 398 time_utils::fromDurationString(dwellTimeStr); 399 if (!dwellTime) 400 { 401 messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); 402 return false; 403 } 404 405 std::string dbusSeverity = toDbusSeverity(severity); 406 if (dbusSeverity.empty()) 407 { 408 messages::propertyValueIncorrect(res, "Severity", severity); 409 return false; 410 } 411 412 parsedParams.emplace_back(*name, dbusSeverity, 413 static_cast<uint64_t>(dwellTime->count()), 414 value); 415 } 416 417 ctx.discreteThresholds = std::move(parsedParams); 418 return true; 419 } 420 421 inline bool parseTriggerThresholds( 422 crow::Response& res, 423 std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers, 424 NumericThresholds& numericThresholds, Context& ctx) 425 { 426 if (discreteTriggers && numericThresholds.any()) 427 { 428 messages::propertyValueConflict(res, "DiscreteTriggers", 429 "NumericThresholds"); 430 messages::propertyValueConflict(res, "NumericThresholds", 431 "DiscreteTriggers"); 432 return false; 433 } 434 435 if (ctx.discreteCondition) 436 { 437 if (numericThresholds.any()) 438 { 439 messages::propertyValueConflict(res, "DiscreteTriggerCondition", 440 "NumericThresholds"); 441 messages::propertyValueConflict(res, "NumericThresholds", 442 "DiscreteTriggerCondition"); 443 return false; 444 } 445 } 446 447 if (ctx.metricType) 448 { 449 if (*ctx.metricType == MetricType::Discrete && numericThresholds.any()) 450 { 451 messages::propertyValueConflict(res, "NumericThresholds", 452 "MetricType"); 453 return false; 454 } 455 if (*ctx.metricType == MetricType::Numeric && discreteTriggers) 456 { 457 messages::propertyValueConflict(res, "DiscreteTriggers", 458 "MetricType"); 459 return false; 460 } 461 if (*ctx.metricType == MetricType::Numeric && ctx.discreteCondition) 462 { 463 messages::propertyValueConflict(res, "DiscreteTriggers", 464 "DiscreteTriggerCondition"); 465 return false; 466 } 467 } 468 469 if (discreteTriggers || ctx.discreteCondition || 470 (ctx.metricType && *ctx.metricType == MetricType::Discrete)) 471 { 472 if (ctx.discreteCondition) 473 { 474 if (*ctx.discreteCondition == DiscreteCondition::Specified && 475 !discreteTriggers) 476 { 477 messages::createFailedMissingReqProperties(res, 478 "DiscreteTriggers"); 479 return false; 480 } 481 if (discreteTriggers && 482 ((*ctx.discreteCondition == DiscreteCondition::Specified && 483 discreteTriggers->empty()) || 484 (*ctx.discreteCondition == DiscreteCondition::Changed && 485 !discreteTriggers->empty()))) 486 { 487 messages::propertyValueConflict(res, "DiscreteTriggers", 488 "DiscreteTriggerCondition"); 489 return false; 490 } 491 } 492 if (!parseDiscreteTriggers(res, discreteTriggers, ctx)) 493 { 494 return false; 495 } 496 } 497 else if (numericThresholds.any()) 498 { 499 if (!parseNumericThresholds(res, numericThresholds, ctx)) 500 { 501 return false; 502 } 503 } 504 else 505 { 506 messages::createFailedMissingReqProperties( 507 res, "'DiscreteTriggers', 'NumericThresholds', " 508 "'DiscreteTriggerCondition' or 'MetricType'"); 509 return false; 510 } 511 return true; 512 } 513 514 inline bool parseLinks(crow::Response& res, 515 const std::vector<std::string>& metricReportDefinitions, 516 Context& ctx) 517 { 518 ctx.reports.reserve(metricReportDefinitions.size()); 519 for (const std::string& reportDefinionUri : metricReportDefinitions) 520 { 521 std::optional<sdbusplus::message::object_path> reportPath = 522 getReportPathFromReportDefinitionUri(reportDefinionUri); 523 if (!reportPath) 524 { 525 messages::propertyValueIncorrect(res, "MetricReportDefinitions", 526 reportDefinionUri); 527 return false; 528 } 529 ctx.reports.emplace_back(*reportPath); 530 } 531 return true; 532 } 533 534 inline bool parseMetricProperties(crow::Response& res, Context& ctx) 535 { 536 if (!ctx.metricProperties) 537 { 538 return true; 539 } 540 541 ctx.sensors.reserve(ctx.metricProperties->size()); 542 543 size_t uriIdx = 0; 544 for (const std::string& uriStr : *ctx.metricProperties) 545 { 546 boost::system::result<boost::urls::url> uri = 547 boost::urls::parse_relative_ref(uriStr); 548 if (!uri) 549 { 550 messages::propertyValueIncorrect( 551 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 552 return false; 553 } 554 std::string chassisName; 555 std::string sensorName; 556 if (!crow::utility::readUrlSegments(*uri, "redfish", "v1", "Chassis", 557 std::ref(chassisName), "Sensors", 558 std::ref(sensorName))) 559 { 560 messages::propertyValueIncorrect( 561 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 562 return false; 563 } 564 565 std::pair<std::string, std::string> split = 566 redfish::sensor_utils::splitSensorNameAndType(sensorName); 567 if (split.first.empty() || split.second.empty()) 568 { 569 messages::propertyValueIncorrect( 570 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 571 return false; 572 } 573 574 std::string sensorPath = 575 "/xyz/openbmc_project/sensors/" + split.first + '/' + split.second; 576 577 ctx.sensors.emplace_back(sensorPath, uriStr); 578 uriIdx++; 579 } 580 return true; 581 } 582 583 inline bool parsePostTriggerParams(crow::Response& res, 584 const crow::Request& req, Context& ctx) 585 { 586 std::optional<std::string> id = ""; 587 std::optional<std::string> name = ""; 588 std::optional<std::string> metricType; 589 std::optional<std::vector<std::string>> triggerActions; 590 std::optional<std::string> discreteTriggerCondition; 591 std::optional<std::vector<nlohmann::json::object_t>> discreteTriggers; 592 std::optional<std::vector<std::string>> metricReportDefinitions; 593 NumericThresholds thresholds; 594 595 if (!json_util::readJsonPatch( // 596 req, res, // 597 "Id", id, // 598 "DiscreteTriggerCondition", discreteTriggerCondition, // 599 "DiscreteTriggers", discreteTriggers, // 600 "Links/MetricReportDefinitions", metricReportDefinitions, // 601 "MetricProperties", ctx.metricProperties, // 602 "MetricType", metricType, // 603 "Name", name, // 604 "NumericThresholds/LowerCritical", thresholds.lowerCritical, // 605 "NumericThresholds/LowerWarning", thresholds.lowerWarning, // 606 "NumericThresholds/UpperCritical", thresholds.upperCritical, // 607 "NumericThresholds/UpperWarning", thresholds.upperWarning, // 608 "TriggerActions", triggerActions // 609 )) 610 { 611 return false; 612 } 613 614 ctx.id = *id; 615 ctx.name = *name; 616 617 if (metricType) 618 { 619 ctx.metricType = getMetricType(*metricType); 620 if (!ctx.metricType) 621 { 622 messages::propertyValueIncorrect(res, "MetricType", *metricType); 623 return false; 624 } 625 } 626 627 if (discreteTriggerCondition) 628 { 629 ctx.discreteCondition = getDiscreteCondition(*discreteTriggerCondition); 630 if (!ctx.discreteCondition) 631 { 632 messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", 633 *discreteTriggerCondition); 634 return false; 635 } 636 } 637 638 if (triggerActions) 639 { 640 ctx.actions.reserve(triggerActions->size()); 641 for (const std::string& action : *triggerActions) 642 { 643 std::string dbusAction = toDbusTriggerAction(action); 644 645 if (dbusAction.empty()) 646 { 647 messages::propertyValueNotInList(res, action, "TriggerActions"); 648 return false; 649 } 650 651 ctx.actions.emplace_back(dbusAction); 652 } 653 } 654 if (!parseMetricProperties(res, ctx)) 655 { 656 return false; 657 } 658 659 if (!parseTriggerThresholds(res, discreteTriggers, thresholds, ctx)) 660 { 661 return false; 662 } 663 664 if (metricReportDefinitions) 665 { 666 if (!parseLinks(res, *metricReportDefinitions, ctx)) 667 { 668 return false; 669 } 670 } 671 return true; 672 } 673 674 inline void afterCreateTrigger( 675 const boost::system::error_code& ec, const std::string& dbusPath, 676 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 677 { 678 if (ec == boost::system::errc::file_exists) 679 { 680 messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id); 681 return; 682 } 683 if (ec == boost::system::errc::too_many_files_open) 684 { 685 messages::createLimitReachedForResource(asyncResp->res); 686 return; 687 } 688 if (ec) 689 { 690 messages::internalError(asyncResp->res); 691 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 692 return; 693 } 694 695 const std::optional<std::string>& triggerId = 696 getTriggerIdFromDbusPath(dbusPath); 697 if (!triggerId) 698 { 699 messages::internalError(asyncResp->res); 700 BMCWEB_LOG_ERROR("Unknown data returned by " 701 "AddTrigger DBus method"); 702 return; 703 } 704 705 messages::created(asyncResp->res); 706 boost::urls::url locationUrl = boost::urls::format( 707 "/redfish/v1/TelemetryService/Triggers/{}", *triggerId); 708 asyncResp->res.addHeader("Location", locationUrl.buffer()); 709 } 710 711 inline std::optional<nlohmann::json::array_t> 712 getTriggerActions(const std::vector<std::string>& dbusActions) 713 { 714 nlohmann::json::array_t triggerActions; 715 for (const std::string& dbusAction : dbusActions) 716 { 717 triggers::TriggerActionEnum redfishAction = 718 toRedfishTriggerAction(dbusAction); 719 720 if (redfishAction == triggers::TriggerActionEnum::Invalid) 721 { 722 return std::nullopt; 723 } 724 725 triggerActions.emplace_back(redfishAction); 726 } 727 728 return triggerActions; 729 } 730 731 inline std::optional<nlohmann::json::array_t> getDiscreteTriggers( 732 const std::vector<DiscreteThresholdParams>& discreteParams) 733 { 734 nlohmann::json::array_t triggers; 735 for (const auto& [name, severity, dwellTime, value] : discreteParams) 736 { 737 std::optional<std::string> duration = 738 time_utils::toDurationStringFromUint(dwellTime); 739 740 if (!duration) 741 { 742 return std::nullopt; 743 } 744 nlohmann::json::object_t trigger; 745 trigger["Name"] = name; 746 trigger["Severity"] = toRedfishSeverity(severity); 747 trigger["DwellTime"] = *duration; 748 trigger["Value"] = value; 749 triggers.emplace_back(std::move(trigger)); 750 } 751 752 return triggers; 753 } 754 755 inline std::optional<nlohmann::json::object_t> getNumericThresholds( 756 const std::vector<NumericThresholdParams>& numericParams) 757 { 758 nlohmann::json::object_t thresholds; 759 760 for (const auto& [type, dwellTime, activation, reading] : numericParams) 761 { 762 std::optional<std::string> duration = 763 time_utils::toDurationStringFromUint(dwellTime); 764 765 if (!duration) 766 { 767 return std::nullopt; 768 } 769 nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)]; 770 threshold["Reading"] = reading; 771 threshold["Activation"] = toRedfishActivation(activation); 772 threshold["DwellTime"] = *duration; 773 } 774 775 return thresholds; 776 } 777 778 inline std::optional<nlohmann::json> getMetricReportDefinitions( 779 const std::vector<sdbusplus::message::object_path>& reportPaths) 780 { 781 nlohmann::json reports = nlohmann::json::array(); 782 783 for (const sdbusplus::message::object_path& path : reportPaths) 784 { 785 std::string reportId = path.filename(); 786 if (reportId.empty()) 787 { 788 { 789 BMCWEB_LOG_ERROR("Property Reports contains invalid value: {}", 790 path.str); 791 return std::nullopt; 792 } 793 } 794 795 nlohmann::json::object_t report; 796 report["@odata.id"] = boost::urls::format( 797 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 798 reportId); 799 reports.emplace_back(std::move(report)); 800 } 801 802 return {std::move(reports)}; 803 } 804 805 inline std::vector<std::string> 806 getMetricProperties(const TriggerSensorsParams& sensors) 807 { 808 std::vector<std::string> metricProperties; 809 metricProperties.reserve(sensors.size()); 810 for (const auto& [_, metadata] : sensors) 811 { 812 metricProperties.emplace_back(metadata); 813 } 814 815 return metricProperties; 816 } 817 818 inline bool fillTrigger(nlohmann::json& json, const std::string& id, 819 const dbus::utility::DBusPropertiesMap& properties) 820 { 821 const std::string* name = nullptr; 822 const bool* discrete = nullptr; 823 const TriggerSensorsParams* sensors = nullptr; 824 const std::vector<sdbusplus::message::object_path>* reports = nullptr; 825 const std::vector<std::string>* triggerActions = nullptr; 826 827 const std::vector<DiscreteThresholdParams>* discreteThresholds = nullptr; 828 const std::vector<NumericThresholdParams>* numericThresholds = nullptr; 829 830 const bool success = sdbusplus::unpackPropertiesNoThrow( 831 dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete", 832 discrete, "Sensors", sensors, "Reports", reports, "TriggerActions", 833 triggerActions, "DiscreteThresholds", discreteThresholds, 834 "NumericThresholds", numericThresholds); 835 836 if (!success) 837 { 838 return false; 839 } 840 841 if (triggerActions != nullptr) 842 { 843 std::optional<nlohmann::json::array_t> redfishTriggerActions = 844 getTriggerActions(*triggerActions); 845 if (!redfishTriggerActions) 846 { 847 BMCWEB_LOG_ERROR( 848 "Property TriggerActions is invalid in Trigger: {}", id); 849 return false; 850 } 851 json["TriggerActions"] = *redfishTriggerActions; 852 } 853 854 if (reports != nullptr) 855 { 856 std::optional<nlohmann::json> linkedReports = 857 getMetricReportDefinitions(*reports); 858 if (!linkedReports) 859 { 860 BMCWEB_LOG_ERROR("Property Reports is invalid in Trigger: {}", id); 861 return false; 862 } 863 json["Links"]["MetricReportDefinitions"] = *linkedReports; 864 } 865 866 if (discreteThresholds != nullptr) 867 { 868 std::optional<nlohmann::json::array_t> discreteTriggers = 869 getDiscreteTriggers(*discreteThresholds); 870 871 if (!discreteTriggers) 872 { 873 BMCWEB_LOG_ERROR("Property Thresholds is invalid for discrete " 874 "triggers in Trigger: {}", 875 id); 876 return false; 877 } 878 879 json["DiscreteTriggers"] = *discreteTriggers; 880 json["DiscreteTriggerCondition"] = 881 discreteTriggers->empty() ? "Changed" : "Specified"; 882 json["MetricType"] = metric_definition::MetricType::Discrete; 883 } 884 if (numericThresholds != nullptr) 885 { 886 std::optional<nlohmann::json::object_t> jnumericThresholds = 887 getNumericThresholds(*numericThresholds); 888 889 if (!jnumericThresholds) 890 { 891 BMCWEB_LOG_ERROR("Property Thresholds is invalid for numeric " 892 "thresholds in Trigger: {}", 893 id); 894 return false; 895 } 896 897 json["NumericThresholds"] = *jnumericThresholds; 898 json["MetricType"] = metric_definition::MetricType::Numeric; 899 } 900 901 if (name != nullptr) 902 { 903 json["Name"] = *name; 904 } 905 906 if (sensors != nullptr) 907 { 908 json["MetricProperties"] = getMetricProperties(*sensors); 909 } 910 911 json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; 912 json["@odata.id"] = 913 boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id); 914 json["Id"] = id; 915 916 return true; 917 } 918 919 inline void handleTriggerCollectionPost( 920 App& app, const crow::Request& req, 921 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 922 { 923 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 924 { 925 return; 926 } 927 928 telemetry::Context ctx; 929 if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx)) 930 { 931 return; 932 } 933 934 crow::connections::systemBus->async_method_call( 935 [asyncResp, id = ctx.id](const boost::system::error_code& ec, 936 const std::string& dbusPath) { 937 afterCreateTrigger(ec, dbusPath, asyncResp, id); 938 }, 939 service, "/xyz/openbmc_project/Telemetry/Triggers", 940 "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", 941 "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors, 942 ctx.reports, ctx.numericThresholds, ctx.discreteThresholds); 943 } 944 945 } // namespace telemetry 946 947 inline void requestRoutesTriggerCollection(App& app) 948 { 949 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 950 .privileges(redfish::privileges::getTriggersCollection) 951 .methods(boost::beast::http::verb::get)( 952 [&app](const crow::Request& req, 953 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 954 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 955 { 956 return; 957 } 958 asyncResp->res.jsonValue["@odata.type"] = 959 "#TriggersCollection.TriggersCollection"; 960 asyncResp->res.jsonValue["@odata.id"] = 961 "/redfish/v1/TelemetryService/Triggers"; 962 asyncResp->res.jsonValue["Name"] = "Triggers Collection"; 963 constexpr std::array<std::string_view, 1> interfaces{ 964 telemetry::triggerInterface}; 965 collection_util::getCollectionMembers( 966 asyncResp, 967 boost::urls::url("/redfish/v1/TelemetryService/Triggers"), 968 interfaces, 969 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); 970 }); 971 972 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 973 .privileges(redfish::privileges::postTriggersCollection) 974 .methods(boost::beast::http::verb::post)(std::bind_front( 975 telemetry::handleTriggerCollectionPost, std::ref(app))); 976 } 977 978 inline void requestRoutesTrigger(App& app) 979 { 980 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 981 .privileges(redfish::privileges::getTriggers) 982 .methods(boost::beast::http::verb::get)( 983 [&app](const crow::Request& req, 984 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 985 const std::string& id) { 986 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 987 { 988 return; 989 } 990 sdbusplus::asio::getAllProperties( 991 *crow::connections::systemBus, telemetry::service, 992 telemetry::getDbusTriggerPath(id), 993 telemetry::triggerInterface, 994 [asyncResp, 995 id](const boost::system::error_code& ec, 996 const dbus::utility::DBusPropertiesMap& ret) { 997 if (ec.value() == EBADR || 998 ec == boost::system::errc::host_unreachable) 999 { 1000 messages::resourceNotFound(asyncResp->res, 1001 "Triggers", id); 1002 return; 1003 } 1004 if (ec) 1005 { 1006 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1007 messages::internalError(asyncResp->res); 1008 return; 1009 } 1010 1011 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, 1012 id, ret)) 1013 { 1014 messages::internalError(asyncResp->res); 1015 } 1016 }); 1017 }); 1018 1019 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 1020 .privileges(redfish::privileges::deleteTriggers) 1021 .methods(boost::beast::http::verb::delete_)( 1022 [&app](const crow::Request& req, 1023 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1024 const std::string& id) { 1025 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1026 { 1027 return; 1028 } 1029 const std::string triggerPath = 1030 telemetry::getDbusTriggerPath(id); 1031 1032 crow::connections::systemBus->async_method_call( 1033 [asyncResp, id](const boost::system::error_code& ec) { 1034 if (ec.value() == EBADR) 1035 { 1036 messages::resourceNotFound(asyncResp->res, 1037 "Triggers", id); 1038 return; 1039 } 1040 1041 if (ec) 1042 { 1043 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1044 messages::internalError(asyncResp->res); 1045 return; 1046 } 1047 1048 asyncResp->res.result( 1049 boost::beast::http::status::no_content); 1050 }, 1051 telemetry::service, triggerPath, 1052 "xyz.openbmc_project.Object.Delete", "Delete"); 1053 }); 1054 } 1055 1056 } // namespace redfish 1057