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 dbus::utility::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 dbus::utility::getProperty<std::string>( 1379 ledConnection, ledPath, "xyz.openbmc_project.Led.Physical", "State", 1380 std::move(respHandler)); 1381 } 1382 1383 BMCWEB_LOG_DEBUG("getInventoryLedData exit"); 1384 } 1385 1386 /** 1387 * @brief Gets LED data for LEDs associated with given inventory items. 1388 * 1389 * Gets the D-Bus connections (services) that provide LED data for the LEDs 1390 * associated with the specified inventory items. Then gets the LED data from 1391 * each connection and stores it in the inventory item. 1392 * 1393 * This data is later used to provide sensor property values in the JSON 1394 * response. 1395 * 1396 * Finds the LED data asynchronously. Invokes callback when information has 1397 * been obtained. 1398 * 1399 * The callback must have the following signature: 1400 * @code 1401 * callback() 1402 * @endcode 1403 * 1404 * @param sensorsAsyncResp Pointer to object holding response data. 1405 * @param inventoryItems D-Bus inventory items associated with sensors. 1406 * @param callback Callback to invoke when inventory items have been obtained. 1407 */ 1408 template <typename Callback> 1409 void getInventoryLeds( 1410 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1411 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1412 Callback&& callback) 1413 { 1414 BMCWEB_LOG_DEBUG("getInventoryLeds enter"); 1415 1416 const std::string path = "/xyz/openbmc_project"; 1417 constexpr std::array<std::string_view, 1> interfaces = { 1418 "xyz.openbmc_project.Led.Physical"}; 1419 1420 // Make call to ObjectMapper to find all inventory items 1421 dbus::utility::getSubTree( 1422 path, 0, interfaces, 1423 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1424 inventoryItems]( 1425 const boost::system::error_code& ec, 1426 const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { 1427 // Response handler for parsing output from GetSubTree 1428 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter"); 1429 if (ec) 1430 { 1431 messages::internalError(sensorsAsyncResp->asyncResp->res); 1432 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}", 1433 ec); 1434 return; 1435 } 1436 1437 // Build map of LED object paths to connections 1438 std::shared_ptr<std::map<std::string, std::string>> ledConnections = 1439 std::make_shared<std::map<std::string, std::string>>(); 1440 1441 // Loop through objects from GetSubTree 1442 for (const std::pair<std::string, 1443 std::vector<std::pair< 1444 std::string, std::vector<std::string>>>>& 1445 object : subtree) 1446 { 1447 // Check if object path is LED for one of the specified 1448 // inventory items 1449 const std::string& ledPath = object.first; 1450 if (findInventoryItemForLed(*inventoryItems, ledPath) != 1451 nullptr) 1452 { 1453 // Add mapping from ledPath to connection 1454 const std::string& connection = 1455 object.second.begin()->first; 1456 (*ledConnections)[ledPath] = connection; 1457 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath, 1458 connection); 1459 } 1460 } 1461 1462 getInventoryLedData(sensorsAsyncResp, inventoryItems, 1463 ledConnections, std::move(callback)); 1464 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit"); 1465 }); 1466 BMCWEB_LOG_DEBUG("getInventoryLeds exit"); 1467 } 1468 1469 /** 1470 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 1471 * 1472 * Uses the specified connections (services) (currently assumes just one) to 1473 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 1474 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 1475 * 1476 * This data is later used to provide sensor property values in the JSON 1477 * response. 1478 * 1479 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1480 * when data has been obtained. 1481 * 1482 * The callback must have the following signature: 1483 * @code 1484 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1485 * @endcode 1486 * 1487 * @param sensorsAsyncResp Pointer to object holding response data. 1488 * @param inventoryItems D-Bus inventory items associated with sensors. 1489 * @param psAttributesConnections Connections that provide data for the Power 1490 * Supply Attributes 1491 * @param callback Callback to invoke when data has been obtained. 1492 */ 1493 template <typename Callback> 1494 void getPowerSupplyAttributesData( 1495 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1496 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1497 const std::map<std::string, std::string>& psAttributesConnections, 1498 Callback&& callback) 1499 { 1500 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter"); 1501 1502 if (psAttributesConnections.empty()) 1503 { 1504 BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!"); 1505 callback(inventoryItems); 1506 return; 1507 } 1508 1509 // Assuming just one connection (service) for now 1510 auto it = psAttributesConnections.begin(); 1511 1512 const std::string& psAttributesPath = (*it).first; 1513 const std::string& psAttributesConnection = (*it).second; 1514 1515 // Response handler for Get DeratingFactor property 1516 auto respHandler = [sensorsAsyncResp, inventoryItems, 1517 callback = std::forward<Callback>(callback)]( 1518 const boost::system::error_code& ec, 1519 uint32_t value) mutable { 1520 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter"); 1521 if (ec) 1522 { 1523 BMCWEB_LOG_ERROR( 1524 "getPowerSupplyAttributesData respHandler DBus error {}", ec); 1525 messages::internalError(sensorsAsyncResp->asyncResp->res); 1526 return; 1527 } 1528 1529 BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value); 1530 // Store value in Power Supply Inventory Items 1531 for (InventoryItem& inventoryItem : *inventoryItems) 1532 { 1533 if (inventoryItem.isPowerSupply) 1534 { 1535 inventoryItem.powerSupplyEfficiencyPercent = 1536 static_cast<int>(value); 1537 } 1538 } 1539 1540 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit"); 1541 callback(inventoryItems); 1542 }; 1543 1544 // Get the DeratingFactor property for the PowerSupplyAttributes 1545 // Currently only property on the interface/only one we care about 1546 dbus::utility::getProperty<uint32_t>( 1547 psAttributesConnection, psAttributesPath, 1548 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 1549 std::move(respHandler)); 1550 1551 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit"); 1552 } 1553 1554 /** 1555 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 1556 * 1557 * Gets the D-Bus connection (service) that provides Power Supply Attributes 1558 * data. Then gets the Power Supply Attributes data from the connection 1559 * (currently just assumes 1 connection) and stores the data in the inventory 1560 * item. 1561 * 1562 * This data is later used to provide sensor property values in the JSON 1563 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 1564 * 1565 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1566 * when information has been obtained. 1567 * 1568 * The callback must have the following signature: 1569 * @code 1570 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1571 * @endcode 1572 * 1573 * @param sensorsAsyncResp Pointer to object holding response data. 1574 * @param inventoryItems D-Bus inventory items associated with sensors. 1575 * @param callback Callback to invoke when data has been obtained. 1576 */ 1577 template <typename Callback> 1578 void getPowerSupplyAttributes( 1579 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1580 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1581 Callback&& callback) 1582 { 1583 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter"); 1584 1585 // Only need the power supply attributes when the Power Schema 1586 if (sensorsAsyncResp->chassisSubNode != sensors::powerNodeStr) 1587 { 1588 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power"); 1589 callback(inventoryItems); 1590 return; 1591 } 1592 1593 constexpr std::array<std::string_view, 1> interfaces = { 1594 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 1595 1596 // Make call to ObjectMapper to find the PowerSupplyAttributes service 1597 dbus::utility::getSubTree( 1598 "/xyz/openbmc_project", 0, interfaces, 1599 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1600 inventoryItems]( 1601 const boost::system::error_code& ec, 1602 const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { 1603 // Response handler for parsing output from GetSubTree 1604 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter"); 1605 if (ec) 1606 { 1607 messages::internalError(sensorsAsyncResp->asyncResp->res); 1608 BMCWEB_LOG_ERROR( 1609 "getPowerSupplyAttributes respHandler DBus error {}", ec); 1610 return; 1611 } 1612 if (subtree.empty()) 1613 { 1614 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!"); 1615 callback(inventoryItems); 1616 return; 1617 } 1618 1619 // Currently we only support 1 power supply attribute, use this for 1620 // all the power supplies. Build map of object path to connection. 1621 // Assume just 1 connection and 1 path for now. 1622 std::map<std::string, std::string> psAttributesConnections; 1623 1624 if (subtree[0].first.empty() || subtree[0].second.empty()) 1625 { 1626 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 1627 callback(inventoryItems); 1628 return; 1629 } 1630 1631 const std::string& psAttributesPath = subtree[0].first; 1632 const std::string& connection = subtree[0].second.begin()->first; 1633 1634 if (connection.empty()) 1635 { 1636 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 1637 callback(inventoryItems); 1638 return; 1639 } 1640 1641 psAttributesConnections[psAttributesPath] = connection; 1642 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath, 1643 connection); 1644 1645 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 1646 psAttributesConnections, 1647 std::move(callback)); 1648 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit"); 1649 }); 1650 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit"); 1651 } 1652 1653 /** 1654 * @brief Gets inventory items associated with sensors. 1655 * 1656 * Finds the inventory items that are associated with the specified sensors. 1657 * Then gets D-Bus data for the inventory items, such as presence and VPD. 1658 * 1659 * This data is later used to provide sensor property values in the JSON 1660 * response. 1661 * 1662 * Finds the inventory items asynchronously. Invokes callback when the 1663 * inventory items have been obtained. 1664 * 1665 * The callback must have the following signature: 1666 * @code 1667 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1668 * @endcode 1669 * 1670 * @param sensorsAsyncResp Pointer to object holding response data. 1671 * @param sensorNames All sensors within the current chassis. 1672 * implements ObjectManager. 1673 * @param callback Callback to invoke when inventory items have been obtained. 1674 */ 1675 template <typename Callback> 1676 inline void 1677 getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1678 const std::shared_ptr<std::set<std::string>> sensorNames, 1679 Callback&& callback) 1680 { 1681 BMCWEB_LOG_DEBUG("getInventoryItems enter"); 1682 auto getInventoryItemAssociationsCb = 1683 [sensorsAsyncResp, callback = std::forward<Callback>(callback)]( 1684 std::shared_ptr<std::vector<InventoryItem>> 1685 inventoryItems) mutable { 1686 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter"); 1687 auto getInventoryItemsConnectionsCb = 1688 [sensorsAsyncResp, inventoryItems, 1689 callback = std::forward<Callback>(callback)]( 1690 std::shared_ptr<std::set<std::string>> 1691 invConnections) mutable { 1692 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter"); 1693 auto getInventoryItemsDataCb = 1694 [sensorsAsyncResp, inventoryItems, 1695 callback = 1696 std::forward<Callback>(callback)]() mutable { 1697 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter"); 1698 1699 auto getInventoryLedsCb = 1700 [sensorsAsyncResp, inventoryItems, 1701 callback = std::forward<Callback>( 1702 callback)]() mutable { 1703 BMCWEB_LOG_DEBUG( 1704 "getInventoryLedsCb enter"); 1705 // Find Power Supply Attributes and get the 1706 // data 1707 getPowerSupplyAttributes( 1708 sensorsAsyncResp, inventoryItems, 1709 std::move(callback)); 1710 BMCWEB_LOG_DEBUG("getInventoryLedsCb exit"); 1711 }; 1712 1713 // Find led connections and get the data 1714 getInventoryLeds(sensorsAsyncResp, inventoryItems, 1715 std::move(getInventoryLedsCb)); 1716 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit"); 1717 }; 1718 1719 // Get inventory item data from connections 1720 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1721 invConnections, 1722 std::move(getInventoryItemsDataCb)); 1723 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit"); 1724 }; 1725 1726 // Get connections that provide inventory item data 1727 getInventoryItemsConnections( 1728 sensorsAsyncResp, inventoryItems, 1729 std::move(getInventoryItemsConnectionsCb)); 1730 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit"); 1731 }; 1732 1733 // Get associations from sensors to inventory items 1734 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, 1735 std::move(getInventoryItemAssociationsCb)); 1736 BMCWEB_LOG_DEBUG("getInventoryItems exit"); 1737 } 1738 1739 /** 1740 * @brief Returns JSON PowerSupply object for the specified inventory item. 1741 * 1742 * Searches for a JSON PowerSupply object that matches the specified inventory 1743 * item. If one is not found, a new PowerSupply object is added to the JSON 1744 * array. 1745 * 1746 * Multiple sensors are often associated with one power supply inventory item. 1747 * As a result, multiple sensor values are stored in one JSON PowerSupply 1748 * object. 1749 * 1750 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 1751 * @param inventoryItem Inventory item for the power supply. 1752 * @param chassisId Chassis that contains the power supply. 1753 * @return JSON PowerSupply object for the specified inventory item. 1754 */ 1755 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 1756 const InventoryItem& inventoryItem, 1757 const std::string& chassisId) 1758 { 1759 std::string nameS; 1760 nameS.resize(inventoryItem.name.size()); 1761 std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' '); 1762 // Check if matching PowerSupply object already exists in JSON array 1763 for (nlohmann::json& powerSupply : powerSupplyArray) 1764 { 1765 nlohmann::json::iterator nameIt = powerSupply.find("Name"); 1766 if (nameIt == powerSupply.end()) 1767 { 1768 continue; 1769 } 1770 const std::string* name = nameIt->get_ptr<std::string*>(); 1771 if (name == nullptr) 1772 { 1773 continue; 1774 } 1775 if (nameS == *name) 1776 { 1777 return powerSupply; 1778 } 1779 } 1780 1781 // Add new PowerSupply object to JSON array 1782 powerSupplyArray.push_back({}); 1783 nlohmann::json& powerSupply = powerSupplyArray.back(); 1784 boost::urls::url url = 1785 boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId); 1786 url.set_fragment(("/PowerSupplies"_json_pointer).to_string()); 1787 powerSupply["@odata.id"] = std::move(url); 1788 std::string escaped; 1789 escaped.resize(inventoryItem.name.size()); 1790 std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' '); 1791 powerSupply["Name"] = std::move(escaped); 1792 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 1793 powerSupply["Model"] = inventoryItem.model; 1794 powerSupply["PartNumber"] = inventoryItem.partNumber; 1795 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 1796 sensor_utils::setLedState(powerSupply, &inventoryItem); 1797 1798 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 1799 { 1800 powerSupply["EfficiencyPercent"] = 1801 inventoryItem.powerSupplyEfficiencyPercent; 1802 } 1803 1804 powerSupply["Status"]["State"] = 1805 sensor_utils::getState(&inventoryItem, true); 1806 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 1807 powerSupply["Status"]["Health"] = health; 1808 1809 return powerSupply; 1810 } 1811 1812 /** 1813 * @brief Gets the values of the specified sensors. 1814 * 1815 * Stores the results as JSON in the SensorsAsyncResp. 1816 * 1817 * Gets the sensor values asynchronously. Stores the results later when the 1818 * information has been obtained. 1819 * 1820 * The sensorNames set contains all requested sensors for the current chassis. 1821 * 1822 * To minimize the number of DBus calls, the DBus method 1823 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 1824 * values of all sensors provided by a connection (service). 1825 * 1826 * The connections set contains all the connections that provide sensor values. 1827 * 1828 * The InventoryItem vector contains D-Bus inventory items associated with the 1829 * sensors. Inventory item data is needed for some Redfish sensor properties. 1830 * 1831 * @param SensorsAsyncResp Pointer to object holding response data. 1832 * @param sensorNames All requested sensors within the current chassis. 1833 * @param connections Connections that provide sensor values. 1834 * implements ObjectManager. 1835 * @param inventoryItems Inventory items associated with the sensors. 1836 */ 1837 inline void getSensorData( 1838 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1839 const std::shared_ptr<std::set<std::string>>& sensorNames, 1840 const std::set<std::string>& connections, 1841 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 1842 { 1843 BMCWEB_LOG_DEBUG("getSensorData enter"); 1844 // Get managed objects from all services exposing sensors 1845 for (const std::string& connection : connections) 1846 { 1847 sdbusplus::message::object_path sensorPath( 1848 "/xyz/openbmc_project/sensors"); 1849 dbus::utility::getManagedObjects( 1850 connection, sensorPath, 1851 [sensorsAsyncResp, sensorNames, 1852 inventoryItems](const boost::system::error_code& ec, 1853 const dbus::utility::ManagedObjectType& resp) { 1854 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter"); 1855 if (ec) 1856 { 1857 BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec); 1858 messages::internalError(sensorsAsyncResp->asyncResp->res); 1859 return; 1860 } 1861 auto chassisSubNode = sensor_utils::chassisSubNodeFromString( 1862 sensorsAsyncResp->chassisSubNode); 1863 // Go through all objects and update response with sensor data 1864 for (const auto& objDictEntry : resp) 1865 { 1866 const std::string& objPath = 1867 static_cast<const std::string&>(objDictEntry.first); 1868 BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}", 1869 objPath); 1870 1871 std::vector<std::string> split; 1872 // Reserve space for 1873 // /xyz/openbmc_project/sensors/<name>/<subname> 1874 split.reserve(6); 1875 // NOLINTNEXTLINE 1876 bmcweb::split(split, objPath, '/'); 1877 if (split.size() < 6) 1878 { 1879 BMCWEB_LOG_ERROR("Got path that isn't long enough {}", 1880 objPath); 1881 continue; 1882 } 1883 // These indexes aren't intuitive, as split puts an empty 1884 // string at the beginning 1885 const std::string& sensorType = split[4]; 1886 const std::string& sensorName = split[5]; 1887 BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName, 1888 sensorType); 1889 if (sensorNames->find(objPath) == sensorNames->end()) 1890 { 1891 BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName); 1892 continue; 1893 } 1894 1895 // Find inventory item (if any) associated with sensor 1896 InventoryItem* inventoryItem = 1897 findInventoryItemForSensor(inventoryItems, objPath); 1898 1899 const std::string& sensorSchema = 1900 sensorsAsyncResp->chassisSubNode; 1901 1902 nlohmann::json* sensorJson = nullptr; 1903 1904 if (sensorSchema == sensors::sensorsNodeStr && 1905 !sensorsAsyncResp->efficientExpand) 1906 { 1907 std::string sensorId = 1908 redfish::sensor_utils::getSensorId(sensorName, 1909 sensorType); 1910 1911 sensorsAsyncResp->asyncResp->res 1912 .jsonValue["@odata.id"] = boost::urls::format( 1913 "/redfish/v1/Chassis/{}/{}/{}", 1914 sensorsAsyncResp->chassisId, 1915 sensorsAsyncResp->chassisSubNode, sensorId); 1916 sensorJson = 1917 &(sensorsAsyncResp->asyncResp->res.jsonValue); 1918 } 1919 else 1920 { 1921 std::string fieldName; 1922 if (sensorsAsyncResp->efficientExpand) 1923 { 1924 fieldName = "Members"; 1925 } 1926 else if (sensorType == "temperature") 1927 { 1928 fieldName = "Temperatures"; 1929 } 1930 else if (sensorType == "fan" || 1931 sensorType == "fan_tach" || 1932 sensorType == "fan_pwm") 1933 { 1934 fieldName = "Fans"; 1935 } 1936 else if (sensorType == "voltage") 1937 { 1938 fieldName = "Voltages"; 1939 } 1940 else if (sensorType == "power") 1941 { 1942 if (sensorName == "total_power") 1943 { 1944 fieldName = "PowerControl"; 1945 } 1946 else if ((inventoryItem != nullptr) && 1947 (inventoryItem->isPowerSupply)) 1948 { 1949 fieldName = "PowerSupplies"; 1950 } 1951 else 1952 { 1953 // Other power sensors are in SensorCollection 1954 continue; 1955 } 1956 } 1957 else 1958 { 1959 BMCWEB_LOG_ERROR( 1960 "Unsure how to handle sensorType {}", 1961 sensorType); 1962 continue; 1963 } 1964 1965 nlohmann::json& tempArray = 1966 sensorsAsyncResp->asyncResp->res 1967 .jsonValue[fieldName]; 1968 if (fieldName == "PowerControl") 1969 { 1970 if (tempArray.empty()) 1971 { 1972 // Put multiple "sensors" into a single 1973 // PowerControl. Follows MemberId naming and 1974 // naming in power.hpp. 1975 nlohmann::json::object_t power; 1976 boost::urls::url url = boost::urls::format( 1977 "/redfish/v1/Chassis/{}/{}", 1978 sensorsAsyncResp->chassisId, 1979 sensorsAsyncResp->chassisSubNode); 1980 url.set_fragment( 1981 (""_json_pointer / fieldName / "0") 1982 .to_string()); 1983 power["@odata.id"] = std::move(url); 1984 tempArray.emplace_back(std::move(power)); 1985 } 1986 sensorJson = &(tempArray.back()); 1987 } 1988 else if (fieldName == "PowerSupplies") 1989 { 1990 if (inventoryItem != nullptr) 1991 { 1992 sensorJson = &(getPowerSupply( 1993 tempArray, *inventoryItem, 1994 sensorsAsyncResp->chassisId)); 1995 } 1996 } 1997 else if (fieldName == "Members") 1998 { 1999 std::string sensorId = 2000 redfish::sensor_utils::getSensorId(sensorName, 2001 sensorType); 2002 2003 nlohmann::json::object_t member; 2004 member["@odata.id"] = boost::urls::format( 2005 "/redfish/v1/Chassis/{}/{}/{}", 2006 sensorsAsyncResp->chassisId, 2007 sensorsAsyncResp->chassisSubNode, sensorId); 2008 tempArray.emplace_back(std::move(member)); 2009 sensorJson = &(tempArray.back()); 2010 } 2011 else 2012 { 2013 nlohmann::json::object_t member; 2014 boost::urls::url url = boost::urls::format( 2015 "/redfish/v1/Chassis/{}/{}", 2016 sensorsAsyncResp->chassisId, 2017 sensorsAsyncResp->chassisSubNode); 2018 url.set_fragment( 2019 (""_json_pointer / fieldName).to_string()); 2020 member["@odata.id"] = std::move(url); 2021 tempArray.emplace_back(std::move(member)); 2022 sensorJson = &(tempArray.back()); 2023 } 2024 } 2025 2026 if (sensorJson != nullptr) 2027 { 2028 objectInterfacesToJson( 2029 sensorName, sensorType, chassisSubNode, 2030 objDictEntry.second, *sensorJson, inventoryItem); 2031 2032 std::string path = "/xyz/openbmc_project/sensors/"; 2033 path += sensorType; 2034 path += "/"; 2035 path += sensorName; 2036 sensorsAsyncResp->addMetadata(*sensorJson, path); 2037 } 2038 } 2039 if (sensorsAsyncResp.use_count() == 1) 2040 { 2041 sortJSONResponse(sensorsAsyncResp); 2042 if (chassisSubNode == 2043 sensor_utils::ChassisSubNode::sensorsNode && 2044 sensorsAsyncResp->efficientExpand) 2045 { 2046 sensorsAsyncResp->asyncResp->res 2047 .jsonValue["Members@odata.count"] = 2048 sensorsAsyncResp->asyncResp->res 2049 .jsonValue["Members"] 2050 .size(); 2051 } 2052 else if (chassisSubNode == 2053 sensor_utils::ChassisSubNode::thermalNode) 2054 { 2055 populateFanRedundancy(sensorsAsyncResp); 2056 } 2057 } 2058 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit"); 2059 }); 2060 } 2061 BMCWEB_LOG_DEBUG("getSensorData exit"); 2062 } 2063 2064 inline void 2065 processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2066 const std::shared_ptr<std::set<std::string>>& sensorNames) 2067 { 2068 auto getConnectionCb = [sensorsAsyncResp, sensorNames]( 2069 const std::set<std::string>& connections) { 2070 BMCWEB_LOG_DEBUG("getConnectionCb enter"); 2071 auto getInventoryItemsCb = 2072 [sensorsAsyncResp, sensorNames, connections]( 2073 const std::shared_ptr<std::vector<InventoryItem>>& 2074 inventoryItems) mutable { 2075 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter"); 2076 // Get sensor data and store results in JSON 2077 getSensorData(sensorsAsyncResp, sensorNames, connections, 2078 inventoryItems); 2079 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit"); 2080 }; 2081 2082 // Get inventory items associated with sensors 2083 getInventoryItems(sensorsAsyncResp, sensorNames, 2084 std::move(getInventoryItemsCb)); 2085 2086 BMCWEB_LOG_DEBUG("getConnectionCb exit"); 2087 }; 2088 2089 // Get set of connections that provide sensor values 2090 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2091 } 2092 2093 /** 2094 * @brief Entry point for retrieving sensors data related to requested 2095 * chassis. 2096 * @param SensorsAsyncResp Pointer to object holding response data 2097 */ 2098 inline void 2099 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2100 { 2101 BMCWEB_LOG_DEBUG("getChassisData enter"); 2102 auto getChassisCb = 2103 [sensorsAsyncResp]( 2104 const std::shared_ptr<std::set<std::string>>& sensorNames) { 2105 BMCWEB_LOG_DEBUG("getChassisCb enter"); 2106 processSensorList(sensorsAsyncResp, sensorNames); 2107 BMCWEB_LOG_DEBUG("getChassisCb exit"); 2108 }; 2109 // SensorCollection doesn't contain the Redundancy property 2110 if (sensorsAsyncResp->chassisSubNode != sensors::sensorsNodeStr) 2111 { 2112 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2113 nlohmann::json::array(); 2114 } 2115 // Get set of sensors in chassis 2116 getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId, 2117 sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types, 2118 std::move(getChassisCb)); 2119 BMCWEB_LOG_DEBUG("getChassisData exit"); 2120 } 2121 2122 /** 2123 * @brief Find the requested sensorName in the list of all sensors supplied by 2124 * the chassis node 2125 * 2126 * @param sensorName The sensor name supplied in the PATCH request 2127 * @param sensorsList The list of sensors managed by the chassis node 2128 * @param sensorsModified The list of sensors that were found as a result of 2129 * repeated calls to this function 2130 */ 2131 inline bool findSensorNameUsingSensorPath( 2132 std::string_view sensorName, const std::set<std::string>& sensorsList, 2133 std::set<std::string>& sensorsModified) 2134 { 2135 for (const auto& chassisSensor : sensorsList) 2136 { 2137 sdbusplus::message::object_path path(chassisSensor); 2138 std::string thisSensorName = path.filename(); 2139 if (thisSensorName.empty()) 2140 { 2141 continue; 2142 } 2143 if (thisSensorName == sensorName) 2144 { 2145 sensorsModified.emplace(chassisSensor); 2146 return true; 2147 } 2148 } 2149 return false; 2150 } 2151 2152 /** 2153 * @brief Entry point for overriding sensor values of given sensor 2154 * 2155 * @param sensorAsyncResp response object 2156 * @param allCollections Collections extract from sensors' request patch info 2157 * @param chassisSubNode Chassis Node for which the query has to happen 2158 */ 2159 inline void setSensorsOverride( 2160 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2161 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>& 2162 allCollections) 2163 { 2164 BMCWEB_LOG_INFO("setSensorsOverride for subNode{}", 2165 sensorAsyncResp->chassisSubNode); 2166 2167 std::string_view propertyValueName; 2168 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2169 std::string memberId; 2170 double value = 0.0; 2171 for (auto& collectionItems : allCollections) 2172 { 2173 if (collectionItems.first == "Temperatures") 2174 { 2175 propertyValueName = "ReadingCelsius"; 2176 } 2177 else if (collectionItems.first == "Fans") 2178 { 2179 propertyValueName = "Reading"; 2180 } 2181 else 2182 { 2183 propertyValueName = "ReadingVolts"; 2184 } 2185 for (auto& item : collectionItems.second) 2186 { 2187 if (!json_util::readJsonObject( // 2188 item, sensorAsyncResp->asyncResp->res, // 2189 "MemberId", memberId, // 2190 propertyValueName, value // 2191 )) 2192 { 2193 return; 2194 } 2195 overrideMap.emplace(memberId, 2196 std::make_pair(value, collectionItems.first)); 2197 } 2198 } 2199 2200 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap, 2201 propertyValueNameStr = 2202 std::string(propertyValueName)]( 2203 const std::shared_ptr< 2204 std::set<std::string>>& sensorsList) { 2205 // Match sensor names in the PATCH request to those managed by the 2206 // chassis node 2207 const std::shared_ptr<std::set<std::string>> sensorNames = 2208 std::make_shared<std::set<std::string>>(); 2209 for (const auto& item : overrideMap) 2210 { 2211 const auto& sensor = item.first; 2212 std::pair<std::string, std::string> sensorNameType = 2213 redfish::sensor_utils::splitSensorNameAndType(sensor); 2214 if (!findSensorNameUsingSensorPath(sensorNameType.second, 2215 *sensorsList, *sensorNames)) 2216 { 2217 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first); 2218 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2219 item.second.second, item.first); 2220 return; 2221 } 2222 } 2223 // Get the connection to which the memberId belongs 2224 auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap, 2225 propertyValueNameStr]( 2226 const std::set< 2227 std::string>& /*connections*/, 2228 const std::set<std::pair< 2229 std::string, std::string>>& 2230 objectsWithConnection) { 2231 if (objectsWithConnection.size() != overrideMap.size()) 2232 { 2233 BMCWEB_LOG_INFO( 2234 "Unable to find all objects with proper connection {} requested {}", 2235 objectsWithConnection.size(), overrideMap.size()); 2236 messages::resourceNotFound( 2237 sensorAsyncResp->asyncResp->res, 2238 sensorAsyncResp->chassisSubNode == sensors::thermalNodeStr 2239 ? "Temperatures" 2240 : "Voltages", 2241 "Count"); 2242 return; 2243 } 2244 for (const auto& item : objectsWithConnection) 2245 { 2246 sdbusplus::message::object_path path(item.first); 2247 std::string sensorName = path.filename(); 2248 if (sensorName.empty()) 2249 { 2250 messages::internalError(sensorAsyncResp->asyncResp->res); 2251 return; 2252 } 2253 std::string id = redfish::sensor_utils::getSensorId( 2254 sensorName, path.parent_path().filename()); 2255 2256 const auto& iterator = overrideMap.find(id); 2257 if (iterator == overrideMap.end()) 2258 { 2259 BMCWEB_LOG_INFO("Unable to find sensor object{}", 2260 item.first); 2261 messages::internalError(sensorAsyncResp->asyncResp->res); 2262 return; 2263 } 2264 setDbusProperty(sensorAsyncResp->asyncResp, 2265 propertyValueNameStr, item.second, item.first, 2266 "xyz.openbmc_project.Sensor.Value", "Value", 2267 iterator->second.first); 2268 } 2269 }; 2270 // Get object with connection for the given sensor name 2271 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2272 std::move(getObjectsWithConnectionCb)); 2273 }; 2274 // get full sensor list for the given chassisId and cross verify the sensor. 2275 getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 2276 sensorAsyncResp->chassisSubNode, sensorAsyncResp->types, 2277 std::move(getChassisSensorListCb)); 2278 } 2279 2280 /** 2281 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2282 * path of the sensor. 2283 * 2284 * Function builds valid Redfish response for sensor query of given chassis and 2285 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2286 * it to caller in a callback. 2287 * 2288 * @param chassis Chassis for which retrieval should be performed 2289 * @param node Node (group) of sensors. See sensor_utils::node for supported 2290 * values 2291 * @param mapComplete Callback to be called with retrieval result 2292 */ 2293 template <typename Callback> 2294 inline void retrieveUriToDbusMap( 2295 const std::string& chassis, const std::string& node, Callback&& mapComplete) 2296 { 2297 decltype(sensors::paths)::const_iterator pathIt = 2298 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2299 [&node](auto&& val) { return val.first == node; }); 2300 if (pathIt == sensors::paths.cend()) 2301 { 2302 BMCWEB_LOG_ERROR("Wrong node provided : {}", node); 2303 std::map<std::string, std::string> noop; 2304 mapComplete(boost::beast::http::status::bad_request, noop); 2305 return; 2306 } 2307 2308 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2309 auto callback = 2310 [asyncResp, mapCompleteCb = std::forward<Callback>(mapComplete)]( 2311 const boost::beast::http::status status, 2312 const std::map<std::string, std::string>& uriToDbus) { 2313 mapCompleteCb(status, uriToDbus); 2314 }; 2315 2316 auto resp = std::make_shared<SensorsAsyncResp>( 2317 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2318 getChassisData(resp); 2319 } 2320 2321 namespace sensors 2322 { 2323 2324 inline void getChassisCallback( 2325 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2326 std::string_view chassisId, std::string_view chassisSubNode, 2327 const std::shared_ptr<std::set<std::string>>& sensorNames) 2328 { 2329 BMCWEB_LOG_DEBUG("getChassisCallback enter "); 2330 2331 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; 2332 for (const std::string& sensor : *sensorNames) 2333 { 2334 BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor); 2335 2336 sdbusplus::message::object_path path(sensor); 2337 std::string sensorName = path.filename(); 2338 if (sensorName.empty()) 2339 { 2340 BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor); 2341 messages::internalError(asyncResp->res); 2342 return; 2343 } 2344 std::string type = path.parent_path().filename(); 2345 std::string id = redfish::sensor_utils::getSensorId(sensorName, type); 2346 2347 nlohmann::json::object_t member; 2348 member["@odata.id"] = boost::urls::format( 2349 "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id); 2350 2351 entriesArray.emplace_back(std::move(member)); 2352 } 2353 2354 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); 2355 BMCWEB_LOG_DEBUG("getChassisCallback exit"); 2356 } 2357 2358 inline void handleSensorCollectionGet( 2359 App& app, const crow::Request& req, 2360 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2361 const std::string& chassisId) 2362 { 2363 query_param::QueryCapabilities capabilities = { 2364 .canDelegateExpandLevel = 1, 2365 }; 2366 query_param::Query delegatedQuery; 2367 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 2368 delegatedQuery, capabilities)) 2369 { 2370 return; 2371 } 2372 2373 if (delegatedQuery.expandType != query_param::ExpandType::None) 2374 { 2375 // we perform efficient expand. 2376 auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>( 2377 asyncResp, chassisId, sensors::dbus::sensorPaths, 2378 sensors::sensorsNodeStr, 2379 /*efficientExpand=*/true); 2380 getChassisData(sensorsAsyncResp); 2381 2382 BMCWEB_LOG_DEBUG( 2383 "SensorCollection doGet exit via efficient expand handler"); 2384 return; 2385 } 2386 2387 // We get all sensors as hyperlinkes in the chassis (this 2388 // implies we reply on the default query parameters handler) 2389 getChassis(asyncResp, chassisId, sensors::sensorsNodeStr, dbus::sensorPaths, 2390 std::bind_front(sensors::getChassisCallback, asyncResp, 2391 chassisId, sensors::sensorsNodeStr)); 2392 } 2393 2394 inline void 2395 getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2396 const std::string& sensorPath, 2397 const ::dbus::utility::MapperGetObject& mapperResponse) 2398 { 2399 if (mapperResponse.size() != 1) 2400 { 2401 messages::internalError(asyncResp->res); 2402 return; 2403 } 2404 const auto& valueIface = *mapperResponse.begin(); 2405 const std::string& connectionName = valueIface.first; 2406 BMCWEB_LOG_DEBUG("Looking up {}", connectionName); 2407 BMCWEB_LOG_DEBUG("Path {}", sensorPath); 2408 2409 ::dbus::utility::getAllProperties( 2410 *crow::connections::systemBus, connectionName, sensorPath, "", 2411 [asyncResp, 2412 sensorPath](const boost::system::error_code& ec, 2413 const ::dbus::utility::DBusPropertiesMap& valuesDict) { 2414 if (ec) 2415 { 2416 messages::internalError(asyncResp->res); 2417 return; 2418 } 2419 sdbusplus::message::object_path path(sensorPath); 2420 std::string name = path.filename(); 2421 path = path.parent_path(); 2422 std::string type = path.filename(); 2423 sensor_utils::objectPropertiesToJson( 2424 name, type, sensor_utils::ChassisSubNode::sensorsNode, 2425 valuesDict, asyncResp->res.jsonValue, nullptr); 2426 }); 2427 } 2428 2429 inline void handleSensorGet(App& app, const crow::Request& req, 2430 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2431 const std::string& chassisId, 2432 const std::string& sensorId) 2433 { 2434 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2435 { 2436 return; 2437 } 2438 std::pair<std::string, std::string> nameType = 2439 redfish::sensor_utils::splitSensorNameAndType(sensorId); 2440 if (nameType.first.empty() || nameType.second.empty()) 2441 { 2442 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2443 return; 2444 } 2445 2446 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2447 "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId); 2448 2449 BMCWEB_LOG_DEBUG("Sensor doGet enter"); 2450 2451 constexpr std::array<std::string_view, 1> interfaces = { 2452 "xyz.openbmc_project.Sensor.Value"}; 2453 std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first + 2454 '/' + nameType.second; 2455 // Get a list of all of the sensors that implement Sensor.Value 2456 // and get the path and service name associated with the sensor 2457 ::dbus::utility::getDbusObject( 2458 sensorPath, interfaces, 2459 [asyncResp, sensorId, 2460 sensorPath](const boost::system::error_code& ec, 2461 const ::dbus::utility::MapperGetObject& subtree) { 2462 BMCWEB_LOG_DEBUG("respHandler1 enter"); 2463 if (ec == boost::system::errc::io_error) 2464 { 2465 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths"); 2466 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2467 return; 2468 } 2469 if (ec) 2470 { 2471 messages::internalError(asyncResp->res); 2472 BMCWEB_LOG_ERROR( 2473 "Sensor getSensorPaths resp_handler: Dbus error {}", ec); 2474 return; 2475 } 2476 getSensorFromDbus(asyncResp, sensorPath, subtree); 2477 BMCWEB_LOG_DEBUG("respHandler1 exit"); 2478 }); 2479 } 2480 2481 } // namespace sensors 2482 2483 inline void requestRoutesSensorCollection(App& app) 2484 { 2485 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2486 .privileges(redfish::privileges::getSensorCollection) 2487 .methods(boost::beast::http::verb::get)( 2488 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app))); 2489 } 2490 2491 inline void requestRoutesSensor(App& app) 2492 { 2493 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2494 .privileges(redfish::privileges::getSensor) 2495 .methods(boost::beast::http::verb::get)( 2496 std::bind_front(sensors::handleSensorGet, std::ref(app))); 2497 } 2498 2499 } // namespace redfish 2500