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