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