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