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