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