1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4 #pragma once 5 6 #include "bmcweb_config.h" 7 8 #include "app.hpp" 9 #include "async_resp.hpp" 10 #include "dbus_singleton.hpp" 11 #include "dbus_utility.hpp" 12 #include "error_messages.hpp" 13 #include "generated/enums/redundancy.hpp" 14 #include "generated/enums/resource.hpp" 15 #include "http_request.hpp" 16 #include "logging.hpp" 17 #include "query.hpp" 18 #include "registries/privilege_registry.hpp" 19 #include "str_utility.hpp" 20 #include "utils/dbus_utils.hpp" 21 #include "utils/json_utils.hpp" 22 #include "utils/query_param.hpp" 23 #include "utils/sensor_utils.hpp" 24 25 #include <asm-generic/errno.h> 26 27 #include <boost/beast/http/status.hpp> 28 #include <boost/beast/http/verb.hpp> 29 #include <boost/system/error_code.hpp> 30 #include <boost/url/format.hpp> 31 #include <boost/url/url.hpp> 32 #include <nlohmann/json.hpp> 33 #include <sdbusplus/message/native_types.hpp> 34 #include <sdbusplus/unpack_properties.hpp> 35 36 #include <algorithm> 37 #include <array> 38 #include <cmath> 39 #include <cstddef> 40 #include <cstdint> 41 #include <functional> 42 #include <iterator> 43 #include <map> 44 #include <memory> 45 #include <optional> 46 #include <ranges> 47 #include <set> 48 #include <span> 49 #include <string> 50 #include <string_view> 51 #include <unordered_map> 52 #include <utility> 53 #include <variant> 54 #include <vector> 55 56 namespace redfish 57 { 58 59 namespace sensors 60 { 61 62 // clang-format off 63 namespace dbus 64 { 65 constexpr auto powerPaths = std::to_array<std::string_view>({ 66 "/xyz/openbmc_project/sensors/voltage", 67 "/xyz/openbmc_project/sensors/power" 68 }); 69 70 constexpr auto getSensorPaths(){ 71 if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){ 72 return std::to_array<std::string_view>({ 73 "/xyz/openbmc_project/sensors/power", 74 "/xyz/openbmc_project/sensors/current", 75 "/xyz/openbmc_project/sensors/airflow", 76 "/xyz/openbmc_project/sensors/humidity", 77 "/xyz/openbmc_project/sensors/voltage", 78 "/xyz/openbmc_project/sensors/fan_tach", 79 "/xyz/openbmc_project/sensors/temperature", 80 "/xyz/openbmc_project/sensors/fan_pwm", 81 "/xyz/openbmc_project/sensors/altitude", 82 "/xyz/openbmc_project/sensors/energy", 83 "/xyz/openbmc_project/sensors/liquidflow", 84 "/xyz/openbmc_project/sensors/pressure", 85 "/xyz/openbmc_project/sensors/utilization"}); 86 } else { 87 return std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power", 88 "/xyz/openbmc_project/sensors/current", 89 "/xyz/openbmc_project/sensors/airflow", 90 "/xyz/openbmc_project/sensors/humidity", 91 "/xyz/openbmc_project/sensors/utilization"}); 92 } 93 } 94 95 constexpr auto sensorPaths = getSensorPaths(); 96 97 constexpr auto thermalPaths = std::to_array<std::string_view>({ 98 "/xyz/openbmc_project/sensors/fan_tach", 99 "/xyz/openbmc_project/sensors/temperature", 100 "/xyz/openbmc_project/sensors/fan_pwm" 101 }); 102 103 } // namespace dbus 104 // clang-format on 105 106 constexpr std::string_view powerNodeStr = sensor_utils::chassisSubNodeToString( 107 sensor_utils::ChassisSubNode::powerNode); 108 constexpr std::string_view sensorsNodeStr = 109 sensor_utils::chassisSubNodeToString( 110 sensor_utils::ChassisSubNode::sensorsNode); 111 constexpr std::string_view thermalNodeStr = 112 sensor_utils::chassisSubNodeToString( 113 sensor_utils::ChassisSubNode::thermalNode); 114 115 using sensorPair = 116 std::pair<std::string_view, std::span<const std::string_view>>; 117 static constexpr std::array<sensorPair, 3> paths = { 118 {{sensors::powerNodeStr, dbus::powerPaths}, 119 {sensors::sensorsNodeStr, dbus::sensorPaths}, 120 {sensors::thermalNodeStr, dbus::thermalPaths}}}; 121 122 } // namespace sensors 123 124 /** 125 * SensorsAsyncResp 126 * Gathers data needed for response processing after async calls are done 127 */ 128 class SensorsAsyncResp 129 { 130 public: 131 using DataCompleteCb = std::function<void( 132 const boost::beast::http::status status, 133 const std::map<std::string, std::string>& uriToDbus)>; 134 135 struct SensorData 136 { 137 std::string name; 138 std::string uri; 139 std::string dbusPath; 140 }; 141 142 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 143 const std::string& chassisIdIn, 144 std::span<const std::string_view> typesIn, 145 std::string_view subNode) : 146 asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn), 147 chassisSubNode(subNode), efficientExpand(false) 148 {} 149 150 // Store extra data about sensor mapping and return it in callback 151 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 152 const std::string& chassisIdIn, 153 std::span<const std::string_view> typesIn, 154 std::string_view subNode, 155 DataCompleteCb&& creationComplete) : 156 asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn), 157 chassisSubNode(subNode), efficientExpand(false), 158 metadata{std::vector<SensorData>()}, 159 dataComplete{std::move(creationComplete)} 160 {} 161 162 // sensor collections expand 163 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 164 const std::string& chassisIdIn, 165 std::span<const std::string_view> typesIn, 166 const std::string_view& subNode, bool efficientExpandIn) : 167 asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn), 168 chassisSubNode(subNode), efficientExpand(efficientExpandIn) 169 {} 170 171 ~SensorsAsyncResp() 172 { 173 if (asyncResp->res.result() == 174 boost::beast::http::status::internal_server_error) 175 { 176 // Reset the json object to clear out any data that made it in 177 // before the error happened todo(ed) handle error condition with 178 // proper code 179 asyncResp->res.jsonValue = nlohmann::json::object(); 180 } 181 182 if (dataComplete && metadata) 183 { 184 std::map<std::string, std::string> map; 185 if (asyncResp->res.result() == boost::beast::http::status::ok) 186 { 187 for (auto& sensor : *metadata) 188 { 189 map.emplace(sensor.uri, sensor.dbusPath); 190 } 191 } 192 dataComplete(asyncResp->res.result(), map); 193 } 194 } 195 196 SensorsAsyncResp(const SensorsAsyncResp&) = delete; 197 SensorsAsyncResp(SensorsAsyncResp&&) = delete; 198 SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete; 199 SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete; 200 201 void addMetadata(const nlohmann::json& sensorObject, 202 const std::string& dbusPath) 203 { 204 if (metadata) 205 { 206 metadata->emplace_back(SensorData{ 207 sensorObject["Name"], sensorObject["@odata.id"], dbusPath}); 208 } 209 } 210 211 void updateUri(const std::string& name, const std::string& uri) 212 { 213 if (metadata) 214 { 215 for (auto& sensor : *metadata) 216 { 217 if (sensor.name == name) 218 { 219 sensor.uri = uri; 220 } 221 } 222 } 223 } 224 225 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 226 const std::string chassisId; 227 const std::span<const std::string_view> types; 228 const std::string chassisSubNode; 229 const bool efficientExpand; 230 231 private: 232 std::optional<std::vector<SensorData>> metadata; 233 DataCompleteCb dataComplete; 234 }; 235 236 using InventoryItem = sensor_utils::InventoryItem; 237 238 /** 239 * @brief Get objects with connection necessary for sensors 240 * @param SensorsAsyncResp Pointer to object holding response data 241 * @param sensorNames Sensors retrieved from chassis 242 * @param callback Callback for processing gathered connections 243 */ 244 template <typename Callback> 245 void getObjectsWithConnection( 246 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 247 const std::shared_ptr<std::set<std::string>>& sensorNames, 248 Callback&& callback) 249 { 250 BMCWEB_LOG_DEBUG("getObjectsWithConnection enter"); 251 const std::string path = "/xyz/openbmc_project/sensors"; 252 constexpr std::array<std::string_view, 1> interfaces = { 253 "xyz.openbmc_project.Sensor.Value"}; 254 255 // Make call to ObjectMapper to find all sensors objects 256 dbus::utility::getSubTree( 257 path, 2, interfaces, 258 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 259 sensorNames](const boost::system::error_code& ec, 260 const dbus::utility::MapperGetSubTreeResponse& subtree) { 261 // Response handler for parsing objects subtree 262 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter"); 263 if (ec) 264 { 265 messages::internalError(sensorsAsyncResp->asyncResp->res); 266 BMCWEB_LOG_ERROR( 267 "getObjectsWithConnection resp_handler: Dbus error {}", ec); 268 return; 269 } 270 271 BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size()); 272 273 // Make unique list of connections only for requested sensor types 274 // and found in the chassis 275 std::set<std::string> connections; 276 std::set<std::pair<std::string, std::string>> objectsWithConnection; 277 278 BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size()); 279 for (const std::string& tsensor : *sensorNames) 280 { 281 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor); 282 } 283 284 for (const std::pair<std::string, 285 std::vector<std::pair< 286 std::string, std::vector<std::string>>>>& 287 object : subtree) 288 { 289 if (sensorNames->find(object.first) != sensorNames->end()) 290 { 291 for (const std::pair<std::string, std::vector<std::string>>& 292 objData : object.second) 293 { 294 BMCWEB_LOG_DEBUG("Adding connection: {}", 295 objData.first); 296 connections.insert(objData.first); 297 objectsWithConnection.insert( 298 std::make_pair(object.first, objData.first)); 299 } 300 } 301 } 302 BMCWEB_LOG_DEBUG("Found {} connections", connections.size()); 303 callback(std::move(connections), std::move(objectsWithConnection)); 304 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit"); 305 }); 306 BMCWEB_LOG_DEBUG("getObjectsWithConnection exit"); 307 } 308 309 /** 310 * @brief Create connections necessary for sensors 311 * @param SensorsAsyncResp Pointer to object holding response data 312 * @param sensorNames Sensors retrieved from chassis 313 * @param callback Callback for processing gathered connections 314 */ 315 template <typename Callback> 316 void getConnections(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 317 const std::shared_ptr<std::set<std::string>>& sensorNames, 318 Callback&& callback) 319 { 320 auto objectsWithConnectionCb = 321 [callback = std::forward<Callback>(callback)]( 322 const std::set<std::string>& connections, 323 const std::set<std::pair<std::string, std::string>>& 324 /*objectsWithConnection*/) { callback(connections); }; 325 getObjectsWithConnection(sensorsAsyncResp, sensorNames, 326 std::move(objectsWithConnectionCb)); 327 } 328 329 /** 330 * @brief Shrinks the list of sensors for processing 331 * @param SensorsAysncResp The class holding the Redfish response 332 * @param allSensors A list of all the sensors associated to the 333 * chassis element (i.e. baseboard, front panel, etc...) 334 * @param activeSensors A list that is a reduction of the incoming 335 * allSensors list. Eliminate Thermal sensors when a Power request is 336 * made, and eliminate Power sensors when a Thermal request is made. 337 */ 338 inline void reduceSensorList( 339 crow::Response& res, std::string_view chassisSubNode, 340 std::span<const std::string_view> sensorTypes, 341 const std::vector<std::string>* allSensors, 342 const std::shared_ptr<std::set<std::string>>& activeSensors) 343 { 344 if ((allSensors == nullptr) || (activeSensors == nullptr)) 345 { 346 messages::resourceNotFound(res, chassisSubNode, 347 chassisSubNode == sensors::thermalNodeStr 348 ? "Temperatures" 349 : "Voltages"); 350 351 return; 352 } 353 if (allSensors->empty()) 354 { 355 // Nothing to do, the activeSensors object is also empty 356 return; 357 } 358 359 for (std::string_view type : sensorTypes) 360 { 361 for (const std::string& sensor : *allSensors) 362 { 363 if (sensor.starts_with(type)) 364 { 365 activeSensors->emplace(sensor); 366 } 367 } 368 } 369 } 370 371 /* 372 *Populates the top level collection for a given subnode. Populates 373 *SensorCollection, Power, or Thermal schemas. 374 * 375 * */ 376 inline void populateChassisNode(nlohmann::json& jsonValue, 377 std::string_view chassisSubNode) 378 { 379 if (chassisSubNode == sensors::powerNodeStr) 380 { 381 jsonValue["@odata.type"] = "#Power.v1_5_2.Power"; 382 } 383 else if (chassisSubNode == sensors::thermalNodeStr) 384 { 385 jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal"; 386 jsonValue["Fans"] = nlohmann::json::array(); 387 jsonValue["Temperatures"] = nlohmann::json::array(); 388 } 389 else if (chassisSubNode == sensors::sensorsNodeStr) 390 { 391 jsonValue["@odata.type"] = "#SensorCollection.SensorCollection"; 392 jsonValue["Description"] = "Collection of Sensors for this Chassis"; 393 jsonValue["Members"] = nlohmann::json::array(); 394 jsonValue["Members@odata.count"] = 0; 395 } 396 397 if (chassisSubNode != sensors::sensorsNodeStr) 398 { 399 jsonValue["Id"] = chassisSubNode; 400 } 401 jsonValue["Name"] = chassisSubNode; 402 } 403 404 /** 405 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 406 * @param SensorsAsyncResp Pointer to object holding response data 407 * @param callback Callback for next step in gathered sensor processing 408 */ 409 template <typename Callback> 410 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 411 std::string_view chassisId, std::string_view chassisSubNode, 412 std::span<const std::string_view> sensorTypes, 413 Callback&& callback) 414 { 415 BMCWEB_LOG_DEBUG("getChassis enter"); 416 constexpr std::array<std::string_view, 2> interfaces = { 417 "xyz.openbmc_project.Inventory.Item.Board", 418 "xyz.openbmc_project.Inventory.Item.Chassis"}; 419 420 // Get the Chassis Collection 421 dbus::utility::getSubTreePaths( 422 "/xyz/openbmc_project/inventory", 0, interfaces, 423 [callback = std::forward<Callback>(callback), asyncResp, 424 chassisIdStr{std::string(chassisId)}, 425 chassisSubNode{std::string(chassisSubNode)}, 426 sensorTypes](const boost::system::error_code& ec, 427 const dbus::utility::MapperGetSubTreePathsResponse& 428 chassisPaths) mutable { 429 BMCWEB_LOG_DEBUG("getChassis respHandler enter"); 430 if (ec) 431 { 432 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec); 433 messages::internalError(asyncResp->res); 434 return; 435 } 436 const std::string* chassisPath = nullptr; 437 for (const std::string& chassis : chassisPaths) 438 { 439 sdbusplus::message::object_path path(chassis); 440 std::string chassisName = path.filename(); 441 if (chassisName.empty()) 442 { 443 BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis); 444 continue; 445 } 446 if (chassisName == chassisIdStr) 447 { 448 chassisPath = &chassis; 449 break; 450 } 451 } 452 if (chassisPath == nullptr) 453 { 454 messages::resourceNotFound(asyncResp->res, "Chassis", 455 chassisIdStr); 456 return; 457 } 458 populateChassisNode(asyncResp->res.jsonValue, chassisSubNode); 459 460 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 461 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode); 462 463 // Get the list of all sensors for this Chassis element 464 std::string sensorPath = *chassisPath + "/all_sensors"; 465 dbus::utility::getAssociationEndPoints( 466 sensorPath, [asyncResp, chassisSubNode, sensorTypes, 467 callback = std::forward<Callback>(callback)]( 468 const boost::system::error_code& ec2, 469 const dbus::utility::MapperEndPoints& 470 nodeSensorList) mutable { 471 if (ec2) 472 { 473 if (ec2.value() != EBADR) 474 { 475 messages::internalError(asyncResp->res); 476 return; 477 } 478 } 479 const std::shared_ptr<std::set<std::string>> 480 culledSensorList = 481 std::make_shared<std::set<std::string>>(); 482 reduceSensorList(asyncResp->res, chassisSubNode, 483 sensorTypes, &nodeSensorList, 484 culledSensorList); 485 BMCWEB_LOG_DEBUG("Finishing with {}", 486 culledSensorList->size()); 487 callback(culledSensorList); 488 }); 489 }); 490 BMCWEB_LOG_DEBUG("getChassis exit"); 491 } 492 493 /** 494 * @brief Builds a json sensor representation of a sensor. 495 * @param sensorName The name of the sensor to be built 496 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 497 * build 498 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 499 * @param interfacesDict A dictionary of the interfaces and properties of said 500 * interfaces to be built from 501 * @param sensorJson The json object to fill 502 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 503 * be nullptr if no associated inventory item was found. 504 */ 505 inline void objectInterfacesToJson( 506 const std::string& sensorName, const std::string& sensorType, 507 const sensor_utils::ChassisSubNode chassisSubNode, 508 const dbus::utility::DBusInterfacesMap& interfacesDict, 509 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 510 { 511 for (const auto& [interface, valuesDict] : interfacesDict) 512 { 513 sensor_utils::objectPropertiesToJson( 514 sensorName, sensorType, chassisSubNode, valuesDict, sensorJson, 515 inventoryItem); 516 } 517 BMCWEB_LOG_DEBUG("Added sensor {}", sensorName); 518 } 519 520 inline void populateFanRedundancy( 521 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 522 { 523 constexpr std::array<std::string_view, 1> interfaces = { 524 "xyz.openbmc_project.Control.FanRedundancy"}; 525 dbus::utility::getSubTree( 526 "/xyz/openbmc_project/control", 2, interfaces, 527 [sensorsAsyncResp]( 528 const boost::system::error_code& ec, 529 const dbus::utility::MapperGetSubTreeResponse& resp) { 530 if (ec) 531 { 532 return; // don't have to have this interface 533 } 534 for (const std::pair<std::string, dbus::utility::MapperServiceMap>& 535 pathPair : resp) 536 { 537 const std::string& path = pathPair.first; 538 const dbus::utility::MapperServiceMap& objDict = 539 pathPair.second; 540 if (objDict.empty()) 541 { 542 continue; // this should be impossible 543 } 544 545 const std::string& owner = objDict.begin()->first; 546 dbus::utility::getAssociationEndPoints( 547 path + "/chassis", 548 [path, owner, sensorsAsyncResp]( 549 const boost::system::error_code& ec2, 550 const dbus::utility::MapperEndPoints& endpoints) { 551 if (ec2) 552 { 553 return; // if they don't have an association we 554 // can't tell what chassis is 555 } 556 auto found = std::ranges::find_if( 557 endpoints, 558 [sensorsAsyncResp](const std::string& entry) { 559 return entry.find( 560 sensorsAsyncResp->chassisId) != 561 std::string::npos; 562 }); 563 564 if (found == endpoints.end()) 565 { 566 return; 567 } 568 dbus::utility::getAllProperties( 569 *crow::connections::systemBus, owner, path, 570 "xyz.openbmc_project.Control.FanRedundancy", 571 [path, sensorsAsyncResp]( 572 const boost::system::error_code& ec3, 573 const dbus::utility::DBusPropertiesMap& ret) { 574 if (ec3) 575 { 576 return; // don't have to have this 577 // interface 578 } 579 580 const uint8_t* allowedFailures = nullptr; 581 const std::vector<std::string>* collection = 582 nullptr; 583 const std::string* status = nullptr; 584 585 const bool success = 586 sdbusplus::unpackPropertiesNoThrow( 587 dbus_utils::UnpackErrorPrinter(), ret, 588 "AllowedFailures", allowedFailures, 589 "Collection", collection, "Status", 590 status); 591 592 if (!success) 593 { 594 messages::internalError( 595 sensorsAsyncResp->asyncResp->res); 596 return; 597 } 598 599 if (allowedFailures == nullptr || 600 collection == nullptr || status == nullptr) 601 { 602 BMCWEB_LOG_ERROR( 603 "Invalid redundancy interface"); 604 messages::internalError( 605 sensorsAsyncResp->asyncResp->res); 606 return; 607 } 608 609 sdbusplus::message::object_path objectPath( 610 path); 611 std::string name = objectPath.filename(); 612 if (name.empty()) 613 { 614 // this should be impossible 615 messages::internalError( 616 sensorsAsyncResp->asyncResp->res); 617 return; 618 } 619 std::ranges::replace(name, '_', ' '); 620 621 std::string health; 622 623 if (status->ends_with("Full")) 624 { 625 health = "OK"; 626 } 627 else if (status->ends_with("Degraded")) 628 { 629 health = "Warning"; 630 } 631 else 632 { 633 health = "Critical"; 634 } 635 nlohmann::json::array_t redfishCollection; 636 const auto& fanRedfish = 637 sensorsAsyncResp->asyncResp->res 638 .jsonValue["Fans"]; 639 for (const std::string& item : *collection) 640 { 641 sdbusplus::message::object_path itemPath( 642 item); 643 std::string itemName = itemPath.filename(); 644 if (itemName.empty()) 645 { 646 continue; 647 } 648 /* 649 todo(ed): merge patch that fixes the names 650 std::replace(itemName.begin(), 651 itemName.end(), '_', ' ');*/ 652 auto schemaItem = std::ranges::find_if( 653 fanRedfish, 654 [itemName](const nlohmann::json& fan) { 655 return fan["Name"] == itemName; 656 }); 657 if (schemaItem != fanRedfish.end()) 658 { 659 nlohmann::json::object_t collectionId; 660 collectionId["@odata.id"] = 661 (*schemaItem)["@odata.id"]; 662 redfishCollection.emplace_back( 663 std::move(collectionId)); 664 } 665 else 666 { 667 BMCWEB_LOG_ERROR( 668 "failed to find fan in schema"); 669 messages::internalError( 670 sensorsAsyncResp->asyncResp->res); 671 return; 672 } 673 } 674 675 size_t minNumNeeded = 676 collection->empty() 677 ? 0 678 : collection->size() - *allowedFailures; 679 nlohmann::json& jResp = 680 sensorsAsyncResp->asyncResp->res 681 .jsonValue["Redundancy"]; 682 683 nlohmann::json::object_t redundancy; 684 boost::urls::url url = boost::urls::format( 685 "/redfish/v1/Chassis/{}/{}", 686 sensorsAsyncResp->chassisId, 687 sensorsAsyncResp->chassisSubNode); 688 url.set_fragment( 689 ("/Redundancy"_json_pointer / jResp.size()) 690 .to_string()); 691 redundancy["@odata.id"] = std::move(url); 692 redundancy["@odata.type"] = 693 "#Redundancy.v1_3_2.Redundancy"; 694 redundancy["MinNumNeeded"] = minNumNeeded; 695 redundancy["Mode"] = 696 redundancy::RedundancyType::NPlusM; 697 redundancy["Name"] = name; 698 redundancy["RedundancySet"] = redfishCollection; 699 redundancy["Status"]["Health"] = health; 700 redundancy["Status"]["State"] = 701 resource::State::Enabled; 702 703 jResp.emplace_back(std::move(redundancy)); 704 }); 705 }); 706 } 707 }); 708 } 709 710 inline void sortJSONResponse( 711 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 712 { 713 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 714 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 715 if (sensorsAsyncResp->chassisSubNode == sensors::powerNodeStr) 716 { 717 sensorHeaders = {"Voltages", "PowerSupplies"}; 718 } 719 for (const std::string& sensorGroup : sensorHeaders) 720 { 721 nlohmann::json::iterator entry = response.find(sensorGroup); 722 if (entry == response.end()) 723 { 724 continue; 725 } 726 nlohmann::json::array_t* arr = 727 entry->get_ptr<nlohmann::json::array_t*>(); 728 if (arr == nullptr) 729 { 730 continue; 731 } 732 json_util::sortJsonArrayByKey(*arr, "Name"); 733 734 // add the index counts to the end of each entry 735 size_t count = 0; 736 for (nlohmann::json& sensorJson : *entry) 737 { 738 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 739 if (odata == sensorJson.end()) 740 { 741 continue; 742 } 743 std::string* value = odata->get_ptr<std::string*>(); 744 if (value != nullptr) 745 { 746 *value += "/" + std::to_string(count); 747 sensorJson["MemberId"] = std::to_string(count); 748 count++; 749 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 750 } 751 } 752 } 753 } 754 755 /** 756 * @brief Finds the inventory item with the specified object path. 757 * @param inventoryItems D-Bus inventory items associated with sensors. 758 * @param invItemObjPath D-Bus object path of inventory item. 759 * @return Inventory item within vector, or nullptr if no match found. 760 */ 761 inline InventoryItem* findInventoryItem( 762 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 763 const std::string& invItemObjPath) 764 { 765 for (InventoryItem& inventoryItem : *inventoryItems) 766 { 767 if (inventoryItem.objectPath == invItemObjPath) 768 { 769 return &inventoryItem; 770 } 771 } 772 return nullptr; 773 } 774 775 /** 776 * @brief Finds the inventory item associated with the specified sensor. 777 * @param inventoryItems D-Bus inventory items associated with sensors. 778 * @param sensorObjPath D-Bus object path of sensor. 779 * @return Inventory item within vector, or nullptr if no match found. 780 */ 781 inline InventoryItem* findInventoryItemForSensor( 782 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 783 const std::string& sensorObjPath) 784 { 785 for (InventoryItem& inventoryItem : *inventoryItems) 786 { 787 if (inventoryItem.sensors.contains(sensorObjPath)) 788 { 789 return &inventoryItem; 790 } 791 } 792 return nullptr; 793 } 794 795 /** 796 * @brief Finds the inventory item associated with the specified led path. 797 * @param inventoryItems D-Bus inventory items associated with sensors. 798 * @param ledObjPath D-Bus object path of led. 799 * @return Inventory item within vector, or nullptr if no match found. 800 */ 801 inline InventoryItem* findInventoryItemForLed( 802 std::vector<InventoryItem>& inventoryItems, const std::string& ledObjPath) 803 { 804 for (InventoryItem& inventoryItem : inventoryItems) 805 { 806 if (inventoryItem.ledObjectPath == ledObjPath) 807 { 808 return &inventoryItem; 809 } 810 } 811 return nullptr; 812 } 813 814 /** 815 * @brief Adds inventory item and associated sensor to specified vector. 816 * 817 * Adds a new InventoryItem to the vector if necessary. Searches for an 818 * existing InventoryItem with the specified object path. If not found, one is 819 * added to the vector. 820 * 821 * Next, the specified sensor is added to the set of sensors associated with the 822 * InventoryItem. 823 * 824 * @param inventoryItems D-Bus inventory items associated with sensors. 825 * @param invItemObjPath D-Bus object path of inventory item. 826 * @param sensorObjPath D-Bus object path of sensor 827 */ 828 inline void addInventoryItem( 829 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 830 const std::string& invItemObjPath, const std::string& sensorObjPath) 831 { 832 // Look for inventory item in vector 833 InventoryItem* inventoryItem = 834 findInventoryItem(inventoryItems, invItemObjPath); 835 836 // If inventory item doesn't exist in vector, add it 837 if (inventoryItem == nullptr) 838 { 839 inventoryItems->emplace_back(invItemObjPath); 840 inventoryItem = &(inventoryItems->back()); 841 } 842 843 // Add sensor to set of sensors associated with inventory item 844 inventoryItem->sensors.emplace(sensorObjPath); 845 } 846 847 /** 848 * @brief Stores D-Bus data in the specified inventory item. 849 * 850 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 851 * specified InventoryItem. 852 * 853 * This data is later used to provide sensor property values in the JSON 854 * response. 855 * 856 * @param inventoryItem Inventory item where data will be stored. 857 * @param interfacesDict Map containing D-Bus interfaces and their properties 858 * for the specified inventory item. 859 */ 860 inline void storeInventoryItemData( 861 InventoryItem& inventoryItem, 862 const dbus::utility::DBusInterfacesMap& interfacesDict) 863 { 864 // Get properties from Inventory.Item interface 865 866 for (const auto& [interface, values] : interfacesDict) 867 { 868 if (interface == "xyz.openbmc_project.Inventory.Item") 869 { 870 for (const auto& [name, dbusValue] : values) 871 { 872 if (name == "Present") 873 { 874 const bool* value = std::get_if<bool>(&dbusValue); 875 if (value != nullptr) 876 { 877 inventoryItem.isPresent = *value; 878 } 879 } 880 } 881 } 882 // Check if Inventory.Item.PowerSupply interface is present 883 884 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 885 { 886 inventoryItem.isPowerSupply = true; 887 } 888 889 // Get properties from Inventory.Decorator.Asset interface 890 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 891 { 892 for (const auto& [name, dbusValue] : values) 893 { 894 if (name == "Manufacturer") 895 { 896 const std::string* value = 897 std::get_if<std::string>(&dbusValue); 898 if (value != nullptr) 899 { 900 inventoryItem.manufacturer = *value; 901 } 902 } 903 if (name == "Model") 904 { 905 const std::string* value = 906 std::get_if<std::string>(&dbusValue); 907 if (value != nullptr) 908 { 909 inventoryItem.model = *value; 910 } 911 } 912 if (name == "SerialNumber") 913 { 914 const std::string* value = 915 std::get_if<std::string>(&dbusValue); 916 if (value != nullptr) 917 { 918 inventoryItem.serialNumber = *value; 919 } 920 } 921 if (name == "PartNumber") 922 { 923 const std::string* value = 924 std::get_if<std::string>(&dbusValue); 925 if (value != nullptr) 926 { 927 inventoryItem.partNumber = *value; 928 } 929 } 930 } 931 } 932 933 if (interface == 934 "xyz.openbmc_project.State.Decorator.OperationalStatus") 935 { 936 for (const auto& [name, dbusValue] : values) 937 { 938 if (name == "Functional") 939 { 940 const bool* value = std::get_if<bool>(&dbusValue); 941 if (value != nullptr) 942 { 943 inventoryItem.isFunctional = *value; 944 } 945 } 946 } 947 } 948 } 949 } 950 951 /** 952 * @brief Gets D-Bus data for inventory items associated with sensors. 953 * 954 * Uses the specified connections (services) to obtain D-Bus data for inventory 955 * items associated with sensors. Stores the resulting data in the 956 * inventoryItems vector. 957 * 958 * This data is later used to provide sensor property values in the JSON 959 * response. 960 * 961 * Finds the inventory item data asynchronously. Invokes callback when data has 962 * been obtained. 963 * 964 * The callback must have the following signature: 965 * @code 966 * callback(void) 967 * @endcode 968 * 969 * This function is called recursively, obtaining data asynchronously from one 970 * connection in each call. This ensures the callback is not invoked until the 971 * last asynchronous function has completed. 972 * 973 * @param sensorsAsyncResp Pointer to object holding response data. 974 * @param inventoryItems D-Bus inventory items associated with sensors. 975 * @param invConnections Connections that provide data for the inventory items. 976 * implements ObjectManager. 977 * @param callback Callback to invoke when inventory data has been obtained. 978 * @param invConnectionsIndex Current index in invConnections. Only specified 979 * in recursive calls to this function. 980 */ 981 template <typename Callback> 982 void getInventoryItemsData( 983 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 984 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 985 const std::shared_ptr<std::set<std::string>>& invConnections, 986 Callback&& callback, size_t invConnectionsIndex = 0) 987 { 988 BMCWEB_LOG_DEBUG("getInventoryItemsData enter"); 989 990 // If no more connections left, call callback 991 if (invConnectionsIndex >= invConnections->size()) 992 { 993 callback(); 994 BMCWEB_LOG_DEBUG("getInventoryItemsData exit"); 995 return; 996 } 997 998 // Get inventory item data from current connection 999 auto it = invConnections->begin(); 1000 std::advance(it, invConnectionsIndex); 1001 if (it != invConnections->end()) 1002 { 1003 const std::string& invConnection = *it; 1004 1005 // Get all object paths and their interfaces for current connection 1006 sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 1007 dbus::utility::getManagedObjects( 1008 invConnection, path, 1009 [sensorsAsyncResp, inventoryItems, invConnections, 1010 callback = std::forward<Callback>(callback), invConnectionsIndex]( 1011 const boost::system::error_code& ec, 1012 const dbus::utility::ManagedObjectType& resp) mutable { 1013 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler enter"); 1014 if (ec) 1015 { 1016 BMCWEB_LOG_ERROR( 1017 "getInventoryItemsData respHandler DBus error {}", ec); 1018 messages::internalError(sensorsAsyncResp->asyncResp->res); 1019 return; 1020 } 1021 1022 // Loop through returned object paths 1023 for (const auto& objDictEntry : resp) 1024 { 1025 const std::string& objPath = 1026 static_cast<const std::string&>(objDictEntry.first); 1027 1028 // If this object path is one of the specified inventory 1029 // items 1030 InventoryItem* inventoryItem = 1031 findInventoryItem(inventoryItems, objPath); 1032 if (inventoryItem != nullptr) 1033 { 1034 // Store inventory data in InventoryItem 1035 storeInventoryItemData(*inventoryItem, 1036 objDictEntry.second); 1037 } 1038 } 1039 1040 // Recurse to get inventory item data from next connection 1041 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1042 invConnections, std::move(callback), 1043 invConnectionsIndex + 1); 1044 1045 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler exit"); 1046 }); 1047 } 1048 1049 BMCWEB_LOG_DEBUG("getInventoryItemsData exit"); 1050 } 1051 1052 /** 1053 * @brief Gets connections that provide D-Bus data for inventory items. 1054 * 1055 * Gets the D-Bus connections (services) that provide data for the inventory 1056 * items that are associated with sensors. 1057 * 1058 * Finds the connections asynchronously. Invokes callback when information has 1059 * been obtained. 1060 * 1061 * The callback must have the following signature: 1062 * @code 1063 * callback(std::shared_ptr<std::set<std::string>> invConnections) 1064 * @endcode 1065 * 1066 * @param sensorsAsyncResp Pointer to object holding response data. 1067 * @param inventoryItems D-Bus inventory items associated with sensors. 1068 * @param callback Callback to invoke when connections have been obtained. 1069 */ 1070 template <typename Callback> 1071 void getInventoryItemsConnections( 1072 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1073 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1074 Callback&& callback) 1075 { 1076 BMCWEB_LOG_DEBUG("getInventoryItemsConnections enter"); 1077 1078 const std::string path = "/xyz/openbmc_project/inventory"; 1079 constexpr std::array<std::string_view, 4> interfaces = { 1080 "xyz.openbmc_project.Inventory.Item", 1081 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1082 "xyz.openbmc_project.Inventory.Decorator.Asset", 1083 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1084 1085 // Make call to ObjectMapper to find all inventory items 1086 dbus::utility::getSubTree( 1087 path, 0, interfaces, 1088 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1089 inventoryItems]( 1090 const boost::system::error_code& ec, 1091 const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { 1092 // Response handler for parsing output from GetSubTree 1093 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler enter"); 1094 if (ec) 1095 { 1096 messages::internalError(sensorsAsyncResp->asyncResp->res); 1097 BMCWEB_LOG_ERROR( 1098 "getInventoryItemsConnections respHandler DBus error {}", 1099 ec); 1100 return; 1101 } 1102 1103 // Make unique list of connections for desired inventory items 1104 std::shared_ptr<std::set<std::string>> invConnections = 1105 std::make_shared<std::set<std::string>>(); 1106 1107 // Loop through objects from GetSubTree 1108 for (const std::pair<std::string, 1109 std::vector<std::pair< 1110 std::string, std::vector<std::string>>>>& 1111 object : subtree) 1112 { 1113 // Check if object path is one of the specified inventory items 1114 const std::string& objPath = object.first; 1115 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1116 { 1117 // Store all connections to inventory item 1118 for (const std::pair<std::string, std::vector<std::string>>& 1119 objData : object.second) 1120 { 1121 const std::string& invConnection = objData.first; 1122 invConnections->insert(invConnection); 1123 } 1124 } 1125 } 1126 1127 callback(invConnections); 1128 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler exit"); 1129 }); 1130 BMCWEB_LOG_DEBUG("getInventoryItemsConnections exit"); 1131 } 1132 1133 /** 1134 * @brief Gets associations from sensors to inventory items. 1135 * 1136 * Looks for ObjectMapper associations from the specified sensors to related 1137 * inventory items. Then finds the associations from those inventory items to 1138 * their LEDs, if any. 1139 * 1140 * Finds the inventory items asynchronously. Invokes callback when information 1141 * has been obtained. 1142 * 1143 * The callback must have the following signature: 1144 * @code 1145 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1146 * @endcode 1147 * 1148 * @param sensorsAsyncResp Pointer to object holding response data. 1149 * @param sensorNames All sensors within the current chassis. 1150 * implements ObjectManager. 1151 * @param callback Callback to invoke when inventory items have been obtained. 1152 */ 1153 template <typename Callback> 1154 void getInventoryItemAssociations( 1155 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1156 const std::shared_ptr<std::set<std::string>>& sensorNames, 1157 Callback&& callback) 1158 { 1159 BMCWEB_LOG_DEBUG("getInventoryItemAssociations enter"); 1160 1161 // Call GetManagedObjects on the ObjectMapper to get all associations 1162 sdbusplus::message::object_path path("/"); 1163 dbus::utility::getManagedObjects( 1164 "xyz.openbmc_project.ObjectMapper", path, 1165 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1166 sensorNames](const boost::system::error_code& ec, 1167 const dbus::utility::ManagedObjectType& resp) mutable { 1168 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler enter"); 1169 if (ec) 1170 { 1171 BMCWEB_LOG_ERROR( 1172 "getInventoryItemAssociations respHandler DBus error {}", 1173 ec); 1174 messages::internalError(sensorsAsyncResp->asyncResp->res); 1175 return; 1176 } 1177 1178 // Create vector to hold list of inventory items 1179 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1180 std::make_shared<std::vector<InventoryItem>>(); 1181 1182 // Loop through returned object paths 1183 std::string sensorAssocPath; 1184 sensorAssocPath.reserve(128); // avoid memory allocations 1185 for (const auto& objDictEntry : resp) 1186 { 1187 const std::string& objPath = 1188 static_cast<const std::string&>(objDictEntry.first); 1189 1190 // If path is inventory association for one of the specified 1191 // sensors 1192 for (const std::string& sensorName : *sensorNames) 1193 { 1194 sensorAssocPath = sensorName; 1195 sensorAssocPath += "/inventory"; 1196 if (objPath == sensorAssocPath) 1197 { 1198 // Get Association interface for object path 1199 for (const auto& [interface, values] : 1200 objDictEntry.second) 1201 { 1202 if (interface == "xyz.openbmc_project.Association") 1203 { 1204 for (const auto& [valueName, value] : values) 1205 { 1206 if (valueName == "endpoints") 1207 { 1208 const std::vector<std::string>* 1209 endpoints = std::get_if< 1210 std::vector<std::string>>( 1211 &value); 1212 if ((endpoints != nullptr) && 1213 !endpoints->empty()) 1214 { 1215 // Add inventory item to vector 1216 const std::string& invItemPath = 1217 endpoints->front(); 1218 addInventoryItem(inventoryItems, 1219 invItemPath, 1220 sensorName); 1221 } 1222 } 1223 } 1224 } 1225 } 1226 break; 1227 } 1228 } 1229 } 1230 1231 // Now loop through the returned object paths again, this time to 1232 // find the leds associated with the inventory items we just found 1233 std::string inventoryAssocPath; 1234 inventoryAssocPath.reserve(128); // avoid memory allocations 1235 for (const auto& objDictEntry : resp) 1236 { 1237 const std::string& objPath = 1238 static_cast<const std::string&>(objDictEntry.first); 1239 1240 for (InventoryItem& inventoryItem : *inventoryItems) 1241 { 1242 inventoryAssocPath = inventoryItem.objectPath; 1243 inventoryAssocPath += "/leds"; 1244 if (objPath == inventoryAssocPath) 1245 { 1246 for (const auto& [interface, values] : 1247 objDictEntry.second) 1248 { 1249 if (interface == "xyz.openbmc_project.Association") 1250 { 1251 for (const auto& [valueName, value] : values) 1252 { 1253 if (valueName == "endpoints") 1254 { 1255 const std::vector<std::string>* 1256 endpoints = std::get_if< 1257 std::vector<std::string>>( 1258 &value); 1259 if ((endpoints != nullptr) && 1260 !endpoints->empty()) 1261 { 1262 // Add inventory item to vector 1263 // Store LED path in inventory item 1264 const std::string& ledPath = 1265 endpoints->front(); 1266 inventoryItem.ledObjectPath = 1267 ledPath; 1268 } 1269 } 1270 } 1271 } 1272 } 1273 1274 break; 1275 } 1276 } 1277 } 1278 callback(inventoryItems); 1279 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler exit"); 1280 }); 1281 1282 BMCWEB_LOG_DEBUG("getInventoryItemAssociations exit"); 1283 } 1284 1285 /** 1286 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1287 * 1288 * Uses the specified connections (services) to obtain D-Bus data for inventory 1289 * item leds associated with sensors. Stores the resulting data in the 1290 * inventoryItems vector. 1291 * 1292 * This data is later used to provide sensor property values in the JSON 1293 * response. 1294 * 1295 * Finds the inventory item led data asynchronously. Invokes callback when data 1296 * has been obtained. 1297 * 1298 * The callback must have the following signature: 1299 * @code 1300 * callback() 1301 * @endcode 1302 * 1303 * This function is called recursively, obtaining data asynchronously from one 1304 * connection in each call. This ensures the callback is not invoked until the 1305 * last asynchronous function has completed. 1306 * 1307 * @param sensorsAsyncResp Pointer to object holding response data. 1308 * @param inventoryItems D-Bus inventory items associated with sensors. 1309 * @param ledConnections Connections that provide data for the inventory leds. 1310 * @param callback Callback to invoke when inventory data has been obtained. 1311 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1312 * in recursive calls to this function. 1313 */ 1314 template <typename Callback> 1315 void getInventoryLedData( 1316 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1317 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1318 const std::shared_ptr<std::map<std::string, std::string>>& ledConnections, 1319 Callback&& callback, size_t ledConnectionsIndex = 0) 1320 { 1321 BMCWEB_LOG_DEBUG("getInventoryLedData enter"); 1322 1323 // If no more connections left, call callback 1324 if (ledConnectionsIndex >= ledConnections->size()) 1325 { 1326 callback(); 1327 BMCWEB_LOG_DEBUG("getInventoryLedData exit"); 1328 return; 1329 } 1330 1331 // Get inventory item data from current connection 1332 auto it = ledConnections->begin(); 1333 std::advance(it, ledConnectionsIndex); 1334 if (it != ledConnections->end()) 1335 { 1336 const std::string& ledPath = (*it).first; 1337 const std::string& ledConnection = (*it).second; 1338 // Response handler for Get State property 1339 auto respHandler = 1340 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1341 callback = std::forward<Callback>(callback), 1342 ledConnectionsIndex](const boost::system::error_code& ec, 1343 const std::string& state) mutable { 1344 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler enter"); 1345 if (ec) 1346 { 1347 BMCWEB_LOG_ERROR( 1348 "getInventoryLedData respHandler DBus error {}", ec); 1349 messages::internalError(sensorsAsyncResp->asyncResp->res); 1350 return; 1351 } 1352 1353 BMCWEB_LOG_DEBUG("Led state: {}", state); 1354 // Find inventory item with this LED object path 1355 InventoryItem* inventoryItem = 1356 findInventoryItemForLed(*inventoryItems, ledPath); 1357 if (inventoryItem != nullptr) 1358 { 1359 // Store LED state in InventoryItem 1360 if (state.ends_with("On")) 1361 { 1362 inventoryItem->ledState = sensor_utils::LedState::ON; 1363 } 1364 else if (state.ends_with("Blink")) 1365 { 1366 inventoryItem->ledState = sensor_utils::LedState::BLINK; 1367 } 1368 else if (state.ends_with("Off")) 1369 { 1370 inventoryItem->ledState = sensor_utils::LedState::OFF; 1371 } 1372 else 1373 { 1374 inventoryItem->ledState = 1375 sensor_utils::LedState::UNKNOWN; 1376 } 1377 } 1378 1379 // Recurse to get LED data from next connection 1380 getInventoryLedData(sensorsAsyncResp, inventoryItems, 1381 ledConnections, std::move(callback), 1382 ledConnectionsIndex + 1); 1383 1384 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler exit"); 1385 }; 1386 1387 // Get the State property for the current LED 1388 dbus::utility::getProperty<std::string>( 1389 ledConnection, ledPath, "xyz.openbmc_project.Led.Physical", "State", 1390 std::move(respHandler)); 1391 } 1392 1393 BMCWEB_LOG_DEBUG("getInventoryLedData exit"); 1394 } 1395 1396 /** 1397 * @brief Gets LED data for LEDs associated with given inventory items. 1398 * 1399 * Gets the D-Bus connections (services) that provide LED data for the LEDs 1400 * associated with the specified inventory items. Then gets the LED data from 1401 * each connection and stores it in the inventory item. 1402 * 1403 * This data is later used to provide sensor property values in the JSON 1404 * response. 1405 * 1406 * Finds the LED data asynchronously. Invokes callback when information has 1407 * been obtained. 1408 * 1409 * The callback must have the following signature: 1410 * @code 1411 * callback() 1412 * @endcode 1413 * 1414 * @param sensorsAsyncResp Pointer to object holding response data. 1415 * @param inventoryItems D-Bus inventory items associated with sensors. 1416 * @param callback Callback to invoke when inventory items have been obtained. 1417 */ 1418 template <typename Callback> 1419 void getInventoryLeds( 1420 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1421 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1422 Callback&& callback) 1423 { 1424 BMCWEB_LOG_DEBUG("getInventoryLeds enter"); 1425 1426 const std::string path = "/xyz/openbmc_project"; 1427 constexpr std::array<std::string_view, 1> interfaces = { 1428 "xyz.openbmc_project.Led.Physical"}; 1429 1430 // Make call to ObjectMapper to find all inventory items 1431 dbus::utility::getSubTree( 1432 path, 0, interfaces, 1433 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1434 inventoryItems]( 1435 const boost::system::error_code& ec, 1436 const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { 1437 // Response handler for parsing output from GetSubTree 1438 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter"); 1439 if (ec) 1440 { 1441 messages::internalError(sensorsAsyncResp->asyncResp->res); 1442 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}", 1443 ec); 1444 return; 1445 } 1446 1447 // Build map of LED object paths to connections 1448 std::shared_ptr<std::map<std::string, std::string>> ledConnections = 1449 std::make_shared<std::map<std::string, std::string>>(); 1450 1451 // Loop through objects from GetSubTree 1452 for (const std::pair<std::string, 1453 std::vector<std::pair< 1454 std::string, std::vector<std::string>>>>& 1455 object : subtree) 1456 { 1457 // Check if object path is LED for one of the specified 1458 // inventory items 1459 const std::string& ledPath = object.first; 1460 if (findInventoryItemForLed(*inventoryItems, ledPath) != 1461 nullptr) 1462 { 1463 // Add mapping from ledPath to connection 1464 const std::string& connection = 1465 object.second.begin()->first; 1466 (*ledConnections)[ledPath] = connection; 1467 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath, 1468 connection); 1469 } 1470 } 1471 1472 getInventoryLedData(sensorsAsyncResp, inventoryItems, 1473 ledConnections, std::move(callback)); 1474 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit"); 1475 }); 1476 BMCWEB_LOG_DEBUG("getInventoryLeds exit"); 1477 } 1478 1479 /** 1480 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 1481 * 1482 * Uses the specified connections (services) (currently assumes just one) to 1483 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 1484 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 1485 * 1486 * This data is later used to provide sensor property values in the JSON 1487 * response. 1488 * 1489 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1490 * when data has been obtained. 1491 * 1492 * The callback must have the following signature: 1493 * @code 1494 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1495 * @endcode 1496 * 1497 * @param sensorsAsyncResp Pointer to object holding response data. 1498 * @param inventoryItems D-Bus inventory items associated with sensors. 1499 * @param psAttributesConnections Connections that provide data for the Power 1500 * Supply Attributes 1501 * @param callback Callback to invoke when data has been obtained. 1502 */ 1503 template <typename Callback> 1504 void getPowerSupplyAttributesData( 1505 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1506 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1507 const std::map<std::string, std::string>& psAttributesConnections, 1508 Callback&& callback) 1509 { 1510 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter"); 1511 1512 if (psAttributesConnections.empty()) 1513 { 1514 BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!"); 1515 callback(inventoryItems); 1516 return; 1517 } 1518 1519 // Assuming just one connection (service) for now 1520 auto it = psAttributesConnections.begin(); 1521 1522 const std::string& psAttributesPath = (*it).first; 1523 const std::string& psAttributesConnection = (*it).second; 1524 1525 // Response handler for Get DeratingFactor property 1526 auto respHandler = [sensorsAsyncResp, inventoryItems, 1527 callback = std::forward<Callback>(callback)]( 1528 const boost::system::error_code& ec, 1529 uint32_t value) mutable { 1530 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter"); 1531 if (ec) 1532 { 1533 BMCWEB_LOG_ERROR( 1534 "getPowerSupplyAttributesData respHandler DBus error {}", ec); 1535 messages::internalError(sensorsAsyncResp->asyncResp->res); 1536 return; 1537 } 1538 1539 BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value); 1540 // Store value in Power Supply Inventory Items 1541 for (InventoryItem& inventoryItem : *inventoryItems) 1542 { 1543 if (inventoryItem.isPowerSupply) 1544 { 1545 inventoryItem.powerSupplyEfficiencyPercent = 1546 static_cast<int>(value); 1547 } 1548 } 1549 1550 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit"); 1551 callback(inventoryItems); 1552 }; 1553 1554 // Get the DeratingFactor property for the PowerSupplyAttributes 1555 // Currently only property on the interface/only one we care about 1556 dbus::utility::getProperty<uint32_t>( 1557 psAttributesConnection, psAttributesPath, 1558 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 1559 std::move(respHandler)); 1560 1561 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit"); 1562 } 1563 1564 /** 1565 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 1566 * 1567 * Gets the D-Bus connection (service) that provides Power Supply Attributes 1568 * data. Then gets the Power Supply Attributes data from the connection 1569 * (currently just assumes 1 connection) and stores the data in the inventory 1570 * item. 1571 * 1572 * This data is later used to provide sensor property values in the JSON 1573 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 1574 * 1575 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1576 * when information has been obtained. 1577 * 1578 * The callback must have the following signature: 1579 * @code 1580 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1581 * @endcode 1582 * 1583 * @param sensorsAsyncResp Pointer to object holding response data. 1584 * @param inventoryItems D-Bus inventory items associated with sensors. 1585 * @param callback Callback to invoke when data has been obtained. 1586 */ 1587 template <typename Callback> 1588 void getPowerSupplyAttributes( 1589 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1590 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1591 Callback&& callback) 1592 { 1593 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter"); 1594 1595 // Only need the power supply attributes when the Power Schema 1596 if (sensorsAsyncResp->chassisSubNode != sensors::powerNodeStr) 1597 { 1598 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power"); 1599 callback(inventoryItems); 1600 return; 1601 } 1602 1603 constexpr std::array<std::string_view, 1> interfaces = { 1604 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 1605 1606 // Make call to ObjectMapper to find the PowerSupplyAttributes service 1607 dbus::utility::getSubTree( 1608 "/xyz/openbmc_project", 0, interfaces, 1609 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1610 inventoryItems]( 1611 const boost::system::error_code& ec, 1612 const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { 1613 // Response handler for parsing output from GetSubTree 1614 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter"); 1615 if (ec) 1616 { 1617 messages::internalError(sensorsAsyncResp->asyncResp->res); 1618 BMCWEB_LOG_ERROR( 1619 "getPowerSupplyAttributes respHandler DBus error {}", ec); 1620 return; 1621 } 1622 if (subtree.empty()) 1623 { 1624 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!"); 1625 callback(inventoryItems); 1626 return; 1627 } 1628 1629 // Currently we only support 1 power supply attribute, use this for 1630 // all the power supplies. Build map of object path to connection. 1631 // Assume just 1 connection and 1 path for now. 1632 std::map<std::string, std::string> psAttributesConnections; 1633 1634 if (subtree[0].first.empty() || subtree[0].second.empty()) 1635 { 1636 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 1637 callback(inventoryItems); 1638 return; 1639 } 1640 1641 const std::string& psAttributesPath = subtree[0].first; 1642 const std::string& connection = subtree[0].second.begin()->first; 1643 1644 if (connection.empty()) 1645 { 1646 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 1647 callback(inventoryItems); 1648 return; 1649 } 1650 1651 psAttributesConnections[psAttributesPath] = connection; 1652 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath, 1653 connection); 1654 1655 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 1656 psAttributesConnections, 1657 std::move(callback)); 1658 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit"); 1659 }); 1660 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit"); 1661 } 1662 1663 /** 1664 * @brief Gets inventory items associated with sensors. 1665 * 1666 * Finds the inventory items that are associated with the specified sensors. 1667 * Then gets D-Bus data for the inventory items, such as presence and VPD. 1668 * 1669 * This data is later used to provide sensor property values in the JSON 1670 * response. 1671 * 1672 * Finds the inventory items asynchronously. Invokes callback when the 1673 * inventory items have been obtained. 1674 * 1675 * The callback must have the following signature: 1676 * @code 1677 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1678 * @endcode 1679 * 1680 * @param sensorsAsyncResp Pointer to object holding response data. 1681 * @param sensorNames All sensors within the current chassis. 1682 * implements ObjectManager. 1683 * @param callback Callback to invoke when inventory items have been obtained. 1684 */ 1685 template <typename Callback> 1686 inline void getInventoryItems( 1687 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1688 const std::shared_ptr<std::set<std::string>>& sensorNames, 1689 Callback&& callback) 1690 { 1691 BMCWEB_LOG_DEBUG("getInventoryItems enter"); 1692 auto getInventoryItemAssociationsCb = 1693 [sensorsAsyncResp, callback = std::forward<Callback>(callback)]( 1694 const std::shared_ptr<std::vector<InventoryItem>>& 1695 inventoryItems) mutable { 1696 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter"); 1697 auto getInventoryItemsConnectionsCb = 1698 [sensorsAsyncResp, inventoryItems, 1699 callback = std::forward<Callback>(callback)]( 1700 const std::shared_ptr<std::set<std::string>>& 1701 invConnections) mutable { 1702 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter"); 1703 auto getInventoryItemsDataCb = 1704 [sensorsAsyncResp, inventoryItems, 1705 callback = 1706 std::forward<Callback>(callback)]() mutable { 1707 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter"); 1708 1709 auto getInventoryLedsCb = 1710 [sensorsAsyncResp, inventoryItems, 1711 callback = std::forward<Callback>( 1712 callback)]() mutable { 1713 BMCWEB_LOG_DEBUG( 1714 "getInventoryLedsCb enter"); 1715 // Find Power Supply Attributes and get the 1716 // data 1717 getPowerSupplyAttributes( 1718 sensorsAsyncResp, inventoryItems, 1719 std::move(callback)); 1720 BMCWEB_LOG_DEBUG("getInventoryLedsCb exit"); 1721 }; 1722 1723 // Find led connections and get the data 1724 getInventoryLeds(sensorsAsyncResp, inventoryItems, 1725 std::move(getInventoryLedsCb)); 1726 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit"); 1727 }; 1728 1729 // Get inventory item data from connections 1730 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1731 invConnections, 1732 std::move(getInventoryItemsDataCb)); 1733 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit"); 1734 }; 1735 1736 // Get connections that provide inventory item data 1737 getInventoryItemsConnections( 1738 sensorsAsyncResp, inventoryItems, 1739 std::move(getInventoryItemsConnectionsCb)); 1740 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit"); 1741 }; 1742 1743 // Get associations from sensors to inventory items 1744 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, 1745 std::move(getInventoryItemAssociationsCb)); 1746 BMCWEB_LOG_DEBUG("getInventoryItems exit"); 1747 } 1748 1749 /** 1750 * @brief Returns JSON PowerSupply object for the specified inventory item. 1751 * 1752 * Searches for a JSON PowerSupply object that matches the specified inventory 1753 * item. If one is not found, a new PowerSupply object is added to the JSON 1754 * array. 1755 * 1756 * Multiple sensors are often associated with one power supply inventory item. 1757 * As a result, multiple sensor values are stored in one JSON PowerSupply 1758 * object. 1759 * 1760 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 1761 * @param inventoryItem Inventory item for the power supply. 1762 * @param chassisId Chassis that contains the power supply. 1763 * @return JSON PowerSupply object for the specified inventory item. 1764 */ 1765 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 1766 const InventoryItem& inventoryItem, 1767 const std::string& chassisId) 1768 { 1769 std::string nameS; 1770 nameS.resize(inventoryItem.name.size()); 1771 std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' '); 1772 // Check if matching PowerSupply object already exists in JSON array 1773 for (nlohmann::json& powerSupply : powerSupplyArray) 1774 { 1775 nlohmann::json::iterator nameIt = powerSupply.find("Name"); 1776 if (nameIt == powerSupply.end()) 1777 { 1778 continue; 1779 } 1780 const std::string* name = nameIt->get_ptr<std::string*>(); 1781 if (name == nullptr) 1782 { 1783 continue; 1784 } 1785 if (nameS == *name) 1786 { 1787 return powerSupply; 1788 } 1789 } 1790 1791 // Add new PowerSupply object to JSON array 1792 powerSupplyArray.push_back({}); 1793 nlohmann::json& powerSupply = powerSupplyArray.back(); 1794 boost::urls::url url = 1795 boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId); 1796 url.set_fragment(("/PowerSupplies"_json_pointer).to_string()); 1797 powerSupply["@odata.id"] = std::move(url); 1798 std::string escaped; 1799 escaped.resize(inventoryItem.name.size()); 1800 std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' '); 1801 powerSupply["Name"] = std::move(escaped); 1802 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 1803 powerSupply["Model"] = inventoryItem.model; 1804 powerSupply["PartNumber"] = inventoryItem.partNumber; 1805 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 1806 sensor_utils::setLedState(powerSupply, &inventoryItem); 1807 1808 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 1809 { 1810 powerSupply["EfficiencyPercent"] = 1811 inventoryItem.powerSupplyEfficiencyPercent; 1812 } 1813 1814 powerSupply["Status"]["State"] = 1815 sensor_utils::getState(&inventoryItem, true); 1816 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 1817 powerSupply["Status"]["Health"] = health; 1818 1819 return powerSupply; 1820 } 1821 1822 /** 1823 * @brief Gets the values of the specified sensors. 1824 * 1825 * Stores the results as JSON in the SensorsAsyncResp. 1826 * 1827 * Gets the sensor values asynchronously. Stores the results later when the 1828 * information has been obtained. 1829 * 1830 * The sensorNames set contains all requested sensors for the current chassis. 1831 * 1832 * To minimize the number of DBus calls, the DBus method 1833 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 1834 * values of all sensors provided by a connection (service). 1835 * 1836 * The connections set contains all the connections that provide sensor values. 1837 * 1838 * The InventoryItem vector contains D-Bus inventory items associated with the 1839 * sensors. Inventory item data is needed for some Redfish sensor properties. 1840 * 1841 * @param SensorsAsyncResp Pointer to object holding response data. 1842 * @param sensorNames All requested sensors within the current chassis. 1843 * @param connections Connections that provide sensor values. 1844 * implements ObjectManager. 1845 * @param inventoryItems Inventory items associated with the sensors. 1846 */ 1847 inline void getSensorData( 1848 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1849 const std::shared_ptr<std::set<std::string>>& sensorNames, 1850 const std::set<std::string>& connections, 1851 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 1852 { 1853 BMCWEB_LOG_DEBUG("getSensorData enter"); 1854 // Get managed objects from all services exposing sensors 1855 for (const std::string& connection : connections) 1856 { 1857 sdbusplus::message::object_path sensorPath( 1858 "/xyz/openbmc_project/sensors"); 1859 dbus::utility::getManagedObjects( 1860 connection, sensorPath, 1861 [sensorsAsyncResp, sensorNames, 1862 inventoryItems](const boost::system::error_code& ec, 1863 const dbus::utility::ManagedObjectType& resp) { 1864 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter"); 1865 if (ec) 1866 { 1867 BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec); 1868 messages::internalError(sensorsAsyncResp->asyncResp->res); 1869 return; 1870 } 1871 auto chassisSubNode = sensor_utils::chassisSubNodeFromString( 1872 sensorsAsyncResp->chassisSubNode); 1873 // Go through all objects and update response with sensor data 1874 for (const auto& objDictEntry : resp) 1875 { 1876 const std::string& objPath = 1877 static_cast<const std::string&>(objDictEntry.first); 1878 BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}", 1879 objPath); 1880 1881 std::vector<std::string> split; 1882 // Reserve space for 1883 // /xyz/openbmc_project/sensors/<name>/<subname> 1884 split.reserve(6); 1885 // NOLINTNEXTLINE 1886 bmcweb::split(split, objPath, '/'); 1887 if (split.size() < 6) 1888 { 1889 BMCWEB_LOG_ERROR("Got path that isn't long enough {}", 1890 objPath); 1891 continue; 1892 } 1893 // These indexes aren't intuitive, as split puts an empty 1894 // string at the beginning 1895 const std::string& sensorType = split[4]; 1896 const std::string& sensorName = split[5]; 1897 BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName, 1898 sensorType); 1899 if (sensorNames->find(objPath) == sensorNames->end()) 1900 { 1901 BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName); 1902 continue; 1903 } 1904 1905 // Find inventory item (if any) associated with sensor 1906 InventoryItem* inventoryItem = 1907 findInventoryItemForSensor(inventoryItems, objPath); 1908 1909 const std::string& sensorSchema = 1910 sensorsAsyncResp->chassisSubNode; 1911 1912 nlohmann::json* sensorJson = nullptr; 1913 1914 if (sensorSchema == sensors::sensorsNodeStr && 1915 !sensorsAsyncResp->efficientExpand) 1916 { 1917 std::string sensorId = 1918 redfish::sensor_utils::getSensorId(sensorName, 1919 sensorType); 1920 1921 sensorsAsyncResp->asyncResp->res 1922 .jsonValue["@odata.id"] = boost::urls::format( 1923 "/redfish/v1/Chassis/{}/{}/{}", 1924 sensorsAsyncResp->chassisId, 1925 sensorsAsyncResp->chassisSubNode, sensorId); 1926 sensorJson = 1927 &(sensorsAsyncResp->asyncResp->res.jsonValue); 1928 } 1929 else 1930 { 1931 std::string fieldName; 1932 if (sensorsAsyncResp->efficientExpand) 1933 { 1934 fieldName = "Members"; 1935 } 1936 else if (sensorType == "temperature") 1937 { 1938 fieldName = "Temperatures"; 1939 } 1940 else if (sensorType == "fan" || 1941 sensorType == "fan_tach" || 1942 sensorType == "fan_pwm") 1943 { 1944 fieldName = "Fans"; 1945 } 1946 else if (sensorType == "voltage") 1947 { 1948 fieldName = "Voltages"; 1949 } 1950 else if (sensorType == "power") 1951 { 1952 if (sensorName == "total_power") 1953 { 1954 fieldName = "PowerControl"; 1955 } 1956 else if ((inventoryItem != nullptr) && 1957 (inventoryItem->isPowerSupply)) 1958 { 1959 fieldName = "PowerSupplies"; 1960 } 1961 else 1962 { 1963 // Other power sensors are in SensorCollection 1964 continue; 1965 } 1966 } 1967 else 1968 { 1969 BMCWEB_LOG_ERROR( 1970 "Unsure how to handle sensorType {}", 1971 sensorType); 1972 continue; 1973 } 1974 1975 nlohmann::json& tempArray = 1976 sensorsAsyncResp->asyncResp->res 1977 .jsonValue[fieldName]; 1978 if (fieldName == "PowerControl") 1979 { 1980 if (tempArray.empty()) 1981 { 1982 // Put multiple "sensors" into a single 1983 // PowerControl. Follows MemberId naming and 1984 // naming in power.hpp. 1985 nlohmann::json::object_t power; 1986 boost::urls::url url = boost::urls::format( 1987 "/redfish/v1/Chassis/{}/{}", 1988 sensorsAsyncResp->chassisId, 1989 sensorsAsyncResp->chassisSubNode); 1990 url.set_fragment( 1991 (""_json_pointer / fieldName / "0") 1992 .to_string()); 1993 power["@odata.id"] = std::move(url); 1994 tempArray.emplace_back(std::move(power)); 1995 } 1996 sensorJson = &(tempArray.back()); 1997 } 1998 else if (fieldName == "PowerSupplies") 1999 { 2000 if (inventoryItem != nullptr) 2001 { 2002 sensorJson = &(getPowerSupply( 2003 tempArray, *inventoryItem, 2004 sensorsAsyncResp->chassisId)); 2005 } 2006 } 2007 else if (fieldName == "Members") 2008 { 2009 std::string sensorId = 2010 redfish::sensor_utils::getSensorId(sensorName, 2011 sensorType); 2012 2013 nlohmann::json::object_t member; 2014 member["@odata.id"] = boost::urls::format( 2015 "/redfish/v1/Chassis/{}/{}/{}", 2016 sensorsAsyncResp->chassisId, 2017 sensorsAsyncResp->chassisSubNode, sensorId); 2018 tempArray.emplace_back(std::move(member)); 2019 sensorJson = &(tempArray.back()); 2020 } 2021 else 2022 { 2023 nlohmann::json::object_t member; 2024 boost::urls::url url = boost::urls::format( 2025 "/redfish/v1/Chassis/{}/{}", 2026 sensorsAsyncResp->chassisId, 2027 sensorsAsyncResp->chassisSubNode); 2028 url.set_fragment( 2029 (""_json_pointer / fieldName).to_string()); 2030 member["@odata.id"] = std::move(url); 2031 tempArray.emplace_back(std::move(member)); 2032 sensorJson = &(tempArray.back()); 2033 } 2034 } 2035 2036 if (sensorJson != nullptr) 2037 { 2038 objectInterfacesToJson( 2039 sensorName, sensorType, chassisSubNode, 2040 objDictEntry.second, *sensorJson, inventoryItem); 2041 2042 std::string path = "/xyz/openbmc_project/sensors/"; 2043 path += sensorType; 2044 path += "/"; 2045 path += sensorName; 2046 sensorsAsyncResp->addMetadata(*sensorJson, path); 2047 } 2048 } 2049 if (sensorsAsyncResp.use_count() == 1) 2050 { 2051 sortJSONResponse(sensorsAsyncResp); 2052 if (chassisSubNode == 2053 sensor_utils::ChassisSubNode::sensorsNode && 2054 sensorsAsyncResp->efficientExpand) 2055 { 2056 sensorsAsyncResp->asyncResp->res 2057 .jsonValue["Members@odata.count"] = 2058 sensorsAsyncResp->asyncResp->res 2059 .jsonValue["Members"] 2060 .size(); 2061 } 2062 else if (chassisSubNode == 2063 sensor_utils::ChassisSubNode::thermalNode) 2064 { 2065 populateFanRedundancy(sensorsAsyncResp); 2066 } 2067 } 2068 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit"); 2069 }); 2070 } 2071 BMCWEB_LOG_DEBUG("getSensorData exit"); 2072 } 2073 2074 inline void processSensorList( 2075 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2076 const std::shared_ptr<std::set<std::string>>& sensorNames) 2077 { 2078 auto getConnectionCb = [sensorsAsyncResp, sensorNames]( 2079 const std::set<std::string>& connections) { 2080 BMCWEB_LOG_DEBUG("getConnectionCb enter"); 2081 auto getInventoryItemsCb = 2082 [sensorsAsyncResp, sensorNames, connections]( 2083 const std::shared_ptr<std::vector<InventoryItem>>& 2084 inventoryItems) mutable { 2085 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter"); 2086 // Get sensor data and store results in JSON 2087 getSensorData(sensorsAsyncResp, sensorNames, connections, 2088 inventoryItems); 2089 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit"); 2090 }; 2091 2092 // Get inventory items associated with sensors 2093 getInventoryItems(sensorsAsyncResp, sensorNames, 2094 std::move(getInventoryItemsCb)); 2095 2096 BMCWEB_LOG_DEBUG("getConnectionCb exit"); 2097 }; 2098 2099 // Get set of connections that provide sensor values 2100 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2101 } 2102 2103 /** 2104 * @brief Entry point for retrieving sensors data related to requested 2105 * chassis. 2106 * @param SensorsAsyncResp Pointer to object holding response data 2107 */ 2108 inline void getChassisData( 2109 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2110 { 2111 BMCWEB_LOG_DEBUG("getChassisData enter"); 2112 auto getChassisCb = 2113 [sensorsAsyncResp]( 2114 const std::shared_ptr<std::set<std::string>>& sensorNames) { 2115 BMCWEB_LOG_DEBUG("getChassisCb enter"); 2116 processSensorList(sensorsAsyncResp, sensorNames); 2117 BMCWEB_LOG_DEBUG("getChassisCb exit"); 2118 }; 2119 // SensorCollection doesn't contain the Redundancy property 2120 if (sensorsAsyncResp->chassisSubNode != sensors::sensorsNodeStr) 2121 { 2122 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2123 nlohmann::json::array(); 2124 } 2125 // Get set of sensors in chassis 2126 getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId, 2127 sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types, 2128 std::move(getChassisCb)); 2129 BMCWEB_LOG_DEBUG("getChassisData exit"); 2130 } 2131 2132 /** 2133 * @brief Find the requested sensorName in the list of all sensors supplied by 2134 * the chassis node 2135 * 2136 * @param sensorName The sensor name supplied in the PATCH request 2137 * @param sensorsList The list of sensors managed by the chassis node 2138 * @param sensorsModified The list of sensors that were found as a result of 2139 * repeated calls to this function 2140 */ 2141 inline bool findSensorNameUsingSensorPath( 2142 std::string_view sensorName, const std::set<std::string>& sensorsList, 2143 std::set<std::string>& sensorsModified) 2144 { 2145 for (const auto& chassisSensor : sensorsList) 2146 { 2147 sdbusplus::message::object_path path(chassisSensor); 2148 std::string thisSensorName = path.filename(); 2149 if (thisSensorName.empty()) 2150 { 2151 continue; 2152 } 2153 if (thisSensorName == sensorName) 2154 { 2155 sensorsModified.emplace(chassisSensor); 2156 return true; 2157 } 2158 } 2159 return false; 2160 } 2161 2162 /** 2163 * @brief Entry point for overriding sensor values of given sensor 2164 * 2165 * @param sensorAsyncResp response object 2166 * @param allCollections Collections extract from sensors' request patch info 2167 * @param chassisSubNode Chassis Node for which the query has to happen 2168 */ 2169 inline void setSensorsOverride( 2170 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2171 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>& 2172 allCollections) 2173 { 2174 BMCWEB_LOG_INFO("setSensorsOverride for subNode{}", 2175 sensorAsyncResp->chassisSubNode); 2176 2177 std::string_view propertyValueName; 2178 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2179 std::string memberId; 2180 double value = 0.0; 2181 for (auto& collectionItems : allCollections) 2182 { 2183 if (collectionItems.first == "Temperatures") 2184 { 2185 propertyValueName = "ReadingCelsius"; 2186 } 2187 else if (collectionItems.first == "Fans") 2188 { 2189 propertyValueName = "Reading"; 2190 } 2191 else 2192 { 2193 propertyValueName = "ReadingVolts"; 2194 } 2195 for (auto& item : collectionItems.second) 2196 { 2197 if (!json_util::readJsonObject( // 2198 item, sensorAsyncResp->asyncResp->res, // 2199 "MemberId", memberId, // 2200 propertyValueName, value // 2201 )) 2202 { 2203 return; 2204 } 2205 overrideMap.emplace(memberId, 2206 std::make_pair(value, collectionItems.first)); 2207 } 2208 } 2209 2210 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap, 2211 propertyValueNameStr = 2212 std::string(propertyValueName)]( 2213 const std::shared_ptr< 2214 std::set<std::string>>& sensorsList) { 2215 // Match sensor names in the PATCH request to those managed by the 2216 // chassis node 2217 const std::shared_ptr<std::set<std::string>> sensorNames = 2218 std::make_shared<std::set<std::string>>(); 2219 for (const auto& item : overrideMap) 2220 { 2221 const auto& sensor = item.first; 2222 std::pair<std::string, std::string> sensorNameType = 2223 redfish::sensor_utils::splitSensorNameAndType(sensor); 2224 if (!findSensorNameUsingSensorPath(sensorNameType.second, 2225 *sensorsList, *sensorNames)) 2226 { 2227 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first); 2228 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2229 item.second.second, item.first); 2230 return; 2231 } 2232 } 2233 // Get the connection to which the memberId belongs 2234 auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap, 2235 propertyValueNameStr]( 2236 const std::set< 2237 std::string>& /*connections*/, 2238 const std::set<std::pair< 2239 std::string, std::string>>& 2240 objectsWithConnection) { 2241 if (objectsWithConnection.size() != overrideMap.size()) 2242 { 2243 BMCWEB_LOG_INFO( 2244 "Unable to find all objects with proper connection {} requested {}", 2245 objectsWithConnection.size(), overrideMap.size()); 2246 messages::resourceNotFound( 2247 sensorAsyncResp->asyncResp->res, 2248 sensorAsyncResp->chassisSubNode == sensors::thermalNodeStr 2249 ? "Temperatures" 2250 : "Voltages", 2251 "Count"); 2252 return; 2253 } 2254 for (const auto& item : objectsWithConnection) 2255 { 2256 sdbusplus::message::object_path path(item.first); 2257 std::string sensorName = path.filename(); 2258 if (sensorName.empty()) 2259 { 2260 messages::internalError(sensorAsyncResp->asyncResp->res); 2261 return; 2262 } 2263 std::string id = redfish::sensor_utils::getSensorId( 2264 sensorName, path.parent_path().filename()); 2265 2266 const auto& iterator = overrideMap.find(id); 2267 if (iterator == overrideMap.end()) 2268 { 2269 BMCWEB_LOG_INFO("Unable to find sensor object{}", 2270 item.first); 2271 messages::internalError(sensorAsyncResp->asyncResp->res); 2272 return; 2273 } 2274 setDbusProperty(sensorAsyncResp->asyncResp, 2275 propertyValueNameStr, item.second, item.first, 2276 "xyz.openbmc_project.Sensor.Value", "Value", 2277 iterator->second.first); 2278 } 2279 }; 2280 // Get object with connection for the given sensor name 2281 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2282 std::move(getObjectsWithConnectionCb)); 2283 }; 2284 // get full sensor list for the given chassisId and cross verify the sensor. 2285 getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 2286 sensorAsyncResp->chassisSubNode, sensorAsyncResp->types, 2287 std::move(getChassisSensorListCb)); 2288 } 2289 2290 /** 2291 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2292 * path of the sensor. 2293 * 2294 * Function builds valid Redfish response for sensor query of given chassis and 2295 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2296 * it to caller in a callback. 2297 * 2298 * @param chassis Chassis for which retrieval should be performed 2299 * @param node Node (group) of sensors. See sensor_utils::node for supported 2300 * values 2301 * @param mapComplete Callback to be called with retrieval result 2302 */ 2303 template <typename Callback> 2304 inline void retrieveUriToDbusMap( 2305 const std::string& chassis, const std::string& node, Callback&& mapComplete) 2306 { 2307 decltype(sensors::paths)::const_iterator pathIt = 2308 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2309 [&node](auto&& val) { return val.first == node; }); 2310 if (pathIt == sensors::paths.cend()) 2311 { 2312 BMCWEB_LOG_ERROR("Wrong node provided : {}", node); 2313 std::map<std::string, std::string> noop; 2314 mapComplete(boost::beast::http::status::bad_request, noop); 2315 return; 2316 } 2317 2318 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2319 auto callback = 2320 [asyncResp, mapCompleteCb = std::forward<Callback>(mapComplete)]( 2321 const boost::beast::http::status status, 2322 const std::map<std::string, std::string>& uriToDbus) { 2323 mapCompleteCb(status, uriToDbus); 2324 }; 2325 2326 auto resp = std::make_shared<SensorsAsyncResp>( 2327 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2328 getChassisData(resp); 2329 } 2330 2331 namespace sensors 2332 { 2333 2334 inline void getChassisCallback( 2335 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2336 std::string_view chassisId, std::string_view chassisSubNode, 2337 const std::shared_ptr<std::set<std::string>>& sensorNames) 2338 { 2339 BMCWEB_LOG_DEBUG("getChassisCallback enter "); 2340 2341 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; 2342 for (const std::string& sensor : *sensorNames) 2343 { 2344 BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor); 2345 2346 sdbusplus::message::object_path path(sensor); 2347 std::string sensorName = path.filename(); 2348 if (sensorName.empty()) 2349 { 2350 BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor); 2351 messages::internalError(asyncResp->res); 2352 return; 2353 } 2354 std::string type = path.parent_path().filename(); 2355 std::string id = redfish::sensor_utils::getSensorId(sensorName, type); 2356 2357 nlohmann::json::object_t member; 2358 member["@odata.id"] = boost::urls::format( 2359 "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id); 2360 2361 entriesArray.emplace_back(std::move(member)); 2362 } 2363 2364 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); 2365 BMCWEB_LOG_DEBUG("getChassisCallback exit"); 2366 } 2367 2368 inline void handleSensorCollectionGet( 2369 App& app, const crow::Request& req, 2370 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2371 const std::string& chassisId) 2372 { 2373 query_param::QueryCapabilities capabilities = { 2374 .canDelegateExpandLevel = 1, 2375 }; 2376 query_param::Query delegatedQuery; 2377 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 2378 delegatedQuery, capabilities)) 2379 { 2380 return; 2381 } 2382 2383 if (delegatedQuery.expandType != query_param::ExpandType::None) 2384 { 2385 // we perform efficient expand. 2386 auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>( 2387 asyncResp, chassisId, sensors::dbus::sensorPaths, 2388 sensors::sensorsNodeStr, 2389 /*efficientExpand=*/true); 2390 getChassisData(sensorsAsyncResp); 2391 2392 BMCWEB_LOG_DEBUG( 2393 "SensorCollection doGet exit via efficient expand handler"); 2394 return; 2395 } 2396 2397 // We get all sensors as hyperlinkes in the chassis (this 2398 // implies we reply on the default query parameters handler) 2399 getChassis(asyncResp, chassisId, sensors::sensorsNodeStr, dbus::sensorPaths, 2400 std::bind_front(sensors::getChassisCallback, asyncResp, 2401 chassisId, sensors::sensorsNodeStr)); 2402 } 2403 2404 inline void getSensorFromDbus( 2405 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2406 const std::string& sensorPath, 2407 const ::dbus::utility::MapperGetObject& mapperResponse) 2408 { 2409 if (mapperResponse.size() != 1) 2410 { 2411 messages::internalError(asyncResp->res); 2412 return; 2413 } 2414 const auto& valueIface = *mapperResponse.begin(); 2415 const std::string& connectionName = valueIface.first; 2416 BMCWEB_LOG_DEBUG("Looking up {}", connectionName); 2417 BMCWEB_LOG_DEBUG("Path {}", sensorPath); 2418 2419 ::dbus::utility::getAllProperties( 2420 *crow::connections::systemBus, connectionName, sensorPath, "", 2421 [asyncResp, 2422 sensorPath](const boost::system::error_code& ec, 2423 const ::dbus::utility::DBusPropertiesMap& valuesDict) { 2424 if (ec) 2425 { 2426 messages::internalError(asyncResp->res); 2427 return; 2428 } 2429 sdbusplus::message::object_path path(sensorPath); 2430 std::string name = path.filename(); 2431 path = path.parent_path(); 2432 std::string type = path.filename(); 2433 sensor_utils::objectPropertiesToJson( 2434 name, type, sensor_utils::ChassisSubNode::sensorsNode, 2435 valuesDict, asyncResp->res.jsonValue, nullptr); 2436 }); 2437 } 2438 2439 inline void handleSensorGet(App& app, const crow::Request& req, 2440 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2441 const std::string& chassisId, 2442 const std::string& sensorId) 2443 { 2444 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2445 { 2446 return; 2447 } 2448 std::pair<std::string, std::string> nameType = 2449 redfish::sensor_utils::splitSensorNameAndType(sensorId); 2450 if (nameType.first.empty() || nameType.second.empty()) 2451 { 2452 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2453 return; 2454 } 2455 2456 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2457 "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId); 2458 2459 BMCWEB_LOG_DEBUG("Sensor doGet enter"); 2460 2461 constexpr std::array<std::string_view, 1> interfaces = { 2462 "xyz.openbmc_project.Sensor.Value"}; 2463 std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first + 2464 '/' + nameType.second; 2465 // Get a list of all of the sensors that implement Sensor.Value 2466 // and get the path and service name associated with the sensor 2467 ::dbus::utility::getDbusObject( 2468 sensorPath, interfaces, 2469 [asyncResp, sensorId, 2470 sensorPath](const boost::system::error_code& ec, 2471 const ::dbus::utility::MapperGetObject& subtree) { 2472 BMCWEB_LOG_DEBUG("respHandler1 enter"); 2473 if (ec == boost::system::errc::io_error) 2474 { 2475 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths"); 2476 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2477 return; 2478 } 2479 if (ec) 2480 { 2481 messages::internalError(asyncResp->res); 2482 BMCWEB_LOG_ERROR( 2483 "Sensor getSensorPaths resp_handler: Dbus error {}", ec); 2484 return; 2485 } 2486 getSensorFromDbus(asyncResp, sensorPath, subtree); 2487 BMCWEB_LOG_DEBUG("respHandler1 exit"); 2488 }); 2489 } 2490 2491 } // namespace sensors 2492 2493 inline void requestRoutesSensorCollection(App& app) 2494 { 2495 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2496 .privileges(redfish::privileges::getSensorCollection) 2497 .methods(boost::beast::http::verb::get)( 2498 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app))); 2499 } 2500 2501 inline void requestRoutesSensor(App& app) 2502 { 2503 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2504 .privileges(redfish::privileges::getSensor) 2505 .methods(boost::beast::http::verb::get)( 2506 std::bind_front(sensors::handleSensorGet, std::ref(app))); 2507 } 2508 2509 } // namespace redfish 2510