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