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