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::urls::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::urls::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 if (!(ctx.metricType = getMetricType(*metricType))) 610 { 611 messages::propertyValueIncorrect(res, "MetricType", *metricType); 612 return false; 613 } 614 } 615 616 if (discreteTriggerCondition) 617 { 618 if (!(ctx.discreteCondition = 619 getDiscreteCondition(*discreteTriggerCondition))) 620 { 621 messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", 622 *discreteTriggerCondition); 623 return false; 624 } 625 } 626 627 if (triggerActions) 628 { 629 ctx.actions.reserve(triggerActions->size()); 630 for (const std::string& action : *triggerActions) 631 { 632 std::string dbusAction = toDbusTriggerAction(action); 633 634 if (dbusAction.empty()) 635 { 636 messages::propertyValueNotInList(res, action, "TriggerActions"); 637 return false; 638 } 639 640 ctx.actions.emplace_back(dbusAction); 641 } 642 } 643 if (!parseMetricProperties(res, ctx)) 644 { 645 return false; 646 } 647 648 if (!parseTriggerThresholds(res, discreteTriggers, numericThresholds, ctx)) 649 { 650 return false; 651 } 652 653 if (links) 654 { 655 if (!parseLinks(res, *links, ctx)) 656 { 657 return false; 658 } 659 } 660 return true; 661 } 662 663 inline void afterCreateTrigger( 664 const boost::system::error_code& ec, const std::string& dbusPath, 665 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 666 { 667 if (ec == boost::system::errc::file_exists) 668 { 669 messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id); 670 return; 671 } 672 if (ec == boost::system::errc::too_many_files_open) 673 { 674 messages::createLimitReachedForResource(asyncResp->res); 675 return; 676 } 677 if (ec) 678 { 679 messages::internalError(asyncResp->res); 680 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 681 return; 682 } 683 684 const std::optional<std::string>& triggerId = 685 getTriggerIdFromDbusPath(dbusPath); 686 if (!triggerId) 687 { 688 messages::internalError(asyncResp->res); 689 BMCWEB_LOG_ERROR << "Unknown data returned by " 690 "AddTrigger DBus method"; 691 return; 692 } 693 694 messages::created(asyncResp->res); 695 boost::urls::url locationUrl = boost::urls::format( 696 "/redfish/v1/TelemetryService/Triggers/{}", *triggerId); 697 asyncResp->res.addHeader("Location", locationUrl.buffer()); 698 } 699 700 inline std::optional<nlohmann::json::array_t> 701 getTriggerActions(const std::vector<std::string>& dbusActions) 702 { 703 nlohmann::json::array_t triggerActions; 704 for (const std::string& dbusAction : dbusActions) 705 { 706 triggers::TriggerActionEnum redfishAction = 707 toRedfishTriggerAction(dbusAction); 708 709 if (redfishAction == triggers::TriggerActionEnum::Invalid) 710 { 711 return std::nullopt; 712 } 713 714 triggerActions.emplace_back(redfishAction); 715 } 716 717 return triggerActions; 718 } 719 720 inline std::optional<nlohmann::json::array_t> 721 getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams) 722 { 723 nlohmann::json::array_t triggers; 724 const std::vector<DiscreteThresholdParams>* discreteParams = 725 std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams); 726 727 if (discreteParams == nullptr) 728 { 729 return std::nullopt; 730 } 731 732 for (const auto& [name, severity, dwellTime, value] : *discreteParams) 733 { 734 std::optional<std::string> duration = 735 time_utils::toDurationStringFromUint(dwellTime); 736 737 if (!duration) 738 { 739 return std::nullopt; 740 } 741 nlohmann::json::object_t trigger; 742 trigger["Name"] = name; 743 trigger["Severity"] = toRedfishSeverity(severity); 744 trigger["DwellTime"] = *duration; 745 trigger["Value"] = value; 746 triggers.emplace_back(std::move(trigger)); 747 } 748 749 return triggers; 750 } 751 752 inline std::optional<nlohmann::json> 753 getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams) 754 { 755 nlohmann::json::object_t thresholds; 756 const std::vector<NumericThresholdParams>* numericParams = 757 std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams); 758 759 if (numericParams == nullptr) 760 { 761 return std::nullopt; 762 } 763 764 for (const auto& [type, dwellTime, activation, reading] : *numericParams) 765 { 766 std::optional<std::string> duration = 767 time_utils::toDurationStringFromUint(dwellTime); 768 769 if (!duration) 770 { 771 return std::nullopt; 772 } 773 nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)]; 774 threshold["Reading"] = reading; 775 threshold["Activation"] = toRedfishActivation(activation); 776 threshold["DwellTime"] = *duration; 777 } 778 779 return thresholds; 780 } 781 782 inline std::optional<nlohmann::json> getMetricReportDefinitions( 783 const std::vector<sdbusplus::message::object_path>& reportPaths) 784 { 785 nlohmann::json reports = nlohmann::json::array(); 786 787 for (const sdbusplus::message::object_path& path : reportPaths) 788 { 789 std::string reportId = path.filename(); 790 if (reportId.empty()) 791 { 792 { 793 BMCWEB_LOG_ERROR << "Property Reports contains invalid value: " 794 << path.str; 795 return std::nullopt; 796 } 797 } 798 799 nlohmann::json::object_t report; 800 report["@odata.id"] = boost::urls::format( 801 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 802 reportId); 803 reports.emplace_back(std::move(report)); 804 } 805 806 return {std::move(reports)}; 807 } 808 809 inline std::vector<std::string> 810 getMetricProperties(const TriggerSensorsParams& sensors) 811 { 812 std::vector<std::string> metricProperties; 813 metricProperties.reserve(sensors.size()); 814 for (const auto& [_, metadata] : sensors) 815 { 816 metricProperties.emplace_back(metadata); 817 } 818 819 return metricProperties; 820 } 821 822 inline bool fillTrigger( 823 nlohmann::json& json, const std::string& id, 824 const std::vector<std::pair<std::string, TriggerGetParamsVariant>>& 825 properties) 826 { 827 const std::string* name = nullptr; 828 const bool* discrete = nullptr; 829 const TriggerSensorsParams* sensors = nullptr; 830 const std::vector<sdbusplus::message::object_path>* reports = nullptr; 831 const std::vector<std::string>* triggerActions = nullptr; 832 const TriggerThresholdParamsExt* thresholds = nullptr; 833 834 const bool success = sdbusplus::unpackPropertiesNoThrow( 835 dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete", 836 discrete, "Sensors", sensors, "Reports", reports, "TriggerActions", 837 triggerActions, "Thresholds", thresholds); 838 839 if (!success) 840 { 841 return false; 842 } 843 844 if (triggerActions != nullptr) 845 { 846 std::optional<nlohmann::json::array_t> redfishTriggerActions = 847 getTriggerActions(*triggerActions); 848 if (!redfishTriggerActions) 849 { 850 BMCWEB_LOG_ERROR 851 << "Property TriggerActions is invalid in Trigger: " << id; 852 return false; 853 } 854 json["TriggerActions"] = *redfishTriggerActions; 855 } 856 857 if (reports != nullptr) 858 { 859 std::optional<nlohmann::json> linkedReports = 860 getMetricReportDefinitions(*reports); 861 if (!linkedReports) 862 { 863 BMCWEB_LOG_ERROR << "Property Reports is invalid in Trigger: " 864 << 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 880 << "Property Thresholds is invalid for discrete " 881 "triggers in Trigger: " 882 << id; 883 return false; 884 } 885 886 json["DiscreteTriggers"] = *discreteTriggers; 887 json["DiscreteTriggerCondition"] = 888 discreteTriggers->empty() ? "Changed" : "Specified"; 889 json["MetricType"] = "Discrete"; 890 } 891 else 892 { 893 std::optional<nlohmann::json> numericThresholds = 894 getNumericThresholds(*thresholds); 895 896 if (!numericThresholds) 897 { 898 BMCWEB_LOG_ERROR 899 << "Property Thresholds is invalid for numeric " 900 "thresholds in Trigger: " 901 << id; 902 return false; 903 } 904 905 json["NumericThresholds"] = *numericThresholds; 906 json["MetricType"] = "Numeric"; 907 } 908 } 909 910 if (name != nullptr) 911 { 912 json["Name"] = *name; 913 } 914 915 if (sensors != nullptr) 916 { 917 json["MetricProperties"] = getMetricProperties(*sensors); 918 } 919 920 json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; 921 json["@odata.id"] = 922 boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id); 923 json["Id"] = id; 924 925 return true; 926 } 927 928 inline void handleTriggerCollectionPost( 929 App& app, const crow::Request& req, 930 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 931 { 932 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 933 { 934 return; 935 } 936 937 telemetry::Context ctx; 938 if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx)) 939 { 940 return; 941 } 942 943 crow::connections::systemBus->async_method_call( 944 [asyncResp, id = ctx.id](const boost::system::error_code& ec, 945 const std::string& dbusPath) { 946 afterCreateTrigger(ec, dbusPath, asyncResp, id); 947 }, 948 service, "/xyz/openbmc_project/Telemetry/Triggers", 949 "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", 950 "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors, 951 ctx.reports, ctx.thresholds); 952 } 953 954 } // namespace telemetry 955 956 inline void requestRoutesTriggerCollection(App& app) 957 { 958 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 959 .privileges(redfish::privileges::getTriggersCollection) 960 .methods(boost::beast::http::verb::get)( 961 [&app](const crow::Request& req, 962 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 963 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 964 { 965 return; 966 } 967 asyncResp->res.jsonValue["@odata.type"] = 968 "#TriggersCollection.TriggersCollection"; 969 asyncResp->res.jsonValue["@odata.id"] = 970 "/redfish/v1/TelemetryService/Triggers"; 971 asyncResp->res.jsonValue["Name"] = "Triggers Collection"; 972 constexpr std::array<std::string_view, 1> interfaces{ 973 telemetry::triggerInterface}; 974 collection_util::getCollectionMembers( 975 asyncResp, 976 boost::urls::url("/redfish/v1/TelemetryService/Triggers"), 977 interfaces, 978 "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); 979 }); 980 981 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") 982 .privileges(redfish::privileges::postTriggersCollection) 983 .methods(boost::beast::http::verb::post)(std::bind_front( 984 telemetry::handleTriggerCollectionPost, std::ref(app))); 985 } 986 987 inline void requestRoutesTrigger(App& app) 988 { 989 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 990 .privileges(redfish::privileges::getTriggers) 991 .methods(boost::beast::http::verb::get)( 992 [&app](const crow::Request& req, 993 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 994 const std::string& id) { 995 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 996 { 997 return; 998 } 999 sdbusplus::asio::getAllProperties( 1000 *crow::connections::systemBus, telemetry::service, 1001 telemetry::getDbusTriggerPath(id), telemetry::triggerInterface, 1002 [asyncResp, 1003 id](const boost::system::error_code& ec, 1004 const std::vector<std::pair< 1005 std::string, telemetry::TriggerGetParamsVariant>>& ret) { 1006 if (ec.value() == EBADR || 1007 ec == boost::system::errc::host_unreachable) 1008 { 1009 messages::resourceNotFound(asyncResp->res, "Triggers", id); 1010 return; 1011 } 1012 if (ec) 1013 { 1014 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 1015 messages::internalError(asyncResp->res); 1016 return; 1017 } 1018 1019 if (!telemetry::fillTrigger(asyncResp->res.jsonValue, id, ret)) 1020 { 1021 messages::internalError(asyncResp->res); 1022 } 1023 }); 1024 }); 1025 1026 BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/") 1027 .privileges(redfish::privileges::deleteTriggers) 1028 .methods(boost::beast::http::verb::delete_)( 1029 [&app](const crow::Request& req, 1030 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1031 const std::string& id) { 1032 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1033 { 1034 return; 1035 } 1036 const std::string triggerPath = telemetry::getDbusTriggerPath(id); 1037 1038 crow::connections::systemBus->async_method_call( 1039 [asyncResp, id](const boost::system::error_code& ec) { 1040 if (ec.value() == EBADR) 1041 { 1042 messages::resourceNotFound(asyncResp->res, "Triggers", id); 1043 return; 1044 } 1045 1046 if (ec) 1047 { 1048 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; 1049 messages::internalError(asyncResp->res); 1050 return; 1051 } 1052 1053 asyncResp->res.result(boost::beast::http::status::no_content); 1054 }, 1055 telemetry::service, triggerPath, 1056 "xyz.openbmc_project.Object.Delete", "Delete"); 1057 }); 1058 } 1059 1060 } // namespace redfish 1061