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)}, 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 static 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 static 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 static 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 static 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