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