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( // 284 threshold, res, // 285 "Activation", activation, // 286 "DwellTime", dwellTimeStr, // 287 "Reading", reading // 288 )) 289 { 290 return false; 291 } 292 293 std::string dbusActivation = toDbusActivation(activation); 294 295 if (dbusActivation.empty()) 296 { 297 messages::propertyValueIncorrect(res, "Activation", activation); 298 return false; 299 } 300 301 std::optional<std::chrono::milliseconds> dwellTime = 302 time_utils::fromDurationString(dwellTimeStr); 303 if (!dwellTime) 304 { 305 messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); 306 return false; 307 } 308 309 parsedParams.emplace_back(dbusThresholdName, 310 static_cast<uint64_t>(dwellTime->count()), 311 dbusActivation, reading); 312 return true; 313 } 314 315 struct NumericThresholds 316 { 317 std::optional<nlohmann::json::object_t> upperCritical; 318 std::optional<nlohmann::json::object_t> upperWarning; 319 std::optional<nlohmann::json::object_t> lowerWarning; 320 std::optional<nlohmann::json::object_t> lowerCritical; 321 322 bool any() const 323 { 324 return upperCritical || upperWarning || lowerWarning || lowerCritical; 325 } 326 }; 327 328 inline bool parseNumericThresholds( 329 crow::Response& res, NumericThresholds& numericThresholds, Context& ctx) 330 { 331 std::vector<NumericThresholdParams> parsedParams; 332 if (numericThresholds.upperCritical) 333 { 334 if (!parseThreshold( 335 res, *numericThresholds.upperCritical, 336 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical", 337 parsedParams)) 338 { 339 return false; 340 } 341 } 342 if (numericThresholds.upperWarning) 343 { 344 if (!parseThreshold( 345 res, *numericThresholds.upperWarning, 346 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning", 347 parsedParams)) 348 { 349 return false; 350 } 351 } 352 if (numericThresholds.lowerWarning) 353 { 354 if (!parseThreshold( 355 res, *numericThresholds.lowerWarning, 356 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning", 357 parsedParams)) 358 { 359 return false; 360 } 361 } 362 if (numericThresholds.lowerCritical) 363 { 364 if (!parseThreshold( 365 res, *numericThresholds.lowerCritical, 366 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical", 367 parsedParams)) 368 { 369 return false; 370 } 371 } 372 373 ctx.thresholds = std::move(parsedParams); 374 return true; 375 } 376 377 inline bool parseDiscreteTriggers( 378 crow::Response& res, 379 std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers, 380 Context& ctx) 381 { 382 std::vector<DiscreteThresholdParams> parsedParams; 383 if (!discreteTriggers) 384 { 385 ctx.thresholds = std::move(parsedParams); 386 return true; 387 } 388 389 parsedParams.reserve(discreteTriggers->size()); 390 for (nlohmann::json::object_t& thresholdInfo : *discreteTriggers) 391 { 392 std::optional<std::string> name = ""; 393 std::string value; 394 std::string dwellTimeStr; 395 std::string severity; 396 397 if (!json_util::readJsonObject( // 398 thresholdInfo, res, // 399 "DwellTime", dwellTimeStr, // 400 "Name", name, // 401 "Severity", severity, // 402 "Value", value // 403 )) 404 { 405 return false; 406 } 407 408 std::optional<std::chrono::milliseconds> dwellTime = 409 time_utils::fromDurationString(dwellTimeStr); 410 if (!dwellTime) 411 { 412 messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); 413 return false; 414 } 415 416 std::string dbusSeverity = toDbusSeverity(severity); 417 if (dbusSeverity.empty()) 418 { 419 messages::propertyValueIncorrect(res, "Severity", severity); 420 return false; 421 } 422 423 parsedParams.emplace_back(*name, dbusSeverity, 424 static_cast<uint64_t>(dwellTime->count()), 425 value); 426 } 427 428 ctx.thresholds = std::move(parsedParams); 429 return true; 430 } 431 432 inline bool parseTriggerThresholds( 433 crow::Response& res, 434 std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers, 435 NumericThresholds& numericThresholds, Context& ctx) 436 { 437 if (discreteTriggers && numericThresholds.any()) 438 { 439 messages::propertyValueConflict(res, "DiscreteTriggers", 440 "NumericThresholds"); 441 messages::propertyValueConflict(res, "NumericThresholds", 442 "DiscreteTriggers"); 443 return false; 444 } 445 446 if (ctx.discreteCondition) 447 { 448 if (numericThresholds.any()) 449 { 450 messages::propertyValueConflict(res, "DiscreteTriggerCondition", 451 "NumericThresholds"); 452 messages::propertyValueConflict(res, "NumericThresholds", 453 "DiscreteTriggerCondition"); 454 return false; 455 } 456 } 457 458 if (ctx.metricType) 459 { 460 if (*ctx.metricType == MetricType::Discrete && numericThresholds.any()) 461 { 462 messages::propertyValueConflict(res, "NumericThresholds", 463 "MetricType"); 464 return false; 465 } 466 if (*ctx.metricType == MetricType::Numeric && discreteTriggers) 467 { 468 messages::propertyValueConflict(res, "DiscreteTriggers", 469 "MetricType"); 470 return false; 471 } 472 if (*ctx.metricType == MetricType::Numeric && ctx.discreteCondition) 473 { 474 messages::propertyValueConflict(res, "DiscreteTriggers", 475 "DiscreteTriggerCondition"); 476 return false; 477 } 478 } 479 480 if (discreteTriggers || ctx.discreteCondition || 481 (ctx.metricType && *ctx.metricType == MetricType::Discrete)) 482 { 483 if (ctx.discreteCondition) 484 { 485 if (*ctx.discreteCondition == DiscreteCondition::Specified && 486 !discreteTriggers) 487 { 488 messages::createFailedMissingReqProperties(res, 489 "DiscreteTriggers"); 490 return false; 491 } 492 if (discreteTriggers && 493 ((*ctx.discreteCondition == DiscreteCondition::Specified && 494 discreteTriggers->empty()) || 495 (*ctx.discreteCondition == DiscreteCondition::Changed && 496 !discreteTriggers->empty()))) 497 { 498 messages::propertyValueConflict(res, "DiscreteTriggers", 499 "DiscreteTriggerCondition"); 500 return false; 501 } 502 } 503 if (!parseDiscreteTriggers(res, discreteTriggers, ctx)) 504 { 505 return false; 506 } 507 } 508 else if (numericThresholds.any()) 509 { 510 if (!parseNumericThresholds(res, numericThresholds, ctx)) 511 { 512 return false; 513 } 514 } 515 else 516 { 517 messages::createFailedMissingReqProperties( 518 res, "'DiscreteTriggers', 'NumericThresholds', " 519 "'DiscreteTriggerCondition' or 'MetricType'"); 520 return false; 521 } 522 return true; 523 } 524 525 inline bool parseLinks(crow::Response& res, 526 const std::vector<std::string>& metricReportDefinitions, 527 Context& ctx) 528 { 529 ctx.reports.reserve(metricReportDefinitions.size()); 530 for (const std::string& reportDefinionUri : metricReportDefinitions) 531 { 532 std::optional<sdbusplus::message::object_path> reportPath = 533 getReportPathFromReportDefinitionUri(reportDefinionUri); 534 if (!reportPath) 535 { 536 messages::propertyValueIncorrect(res, "MetricReportDefinitions", 537 reportDefinionUri); 538 return false; 539 } 540 ctx.reports.emplace_back(*reportPath); 541 } 542 return true; 543 } 544 545 inline bool parseMetricProperties(crow::Response& res, Context& ctx) 546 { 547 if (!ctx.metricProperties) 548 { 549 return true; 550 } 551 552 ctx.sensors.reserve(ctx.metricProperties->size()); 553 554 size_t uriIdx = 0; 555 for (const std::string& uriStr : *ctx.metricProperties) 556 { 557 boost::system::result<boost::urls::url> uri = 558 boost::urls::parse_relative_ref(uriStr); 559 if (!uri) 560 { 561 messages::propertyValueIncorrect( 562 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 563 return false; 564 } 565 std::string chassisName; 566 std::string sensorName; 567 if (!crow::utility::readUrlSegments(*uri, "redfish", "v1", "Chassis", 568 std::ref(chassisName), "Sensors", 569 std::ref(sensorName))) 570 { 571 messages::propertyValueIncorrect( 572 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 573 return false; 574 } 575 576 std::pair<std::string, std::string> split = 577 redfish::sensor_utils::splitSensorNameAndType(sensorName); 578 if (split.first.empty() || split.second.empty()) 579 { 580 messages::propertyValueIncorrect( 581 res, "MetricProperties/" + std::to_string(uriIdx), uriStr); 582 return false; 583 } 584 585 std::string sensorPath = 586 "/xyz/openbmc_project/sensors/" + split.first + '/' + split.second; 587 588 ctx.sensors.emplace_back(sensorPath, uriStr); 589 uriIdx++; 590 } 591 return true; 592 } 593 594 inline bool parsePostTriggerParams(crow::Response& res, 595 const crow::Request& req, Context& ctx) 596 { 597 std::optional<std::string> id = ""; 598 std::optional<std::string> name = ""; 599 std::optional<std::string> metricType; 600 std::optional<std::vector<std::string>> triggerActions; 601 std::optional<std::string> discreteTriggerCondition; 602 std::optional<std::vector<nlohmann::json::object_t>> discreteTriggers; 603 std::optional<std::vector<std::string>> metricReportDefinitions; 604 NumericThresholds thresholds; 605 606 if (!json_util::readJsonPatch( // 607 req, res, // 608 "Id", id, // 609 "DiscreteTriggerCondition", discreteTriggerCondition, // 610 "DiscreteTriggers", discreteTriggers, // 611 "Links/MetricReportDefinitions", metricReportDefinitions, // 612 "MetricProperties", ctx.metricProperties, // 613 "MetricType", metricType, // 614 "Name", name, // 615 "NumericThresholds/LowerCritical", thresholds.lowerCritical, // 616 "NumericThresholds/LowerWarning", thresholds.lowerWarning, // 617 "NumericThresholds/UpperCritical", thresholds.upperCritical, // 618 "NumericThresholds/UpperWarning", thresholds.upperWarning, // 619 "TriggerActions", triggerActions // 620 )) 621 { 622 return false; 623 } 624 625 ctx.id = *id; 626 ctx.name = *name; 627 628 if (metricType) 629 { 630 ctx.metricType = getMetricType(*metricType); 631 if (!ctx.metricType) 632 { 633 messages::propertyValueIncorrect(res, "MetricType", *metricType); 634 return false; 635 } 636 } 637 638 if (discreteTriggerCondition) 639 { 640 ctx.discreteCondition = getDiscreteCondition(*discreteTriggerCondition); 641 if (!ctx.discreteCondition) 642 { 643 messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", 644 *discreteTriggerCondition); 645 return false; 646 } 647 } 648 649 if (triggerActions) 650 { 651 ctx.actions.reserve(triggerActions->size()); 652 for (const std::string& action : *triggerActions) 653 { 654 std::string dbusAction = toDbusTriggerAction(action); 655 656 if (dbusAction.empty()) 657 { 658 messages::propertyValueNotInList(res, action, "TriggerActions"); 659 return false; 660 } 661 662 ctx.actions.emplace_back(dbusAction); 663 } 664 } 665 if (!parseMetricProperties(res, ctx)) 666 { 667 return false; 668 } 669 670 if (!parseTriggerThresholds(res, discreteTriggers, thresholds, ctx)) 671 { 672 return false; 673 } 674 675 if (metricReportDefinitions) 676 { 677 if (!parseLinks(res, *metricReportDefinitions, ctx)) 678 { 679 return false; 680 } 681 } 682 return true; 683 } 684 685 inline void afterCreateTrigger( 686 const boost::system::error_code& ec, const std::string& dbusPath, 687 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 688 { 689 if (ec == boost::system::errc::file_exists) 690 { 691 messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id); 692 return; 693 } 694 if (ec == boost::system::errc::too_many_files_open) 695 { 696 messages::createLimitReachedForResource(asyncResp->res); 697 return; 698 } 699 if (ec) 700 { 701 messages::internalError(asyncResp->res); 702 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 703 return; 704 } 705 706 const std::optional<std::string>& triggerId = 707 getTriggerIdFromDbusPath(dbusPath); 708 if (!triggerId) 709 { 710 messages::internalError(asyncResp->res); 711 BMCWEB_LOG_ERROR("Unknown data returned by " 712 "AddTrigger DBus method"); 713 return; 714 } 715 716 messages::created(asyncResp->res); 717 boost::urls::url locationUrl = boost::urls::format( 718 "/redfish/v1/TelemetryService/Triggers/{}", *triggerId); 719 asyncResp->res.addHeader("Location", locationUrl.buffer()); 720 } 721 722 inline std::optional<nlohmann::json::array_t> 723 getTriggerActions(const std::vector<std::string>& dbusActions) 724 { 725 nlohmann::json::array_t triggerActions; 726 for (const std::string& dbusAction : dbusActions) 727 { 728 triggers::TriggerActionEnum redfishAction = 729 toRedfishTriggerAction(dbusAction); 730 731 if (redfishAction == triggers::TriggerActionEnum::Invalid) 732 { 733 return std::nullopt; 734 } 735 736 triggerActions.emplace_back(redfishAction); 737 } 738 739 return triggerActions; 740 } 741 742 inline std::optional<nlohmann::json::array_t> 743 getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams) 744 { 745 nlohmann::json::array_t triggers; 746 const std::vector<DiscreteThresholdParams>* discreteParams = 747 std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams); 748 749 if (discreteParams == nullptr) 750 { 751 return std::nullopt; 752 } 753 754 for (const auto& [name, severity, dwellTime, value] : *discreteParams) 755 { 756 std::optional<std::string> duration = 757 time_utils::toDurationStringFromUint(dwellTime); 758 759 if (!duration) 760 { 761 return std::nullopt; 762 } 763 nlohmann::json::object_t trigger; 764 trigger["Name"] = name; 765 trigger["Severity"] = toRedfishSeverity(severity); 766 trigger["DwellTime"] = *duration; 767 trigger["Value"] = value; 768 triggers.emplace_back(std::move(trigger)); 769 } 770 771 return triggers; 772 } 773 774 inline std::optional<nlohmann::json> 775 getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams) 776 { 777 nlohmann::json::object_t thresholds; 778 const std::vector<NumericThresholdParams>* numericParams = 779 std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams); 780 781 if (numericParams == nullptr) 782 { 783 return std::nullopt; 784 } 785 786 for (const auto& [type, dwellTime, activation, reading] : *numericParams) 787 { 788 std::optional<std::string> duration = 789 time_utils::toDurationStringFromUint(dwellTime); 790 791 if (!duration) 792 { 793 return std::nullopt; 794 } 795 nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)]; 796 threshold["Reading"] = reading; 797 threshold["Activation"] = toRedfishActivation(activation); 798 threshold["DwellTime"] = *duration; 799 } 800 801 return thresholds; 802 } 803 804 inline std::optional<nlohmann::json> getMetricReportDefinitions( 805 const std::vector<sdbusplus::message::object_path>& reportPaths) 806 { 807 nlohmann::json reports = nlohmann::json::array(); 808 809 for (const sdbusplus::message::object_path& path : reportPaths) 810 { 811 std::string reportId = path.filename(); 812 if (reportId.empty()) 813 { 814 { 815 BMCWEB_LOG_ERROR("Property Reports contains invalid value: {}", 816 path.str); 817 return std::nullopt; 818 } 819 } 820 821 nlohmann::json::object_t report; 822 report["@odata.id"] = boost::urls::format( 823 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 824 reportId); 825 reports.emplace_back(std::move(report)); 826 } 827 828 return {std::move(reports)}; 829 } 830 831 inline std::vector<std::string> 832 getMetricProperties(const TriggerSensorsParams& sensors) 833 { 834 std::vector<std::string> metricProperties; 835 metricProperties.reserve(sensors.size()); 836 for (const auto& [_, metadata] : sensors) 837 { 838 metricProperties.emplace_back(metadata); 839 } 840 841 return metricProperties; 842 } 843 844 inline bool fillTrigger( 845 nlohmann::json& json, const std::string& id, 846 const std::vector<std::pair<std::string, TriggerGetParamsVariant>>& 847 properties) 848 { 849 const std::string* name = nullptr; 850 const bool* discrete = nullptr; 851 const TriggerSensorsParams* sensors = nullptr; 852 const std::vector<sdbusplus::message::object_path>* reports = nullptr; 853 const std::vector<std::string>* triggerActions = nullptr; 854 const TriggerThresholdParamsExt* thresholds = nullptr; 855 856 const bool success = sdbusplus::unpackPropertiesNoThrow( 857 dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete", 858 discrete, "Sensors", sensors, "Reports", reports, "TriggerActions", 859 triggerActions, "Thresholds", thresholds); 860 861 if (!success) 862 { 863 return false; 864 } 865 866 if (triggerActions != nullptr) 867 { 868 std::optional<nlohmann::json::array_t> redfishTriggerActions = 869 getTriggerActions(*triggerActions); 870 if (!redfishTriggerActions) 871 { 872 BMCWEB_LOG_ERROR( 873 "Property TriggerActions is invalid in Trigger: {}", id); 874 return false; 875 } 876 json["TriggerActions"] = *redfishTriggerActions; 877 } 878 879 if (reports != nullptr) 880 { 881 std::optional<nlohmann::json> linkedReports = 882 getMetricReportDefinitions(*reports); 883 if (!linkedReports) 884 { 885 BMCWEB_LOG_ERROR("Property Reports is invalid in Trigger: {}", id); 886 return false; 887 } 888 json["Links"]["MetricReportDefinitions"] = *linkedReports; 889 } 890 891 if (discrete != nullptr) 892 { 893 if (*discrete) 894 { 895 std::optional<nlohmann::json::array_t> discreteTriggers = 896 getDiscreteTriggers(*thresholds); 897 898 if (!discreteTriggers) 899 { 900 BMCWEB_LOG_ERROR("Property Thresholds is invalid for discrete " 901 "triggers in Trigger: {}", 902 id); 903 return false; 904 } 905 906 json["DiscreteTriggers"] = *discreteTriggers; 907 json["DiscreteTriggerCondition"] = 908 discreteTriggers->empty() ? "Changed" : "Specified"; 909 json["MetricType"] = metric_definition::MetricType::Discrete; 910 } 911 else 912 { 913 std::optional<nlohmann::json> numericThresholds = 914 getNumericThresholds(*thresholds); 915 916 if (!numericThresholds) 917 { 918 BMCWEB_LOG_ERROR("Property Thresholds is invalid for numeric " 919 "thresholds in Trigger: {}", 920 id); 921 return false; 922 } 923 924 json["NumericThresholds"] = *numericThresholds; 925 json["MetricType"] = metric_definition::MetricType::Numeric; 926 } 927 } 928 929 if (name != nullptr) 930 { 931 json["Name"] = *name; 932 } 933 934 if (sensors != nullptr) 935 { 936 json["MetricProperties"] = getMetricProperties(*sensors); 937 } 938 939 json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; 940 json["@odata.id"] = 941 boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id); 942 json["Id"] = id; 943 944 return true; 945 } 946 947 inline void handleTriggerCollectionPost( 948 App& app, const crow::Request& req, 949 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 950 { 951 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 952 { 953 return; 954 } 955 956 telemetry::Context ctx; 957 if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx)) 958 { 959 return; 960 } 961 962 crow::connections::systemBus->async_method_call( 963 [asyncResp, id = ctx.id](const boost::system::error_code& ec, 964 const std::string& dbusPath) { 965 afterCreateTrigger(ec, dbusPath, asyncResp, id); 966 }, 967 service, "/xyz/openbmc_project/Telemetry/Triggers", 968 "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", 969 "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors, 970 ctx.reports, ctx.thresholds); 971 } 972 973 } // namespace telemetry 974 975 inline void requestRoutesTriggerCollection(App& app) 976 { 977 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 978 .privileges(redfish::privileges::getTriggersCollection) 979 .methods(boost::beast::http::verb::get)( 980 [&app](const crow::Request& req, 981 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 982 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 983 { 984 return; 985 } 986 asyncResp->res.jsonValue["@odata.type"] = 987 "#TriggersCollection.TriggersCollection"; 988 asyncResp->res.jsonValue["@odata.id"] = 989 "/redfish/v1/TelemetryService/Triggers"; 990 asyncResp->res.jsonValue["Name"] = "Triggers Collection"; 991 constexpr std::array<std::string_view, 1> interfaces{ 992 telemetry::triggerInterface}; 993 collection_util::getCollectionMembers( 994 asyncResp, 995 boost::urls::url("/redfish/v1/TelemetryService/Triggers"), 996 interfaces, 997 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); 998 }); 999 1000 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 1001 .privileges(redfish::privileges::postTriggersCollection) 1002 .methods(boost::beast::http::verb::post)(std::bind_front( 1003 telemetry::handleTriggerCollectionPost, std::ref(app))); 1004 } 1005 1006 inline void requestRoutesTrigger(App& app) 1007 { 1008 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 1009 .privileges(redfish::privileges::getTriggers) 1010 .methods(boost::beast::http::verb::get)( 1011 [&app](const crow::Request& req, 1012 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1013 const std::string& id) { 1014 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1015 { 1016 return; 1017 } 1018 sdbusplus::asio::getAllProperties( 1019 *crow::connections::systemBus, telemetry::service, 1020 telemetry::getDbusTriggerPath(id), 1021 telemetry::triggerInterface, 1022 [asyncResp, 1023 id](const boost::system::error_code& ec, 1024 const std::vector<std::pair< 1025 std::string, telemetry::TriggerGetParamsVariant>>& 1026 ret) { 1027 if (ec.value() == EBADR || 1028 ec == boost::system::errc::host_unreachable) 1029 { 1030 messages::resourceNotFound(asyncResp->res, 1031 "Triggers", id); 1032 return; 1033 } 1034 if (ec) 1035 { 1036 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1037 messages::internalError(asyncResp->res); 1038 return; 1039 } 1040 1041 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, 1042 id, ret)) 1043 { 1044 messages::internalError(asyncResp->res); 1045 } 1046 }); 1047 }); 1048 1049 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 1050 .privileges(redfish::privileges::deleteTriggers) 1051 .methods(boost::beast::http::verb::delete_)( 1052 [&app](const crow::Request& req, 1053 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1054 const std::string& id) { 1055 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1056 { 1057 return; 1058 } 1059 const std::string triggerPath = 1060 telemetry::getDbusTriggerPath(id); 1061 1062 crow::connections::systemBus->async_method_call( 1063 [asyncResp, id](const boost::system::error_code& ec) { 1064 if (ec.value() == EBADR) 1065 { 1066 messages::resourceNotFound(asyncResp->res, 1067 "Triggers", id); 1068 return; 1069 } 1070 1071 if (ec) 1072 { 1073 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec); 1074 messages::internalError(asyncResp->res); 1075 return; 1076 } 1077 1078 asyncResp->res.result( 1079 boost::beast::http::status::no_content); 1080 }, 1081 telemetry::service, triggerPath, 1082 "xyz.openbmc_project.Object.Delete", "Delete"); 1083 }); 1084 } 1085 1086 } // namespace redfish 1087