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