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 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( 326 crow::Response& res, NumericThresholds& numericThresholds, 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 redfish::sensor_utils::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 = 579 "/xyz/openbmc_project/sensors/" + split.first + '/' + 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"] = metric_definition::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"] = metric_definition::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), 1015 telemetry::triggerInterface, 1016 [asyncResp, 1017 id](const boost::system::error_code& ec, 1018 const std::vector<std::pair< 1019 std::string, telemetry::TriggerGetParamsVariant>>& 1020 ret) { 1021 if (ec.value() == EBADR || 1022 ec == boost::system::errc::host_unreachable) 1023 { 1024 messages::resourceNotFound(asyncResp->res, 1025 "Triggers", id); 1026 return; 1027 } 1028 if (ec) 1029 { 1030 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1031 messages::internalError(asyncResp->res); 1032 return; 1033 } 1034 1035 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, 1036 id, ret)) 1037 { 1038 messages::internalError(asyncResp->res); 1039 } 1040 }); 1041 }); 1042 1043 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 1044 .privileges(redfish::privileges::deleteTriggers) 1045 .methods(boost::beast::http::verb::delete_)( 1046 [&app](const crow::Request& req, 1047 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1048 const std::string& id) { 1049 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1050 { 1051 return; 1052 } 1053 const std::string triggerPath = 1054 telemetry::getDbusTriggerPath(id); 1055 1056 crow::connections::systemBus->async_method_call( 1057 [asyncResp, id](const boost::system::error_code& ec) { 1058 if (ec.value() == EBADR) 1059 { 1060 messages::resourceNotFound(asyncResp->res, 1061 "Triggers", id); 1062 return; 1063 } 1064 1065 if (ec) 1066 { 1067 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1068 messages::internalError(asyncResp->res); 1069 return; 1070 } 1071 1072 asyncResp->res.result( 1073 boost::beast::http::status::no_content); 1074 }, 1075 telemetry::service, triggerPath, 1076 "xyz.openbmc_project.Object.Delete", "Delete"); 1077 }); 1078 } 1079 1080 } // namespace redfish 1081