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