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