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