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