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 <boost/algorithm/string/predicate.hpp> 20 #include <boost/algorithm/string/split.hpp> 21 #include <boost/container/flat_map.hpp> 22 #include <boost/range/algorithm/replace_copy_if.hpp> 23 #include <dbus_singleton.hpp> 24 #include <dbus_utility.hpp> 25 #include <registries/privilege_registry.hpp> 26 #include <sdbusplus/asio/property.hpp> 27 #include <utils/json_utils.hpp> 28 29 #include <cmath> 30 #include <utility> 31 #include <variant> 32 33 namespace redfish 34 { 35 36 using GetSubTreeType = std::vector< 37 std::pair<std::string, 38 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 39 40 using ManagedObjectsVectorType = std::vector<std::pair< 41 sdbusplus::message::object_path, 42 boost::container::flat_map< 43 std::string, boost::container::flat_map< 44 std::string, dbus::utility::DbusVariantType>>>>; 45 46 namespace sensors 47 { 48 namespace node 49 { 50 static constexpr std::string_view power = "Power"; 51 static constexpr std::string_view sensors = "Sensors"; 52 static constexpr std::string_view thermal = "Thermal"; 53 } // namespace node 54 55 namespace dbus 56 { 57 58 static const boost::container::flat_map<std::string_view, 59 std::vector<const char*>> 60 paths = {{node::power, 61 {"/xyz/openbmc_project/sensors/voltage", 62 "/xyz/openbmc_project/sensors/power"}}, 63 {node::sensors, 64 {"/xyz/openbmc_project/sensors/power", 65 "/xyz/openbmc_project/sensors/current", 66 "/xyz/openbmc_project/sensors/airflow", 67 #ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM 68 "/xyz/openbmc_project/sensors/voltage", 69 "/xyz/openbmc_project/sensors/fan_tach", 70 "/xyz/openbmc_project/sensors/temperature", 71 "/xyz/openbmc_project/sensors/fan_pwm", 72 "/xyz/openbmc_project/sensors/altitude", 73 "/xyz/openbmc_project/sensors/energy", 74 #endif 75 "/xyz/openbmc_project/sensors/utilization"}}, 76 {node::thermal, 77 {"/xyz/openbmc_project/sensors/fan_tach", 78 "/xyz/openbmc_project/sensors/temperature", 79 "/xyz/openbmc_project/sensors/fan_pwm"}}}; 80 } // namespace dbus 81 82 inline const char* toReadingType(const std::string& sensorType) 83 { 84 if (sensorType == "voltage") 85 { 86 return "Voltage"; 87 } 88 if (sensorType == "power") 89 { 90 return "Power"; 91 } 92 if (sensorType == "current") 93 { 94 return "Current"; 95 } 96 if (sensorType == "fan_tach") 97 { 98 return "Rotational"; 99 } 100 if (sensorType == "temperature") 101 { 102 return "Temperature"; 103 } 104 if (sensorType == "fan_pwm" || sensorType == "utilization") 105 { 106 return "Percent"; 107 } 108 if (sensorType == "altitude") 109 { 110 return "Altitude"; 111 } 112 if (sensorType == "airflow") 113 { 114 return "AirFlow"; 115 } 116 if (sensorType == "energy") 117 { 118 return "EnergyJoules"; 119 } 120 return ""; 121 } 122 123 inline const char* toReadingUnits(const std::string& sensorType) 124 { 125 if (sensorType == "voltage") 126 { 127 return "V"; 128 } 129 if (sensorType == "power") 130 { 131 return "W"; 132 } 133 if (sensorType == "current") 134 { 135 return "A"; 136 } 137 if (sensorType == "fan_tach") 138 { 139 return "RPM"; 140 } 141 if (sensorType == "temperature") 142 { 143 return "Cel"; 144 } 145 if (sensorType == "fan_pwm" || sensorType == "utilization") 146 { 147 return "%"; 148 } 149 if (sensorType == "altitude") 150 { 151 return "m"; 152 } 153 if (sensorType == "airflow") 154 { 155 return "cft_i/min"; 156 } 157 if (sensorType == "energy") 158 { 159 return "J"; 160 } 161 return ""; 162 } 163 } // namespace sensors 164 165 /** 166 * SensorsAsyncResp 167 * Gathers data needed for response processing after async calls are done 168 */ 169 class SensorsAsyncResp 170 { 171 public: 172 using DataCompleteCb = std::function<void( 173 const boost::beast::http::status status, 174 const boost::container::flat_map<std::string, std::string>& uriToDbus)>; 175 176 struct SensorData 177 { 178 const std::string name; 179 std::string uri; 180 const std::string valueKey; 181 const std::string dbusPath; 182 }; 183 184 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 185 const std::string& chassisIdIn, 186 const std::vector<const char*>& typesIn, 187 const std::string_view& subNode) : 188 asyncResp(asyncResp), 189 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode) 190 {} 191 192 // Store extra data about sensor mapping and return it in callback 193 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 194 const std::string& chassisIdIn, 195 const std::vector<const char*>& typesIn, 196 const std::string_view& subNode, 197 DataCompleteCb&& creationComplete) : 198 asyncResp(asyncResp), 199 chassisId(chassisIdIn), types(typesIn), 200 chassisSubNode(subNode), metadata{std::vector<SensorData>()}, 201 dataComplete{std::move(creationComplete)} 202 {} 203 204 ~SensorsAsyncResp() 205 { 206 if (asyncResp->res.result() == 207 boost::beast::http::status::internal_server_error) 208 { 209 // Reset the json object to clear out any data that made it in 210 // before the error happened todo(ed) handle error condition with 211 // proper code 212 asyncResp->res.jsonValue = nlohmann::json::object(); 213 } 214 215 if (dataComplete && metadata) 216 { 217 boost::container::flat_map<std::string, std::string> map; 218 if (asyncResp->res.result() == boost::beast::http::status::ok) 219 { 220 for (auto& sensor : *metadata) 221 { 222 map.insert(std::make_pair(sensor.uri + sensor.valueKey, 223 sensor.dbusPath)); 224 } 225 } 226 dataComplete(asyncResp->res.result(), map); 227 } 228 } 229 230 SensorsAsyncResp(const SensorsAsyncResp&) = delete; 231 SensorsAsyncResp(SensorsAsyncResp&&) = delete; 232 SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete; 233 SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete; 234 235 void addMetadata(const nlohmann::json& sensorObject, 236 const std::string& valueKey, const std::string& dbusPath) 237 { 238 if (metadata) 239 { 240 metadata->emplace_back(SensorData{sensorObject["Name"], 241 sensorObject["@odata.id"], 242 valueKey, dbusPath}); 243 } 244 } 245 246 void updateUri(const std::string& name, const std::string& uri) 247 { 248 if (metadata) 249 { 250 for (auto& sensor : *metadata) 251 { 252 if (sensor.name == name) 253 { 254 sensor.uri = uri; 255 } 256 } 257 } 258 } 259 260 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 261 const std::string chassisId; 262 const std::vector<const char*> types; 263 const std::string chassisSubNode; 264 265 private: 266 std::optional<std::vector<SensorData>> metadata; 267 DataCompleteCb dataComplete; 268 }; 269 270 /** 271 * Possible states for physical inventory leds 272 */ 273 enum class LedState 274 { 275 OFF, 276 ON, 277 BLINK, 278 UNKNOWN 279 }; 280 281 /** 282 * D-Bus inventory item associated with one or more sensors. 283 */ 284 class InventoryItem 285 { 286 public: 287 InventoryItem(const std::string& objPath) : 288 objectPath(objPath), name(), isPresent(true), isFunctional(true), 289 isPowerSupply(false), powerSupplyEfficiencyPercent(-1), manufacturer(), 290 model(), partNumber(), serialNumber(), sensors(), ledObjectPath(""), 291 ledState(LedState::UNKNOWN) 292 { 293 // Set inventory item name to last node of object path 294 sdbusplus::message::object_path path(objectPath); 295 name = path.filename(); 296 if (name.empty()) 297 { 298 BMCWEB_LOG_ERROR << "Failed to find '/' in " << objectPath; 299 } 300 } 301 302 std::string objectPath; 303 std::string name; 304 bool isPresent; 305 bool isFunctional; 306 bool isPowerSupply; 307 int powerSupplyEfficiencyPercent; 308 std::string manufacturer; 309 std::string model; 310 std::string partNumber; 311 std::string serialNumber; 312 std::set<std::string> sensors; 313 std::string ledObjectPath; 314 LedState ledState; 315 }; 316 317 /** 318 * @brief Get objects with connection necessary for sensors 319 * @param SensorsAsyncResp Pointer to object holding response data 320 * @param sensorNames Sensors retrieved from chassis 321 * @param callback Callback for processing gathered connections 322 */ 323 template <typename Callback> 324 void getObjectsWithConnection( 325 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 326 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 327 Callback&& callback) 328 { 329 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 330 const std::string path = "/xyz/openbmc_project/sensors"; 331 const std::array<std::string, 1> interfaces = { 332 "xyz.openbmc_project.Sensor.Value"}; 333 334 // Response handler for parsing objects subtree 335 auto respHandler = [callback{std::forward<Callback>(callback)}, 336 sensorsAsyncResp, 337 sensorNames](const boost::system::error_code ec, 338 const GetSubTreeType& subtree) { 339 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 340 if (ec) 341 { 342 messages::internalError(sensorsAsyncResp->asyncResp->res); 343 BMCWEB_LOG_ERROR 344 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 345 return; 346 } 347 348 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 349 350 // Make unique list of connections only for requested sensor types and 351 // found in the chassis 352 boost::container::flat_set<std::string> connections; 353 std::set<std::pair<std::string, std::string>> objectsWithConnection; 354 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 355 // producers 356 connections.reserve(8); 357 358 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size(); 359 for (const std::string& tsensor : *sensorNames) 360 { 361 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 362 } 363 364 for (const std::pair< 365 std::string, 366 std::vector<std::pair<std::string, std::vector<std::string>>>>& 367 object : subtree) 368 { 369 if (sensorNames->find(object.first) != sensorNames->end()) 370 { 371 for (const std::pair<std::string, std::vector<std::string>>& 372 objData : object.second) 373 { 374 BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first; 375 connections.insert(objData.first); 376 objectsWithConnection.insert( 377 std::make_pair(object.first, objData.first)); 378 } 379 } 380 } 381 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 382 callback(std::move(connections), std::move(objectsWithConnection)); 383 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 384 }; 385 // Make call to ObjectMapper to find all sensors objects 386 crow::connections::systemBus->async_method_call( 387 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 388 "/xyz/openbmc_project/object_mapper", 389 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 390 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 391 } 392 393 /** 394 * @brief Create connections necessary for sensors 395 * @param SensorsAsyncResp Pointer to object holding response data 396 * @param sensorNames Sensors retrieved from chassis 397 * @param callback Callback for processing gathered connections 398 */ 399 template <typename Callback> 400 void getConnections( 401 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 402 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 403 Callback&& callback) 404 { 405 auto objectsWithConnectionCb = 406 [callback](const boost::container::flat_set<std::string>& connections, 407 const std::set<std::pair<std::string, std::string>>& 408 /*objectsWithConnection*/) { callback(connections); }; 409 getObjectsWithConnection(sensorsAsyncResp, sensorNames, 410 std::move(objectsWithConnectionCb)); 411 } 412 413 /** 414 * @brief Shrinks the list of sensors for processing 415 * @param SensorsAysncResp The class holding the Redfish response 416 * @param allSensors A list of all the sensors associated to the 417 * chassis element (i.e. baseboard, front panel, etc...) 418 * @param activeSensors A list that is a reduction of the incoming 419 * allSensors list. Eliminate Thermal sensors when a Power request is 420 * made, and eliminate Power sensors when a Thermal request is made. 421 */ 422 inline void reduceSensorList( 423 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 424 const std::vector<std::string>* allSensors, 425 const std::shared_ptr<boost::container::flat_set<std::string>>& 426 activeSensors) 427 { 428 if (sensorsAsyncResp == nullptr) 429 { 430 return; 431 } 432 if ((allSensors == nullptr) || (activeSensors == nullptr)) 433 { 434 messages::resourceNotFound( 435 sensorsAsyncResp->asyncResp->res, sensorsAsyncResp->chassisSubNode, 436 sensorsAsyncResp->chassisSubNode == sensors::node::thermal 437 ? "Temperatures" 438 : "Voltages"); 439 440 return; 441 } 442 if (allSensors->empty()) 443 { 444 // Nothing to do, the activeSensors object is also empty 445 return; 446 } 447 448 for (const char* type : sensorsAsyncResp->types) 449 { 450 for (const std::string& sensor : *allSensors) 451 { 452 if (boost::starts_with(sensor, type)) 453 { 454 activeSensors->emplace(sensor); 455 } 456 } 457 } 458 } 459 460 /** 461 * @brief Retrieves valid chassis path 462 * @param asyncResp Pointer to object holding response data 463 * @param callback Callback for next step to get valid chassis path 464 */ 465 template <typename Callback> 466 void getValidChassisPath(const std::shared_ptr<SensorsAsyncResp>& asyncResp, 467 Callback&& callback) 468 { 469 BMCWEB_LOG_DEBUG << "checkChassisId enter"; 470 const std::array<const char*, 2> interfaces = { 471 "xyz.openbmc_project.Inventory.Item.Board", 472 "xyz.openbmc_project.Inventory.Item.Chassis"}; 473 474 auto respHandler = 475 [callback{std::forward<Callback>(callback)}, 476 asyncResp](const boost::system::error_code ec, 477 const std::vector<std::string>& chassisPaths) mutable { 478 BMCWEB_LOG_DEBUG << "getValidChassisPath respHandler enter"; 479 if (ec) 480 { 481 BMCWEB_LOG_ERROR 482 << "getValidChassisPath respHandler DBUS error: " << ec; 483 messages::internalError(asyncResp->asyncResp->res); 484 return; 485 } 486 487 std::optional<std::string> chassisPath; 488 std::string chassisName; 489 for (const std::string& chassis : chassisPaths) 490 { 491 sdbusplus::message::object_path path(chassis); 492 chassisName = path.filename(); 493 if (chassisName.empty()) 494 { 495 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 496 continue; 497 } 498 if (chassisName == asyncResp->chassisId) 499 { 500 chassisPath = chassis; 501 break; 502 } 503 } 504 callback(chassisPath); 505 }; 506 507 // Get the Chassis Collection 508 crow::connections::systemBus->async_method_call( 509 respHandler, "xyz.openbmc_project.ObjectMapper", 510 "/xyz/openbmc_project/object_mapper", 511 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 512 "/xyz/openbmc_project/inventory", 0, interfaces); 513 BMCWEB_LOG_DEBUG << "checkChassisId exit"; 514 } 515 516 /** 517 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 518 * @param SensorsAsyncResp Pointer to object holding response data 519 * @param callback Callback for next step in gathered sensor processing 520 */ 521 template <typename Callback> 522 void getChassis(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 523 Callback&& callback) 524 { 525 BMCWEB_LOG_DEBUG << "getChassis enter"; 526 const std::array<const char*, 2> interfaces = { 527 "xyz.openbmc_project.Inventory.Item.Board", 528 "xyz.openbmc_project.Inventory.Item.Chassis"}; 529 auto respHandler = [callback{std::forward<Callback>(callback)}, 530 sensorsAsyncResp]( 531 const boost::system::error_code ec, 532 const std::vector<std::string>& chassisPaths) { 533 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 534 if (ec) 535 { 536 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 537 messages::internalError(sensorsAsyncResp->asyncResp->res); 538 return; 539 } 540 541 const std::string* chassisPath = nullptr; 542 std::string chassisName; 543 for (const std::string& chassis : chassisPaths) 544 { 545 sdbusplus::message::object_path path(chassis); 546 chassisName = path.filename(); 547 if (chassisName.empty()) 548 { 549 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 550 continue; 551 } 552 if (chassisName == sensorsAsyncResp->chassisId) 553 { 554 chassisPath = &chassis; 555 break; 556 } 557 } 558 if (chassisPath == nullptr) 559 { 560 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 561 "Chassis", sensorsAsyncResp->chassisId); 562 return; 563 } 564 565 const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode; 566 if (chassisSubNode == sensors::node::power) 567 { 568 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 569 "#Power.v1_5_2.Power"; 570 } 571 else if (chassisSubNode == sensors::node::thermal) 572 { 573 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 574 "#Thermal.v1_4_0.Thermal"; 575 sensorsAsyncResp->asyncResp->res.jsonValue["Fans"] = 576 nlohmann::json::array(); 577 sensorsAsyncResp->asyncResp->res.jsonValue["Temperatures"] = 578 nlohmann::json::array(); 579 } 580 else if (chassisSubNode == sensors::node::sensors) 581 { 582 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 583 "#SensorCollection.SensorCollection"; 584 sensorsAsyncResp->asyncResp->res.jsonValue["Description"] = 585 "Collection of Sensors for this Chassis"; 586 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] = 587 nlohmann::json::array(); 588 sensorsAsyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 589 0; 590 } 591 592 if (chassisSubNode != sensors::node::sensors) 593 { 594 sensorsAsyncResp->asyncResp->res.jsonValue["Id"] = chassisSubNode; 595 } 596 597 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 598 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + 599 chassisSubNode; 600 sensorsAsyncResp->asyncResp->res.jsonValue["Name"] = chassisSubNode; 601 // Get the list of all sensors for this Chassis element 602 std::string sensorPath = *chassisPath + "/all_sensors"; 603 sdbusplus::asio::getProperty<std::vector<std::string>>( 604 *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper", 605 sensorPath, "xyz.openbmc_project.Association", "endpoints", 606 [sensorsAsyncResp, 607 callback{std::forward<const Callback>(callback)}]( 608 const boost::system::error_code& e, 609 const std::vector<std::string>& nodeSensorList) { 610 if (e) 611 { 612 if (e.value() != EBADR) 613 { 614 messages::internalError( 615 sensorsAsyncResp->asyncResp->res); 616 return; 617 } 618 } 619 const std::shared_ptr<boost::container::flat_set<std::string>> 620 culledSensorList = std::make_shared< 621 boost::container::flat_set<std::string>>(); 622 reduceSensorList(sensorsAsyncResp, &nodeSensorList, 623 culledSensorList); 624 callback(culledSensorList); 625 }); 626 }; 627 628 // Get the Chassis Collection 629 crow::connections::systemBus->async_method_call( 630 respHandler, "xyz.openbmc_project.ObjectMapper", 631 "/xyz/openbmc_project/object_mapper", 632 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 633 "/xyz/openbmc_project/inventory", 0, interfaces); 634 BMCWEB_LOG_DEBUG << "getChassis exit"; 635 } 636 637 /** 638 * @brief Finds all DBus object paths that implement ObjectManager. 639 * 640 * Creates a mapping from the associated connection name to the object path. 641 * 642 * Finds the object paths asynchronously. Invokes callback when information has 643 * been obtained. 644 * 645 * The callback must have the following signature: 646 * @code 647 * callback(std::shared_ptr<boost::container::flat_map<std::string, 648 * std::string>> objectMgrPaths) 649 * @endcode 650 * 651 * @param sensorsAsyncResp Pointer to object holding response data. 652 * @param callback Callback to invoke when object paths obtained. 653 */ 654 template <typename Callback> 655 void getObjectManagerPaths( 656 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 657 Callback&& callback) 658 { 659 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 660 const std::array<std::string, 1> interfaces = { 661 "org.freedesktop.DBus.ObjectManager"}; 662 663 // Response handler for GetSubTree DBus method 664 auto respHandler = [callback{std::forward<Callback>(callback)}, 665 sensorsAsyncResp](const boost::system::error_code ec, 666 const GetSubTreeType& subtree) { 667 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 668 if (ec) 669 { 670 messages::internalError(sensorsAsyncResp->asyncResp->res); 671 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 672 << ec; 673 return; 674 } 675 676 // Loop over returned object paths 677 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 678 objectMgrPaths = std::make_shared< 679 boost::container::flat_map<std::string, std::string>>(); 680 for (const std::pair< 681 std::string, 682 std::vector<std::pair<std::string, std::vector<std::string>>>>& 683 object : subtree) 684 { 685 // Loop over connections for current object path 686 const std::string& objectPath = object.first; 687 for (const std::pair<std::string, std::vector<std::string>>& 688 objData : object.second) 689 { 690 // Add mapping from connection to object path 691 const std::string& connection = objData.first; 692 (*objectMgrPaths)[connection] = objectPath; 693 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 694 << objectPath; 695 } 696 } 697 callback(objectMgrPaths); 698 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 699 }; 700 701 // Query mapper for all DBus object paths that implement ObjectManager 702 crow::connections::systemBus->async_method_call( 703 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 704 "/xyz/openbmc_project/object_mapper", 705 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, interfaces); 706 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 707 } 708 709 /** 710 * @brief Returns the Redfish State value for the specified inventory item. 711 * @param inventoryItem D-Bus inventory item associated with a sensor. 712 * @return State value for inventory item. 713 */ 714 inline std::string getState(const InventoryItem* inventoryItem) 715 { 716 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 717 { 718 return "Absent"; 719 } 720 721 return "Enabled"; 722 } 723 724 /** 725 * @brief Returns the Redfish Health value for the specified sensor. 726 * @param sensorJson Sensor JSON object. 727 * @param interfacesDict Map of all sensor interfaces. 728 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 729 * be nullptr if no associated inventory item was found. 730 * @return Health value for sensor. 731 */ 732 inline std::string 733 getHealth(nlohmann::json& sensorJson, 734 const dbus::utility::DBusInteracesMap& interfacesDict, 735 const InventoryItem* inventoryItem) 736 { 737 // Get current health value (if any) in the sensor JSON object. Some JSON 738 // objects contain multiple sensors (such as PowerSupplies). We want to set 739 // the overall health to be the most severe of any of the sensors. 740 std::string currentHealth; 741 auto statusIt = sensorJson.find("Status"); 742 if (statusIt != sensorJson.end()) 743 { 744 auto healthIt = statusIt->find("Health"); 745 if (healthIt != statusIt->end()) 746 { 747 std::string* health = healthIt->get_ptr<std::string*>(); 748 if (health != nullptr) 749 { 750 currentHealth = *health; 751 } 752 } 753 } 754 755 // If current health in JSON object is already Critical, return that. This 756 // should override the sensor health, which might be less severe. 757 if (currentHealth == "Critical") 758 { 759 return "Critical"; 760 } 761 762 // Check if sensor has critical threshold alarm 763 764 for (const auto& [interface, values] : interfacesDict) 765 { 766 if (interface == "xyz.openbmc_project.Sensor.Threshold.Critical") 767 { 768 for (const auto& [valueName, value] : values) 769 { 770 if (valueName == "CriticalAlarmHigh" || 771 valueName == "CriticalAlarmLow") 772 { 773 const bool* asserted = std::get_if<bool>(&value); 774 if (asserted == nullptr) 775 { 776 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 777 } 778 else if (*asserted) 779 { 780 return "Critical"; 781 } 782 } 783 } 784 } 785 } 786 787 // Check if associated inventory item is not functional 788 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 789 { 790 return "Critical"; 791 } 792 793 // If current health in JSON object is already Warning, return that. This 794 // should override the sensor status, which might be less severe. 795 if (currentHealth == "Warning") 796 { 797 return "Warning"; 798 } 799 800 // Check if sensor has warning threshold alarm 801 for (const auto& [interface, values] : interfacesDict) 802 { 803 if (interface == "xyz.openbmc_project.Sensor.Threshold.Warning") 804 { 805 for (const auto& [valueName, value] : values) 806 { 807 if (valueName == "WarningAlarmHigh" || 808 valueName == "WarningAlarmLow") 809 { 810 const bool* asserted = std::get_if<bool>(&value); 811 if (asserted == nullptr) 812 { 813 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 814 } 815 else if (*asserted) 816 { 817 return "Warning"; 818 } 819 } 820 } 821 } 822 } 823 824 return "OK"; 825 } 826 827 inline void setLedState(nlohmann::json& sensorJson, 828 const InventoryItem* inventoryItem) 829 { 830 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 831 { 832 switch (inventoryItem->ledState) 833 { 834 case LedState::OFF: 835 sensorJson["IndicatorLED"] = "Off"; 836 break; 837 case LedState::ON: 838 sensorJson["IndicatorLED"] = "Lit"; 839 break; 840 case LedState::BLINK: 841 sensorJson["IndicatorLED"] = "Blinking"; 842 break; 843 case LedState::UNKNOWN: 844 break; 845 } 846 } 847 } 848 849 /** 850 * @brief Builds a json sensor representation of a sensor. 851 * @param sensorName The name of the sensor to be built 852 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 853 * build 854 * @param sensorsAsyncResp Sensor metadata 855 * @param interfacesDict A dictionary of the interfaces and properties of said 856 * interfaces to be built from 857 * @param sensor_json The json object to fill 858 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 859 * be nullptr if no associated inventory item was found. 860 */ 861 inline void objectInterfacesToJson( 862 const std::string& sensorName, const std::string& sensorType, 863 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 864 const dbus::utility::DBusInteracesMap& interfacesDict, 865 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 866 { 867 // Assume values exist as is (10^0 == 1) if no scale exists 868 int64_t scaleMultiplier = 0; 869 for (const auto& [interface, values] : interfacesDict) 870 { 871 if (interface == "xyz.openbmc_project.Sensor.Value") 872 { 873 for (const auto& [valueName, value] : values) 874 { 875 if (valueName == "Scale") 876 { 877 const int64_t* int64Value = std::get_if<int64_t>(&value); 878 if (int64Value != nullptr) 879 { 880 scaleMultiplier = *int64Value; 881 } 882 } 883 } 884 } 885 } 886 887 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 888 { 889 // For sensors in SensorCollection we set Id instead of MemberId, 890 // including power sensors. 891 sensorJson["Id"] = sensorName; 892 sensorJson["Name"] = boost::replace_all_copy(sensorName, "_", " "); 893 } 894 else if (sensorType != "power") 895 { 896 // Set MemberId and Name for non-power sensors. For PowerSupplies and 897 // PowerControl, those properties have more general values because 898 // multiple sensors can be stored in the same JSON object. 899 sensorJson["MemberId"] = sensorName; 900 sensorJson["Name"] = boost::replace_all_copy(sensorName, "_", " "); 901 } 902 903 sensorJson["Status"]["State"] = getState(inventoryItem); 904 sensorJson["Status"]["Health"] = 905 getHealth(sensorJson, interfacesDict, inventoryItem); 906 907 // Parameter to set to override the type we get from dbus, and force it to 908 // int, regardless of what is available. This is used for schemas like fan, 909 // that require integers, not floats. 910 bool forceToInt = false; 911 912 nlohmann::json::json_pointer unit("/Reading"); 913 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 914 { 915 sensorJson["@odata.type"] = "#Sensor.v1_0_0.Sensor"; 916 917 const std::string& readingType = sensors::toReadingType(sensorType); 918 if (readingType.empty()) 919 { 920 BMCWEB_LOG_ERROR << "Redfish cannot map reading type for " 921 << sensorType; 922 } 923 else 924 { 925 sensorJson["ReadingType"] = readingType; 926 } 927 928 const std::string& readingUnits = sensors::toReadingUnits(sensorType); 929 if (readingUnits.empty()) 930 { 931 BMCWEB_LOG_ERROR << "Redfish cannot map reading unit for " 932 << sensorType; 933 } 934 else 935 { 936 sensorJson["ReadingUnits"] = readingUnits; 937 } 938 } 939 else if (sensorType == "temperature") 940 { 941 unit = "/ReadingCelsius"_json_pointer; 942 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 943 // TODO(ed) Documentation says that path should be type fan_tach, 944 // implementation seems to implement fan 945 } 946 else if (sensorType == "fan" || sensorType == "fan_tach") 947 { 948 unit = "/Reading"_json_pointer; 949 sensorJson["ReadingUnits"] = "RPM"; 950 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 951 setLedState(sensorJson, inventoryItem); 952 forceToInt = true; 953 } 954 else if (sensorType == "fan_pwm") 955 { 956 unit = "/Reading"_json_pointer; 957 sensorJson["ReadingUnits"] = "Percent"; 958 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 959 setLedState(sensorJson, inventoryItem); 960 forceToInt = true; 961 } 962 else if (sensorType == "voltage") 963 { 964 unit = "/ReadingVolts"_json_pointer; 965 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 966 } 967 else if (sensorType == "power") 968 { 969 std::string sensorNameLower = 970 boost::algorithm::to_lower_copy(sensorName); 971 972 if (!sensorName.compare("total_power")) 973 { 974 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 975 // Put multiple "sensors" into a single PowerControl, so have 976 // generic names for MemberId and Name. Follows Redfish mockup. 977 sensorJson["MemberId"] = "0"; 978 sensorJson["Name"] = "Chassis Power Control"; 979 unit = "/PowerConsumedWatts"_json_pointer; 980 } 981 else if (sensorNameLower.find("input") != std::string::npos) 982 { 983 unit = "/PowerInputWatts"_json_pointer; 984 } 985 else 986 { 987 unit = "/PowerOutputWatts"_json_pointer; 988 } 989 } 990 else 991 { 992 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 993 return; 994 } 995 // Map of dbus interface name, dbus property name and redfish property_name 996 std::vector< 997 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 998 properties; 999 properties.reserve(7); 1000 1001 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 1002 1003 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 1004 { 1005 properties.emplace_back( 1006 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 1007 "/Thresholds/UpperCaution/Reading"_json_pointer); 1008 properties.emplace_back( 1009 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 1010 "/Thresholds/LowerCaution/Reading"_json_pointer); 1011 properties.emplace_back( 1012 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 1013 "/Thresholds/UpperCritical/Reading"_json_pointer); 1014 properties.emplace_back( 1015 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 1016 "/Thresholds/LowerCritical/Reading"_json_pointer); 1017 } 1018 else if (sensorType != "power") 1019 { 1020 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 1021 "WarningHigh", 1022 "/UpperThresholdNonCritical"_json_pointer); 1023 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 1024 "WarningLow", 1025 "/LowerThresholdNonCritical"_json_pointer); 1026 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 1027 "CriticalHigh", 1028 "/UpperThresholdCritical"_json_pointer); 1029 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 1030 "CriticalLow", 1031 "/LowerThresholdCritical"_json_pointer); 1032 } 1033 1034 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 1035 1036 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 1037 { 1038 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1039 "/ReadingRangeMin"_json_pointer); 1040 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1041 "/ReadingRangeMax"_json_pointer); 1042 } 1043 else if (sensorType == "temperature") 1044 { 1045 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1046 "/MinReadingRangeTemp"_json_pointer); 1047 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1048 "/MaxReadingRangeTemp"_json_pointer); 1049 } 1050 else if (sensorType != "power") 1051 { 1052 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1053 "/MinReadingRange"_json_pointer); 1054 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1055 "/MaxReadingRange"_json_pointer); 1056 } 1057 1058 for (const std::tuple<const char*, const char*, 1059 nlohmann::json::json_pointer>& p : properties) 1060 { 1061 for (auto& [interface, values] : interfacesDict) 1062 { 1063 if (interface != std::get<0>(p)) 1064 { 1065 continue; 1066 } 1067 for (auto& [valueName, valueVariant] : values) 1068 { 1069 if (valueName != std::get<1>(p)) 1070 { 1071 continue; 1072 } 1073 1074 // The property we want to set may be nested json, so use 1075 // a json_pointer for easy indexing into the json structure. 1076 const nlohmann::json::json_pointer& key = std::get<2>(p); 1077 1078 // Attempt to pull the int64 directly 1079 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 1080 1081 const double* doubleValue = std::get_if<double>(&valueVariant); 1082 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant); 1083 double temp = 0.0; 1084 if (int64Value != nullptr) 1085 { 1086 temp = static_cast<double>(*int64Value); 1087 } 1088 else if (doubleValue != nullptr) 1089 { 1090 temp = *doubleValue; 1091 } 1092 else if (uValue != nullptr) 1093 { 1094 temp = *uValue; 1095 } 1096 else 1097 { 1098 BMCWEB_LOG_ERROR 1099 << "Got value interface that wasn't int or double"; 1100 continue; 1101 } 1102 temp = temp * std::pow(10, scaleMultiplier); 1103 if (forceToInt) 1104 { 1105 sensorJson[key] = static_cast<int64_t>(temp); 1106 } 1107 else 1108 { 1109 sensorJson[key] = temp; 1110 } 1111 } 1112 } 1113 } 1114 1115 sensorsAsyncResp->addMetadata(sensorJson, unit.to_string(), 1116 "/xyz/openbmc_project/sensors/" + sensorType + 1117 "/" + sensorName); 1118 1119 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 1120 } 1121 1122 inline void populateFanRedundancy( 1123 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1124 { 1125 crow::connections::systemBus->async_method_call( 1126 [sensorsAsyncResp](const boost::system::error_code ec, 1127 const GetSubTreeType& resp) { 1128 if (ec) 1129 { 1130 return; // don't have to have this interface 1131 } 1132 for (const std::pair<std::string, 1133 std::vector<std::pair< 1134 std::string, std::vector<std::string>>>>& 1135 pathPair : resp) 1136 { 1137 const std::string& path = pathPair.first; 1138 const std::vector< 1139 std::pair<std::string, std::vector<std::string>>>& objDict = 1140 pathPair.second; 1141 if (objDict.empty()) 1142 { 1143 continue; // this should be impossible 1144 } 1145 1146 const std::string& owner = objDict.begin()->first; 1147 sdbusplus::asio::getProperty<std::vector<std::string>>( 1148 *crow::connections::systemBus, 1149 "xyz.openbmc_project.ObjectMapper", path + "/chassis", 1150 "xyz.openbmc_project.Association", "endpoints", 1151 [path, owner, sensorsAsyncResp]( 1152 const boost::system::error_code e, 1153 const std::vector<std::string>& endpoints) { 1154 if (e) 1155 { 1156 return; // if they don't have an association we 1157 // can't tell what chassis is 1158 } 1159 auto found = std::find_if( 1160 endpoints.begin(), endpoints.end(), 1161 [sensorsAsyncResp](const std::string& entry) { 1162 return entry.find( 1163 sensorsAsyncResp->chassisId) != 1164 std::string::npos; 1165 }); 1166 1167 if (found == endpoints.end()) 1168 { 1169 return; 1170 } 1171 crow::connections::systemBus->async_method_call( 1172 [path, sensorsAsyncResp]( 1173 const boost::system::error_code& err, 1174 const boost::container::flat_map< 1175 std::string, 1176 dbus::utility::DbusVariantType>& ret) { 1177 if (err) 1178 { 1179 return; // don't have to have this 1180 // interface 1181 } 1182 auto findFailures = ret.find("AllowedFailures"); 1183 auto findCollection = ret.find("Collection"); 1184 auto findStatus = ret.find("Status"); 1185 1186 if (findFailures == ret.end() || 1187 findCollection == ret.end() || 1188 findStatus == ret.end()) 1189 { 1190 BMCWEB_LOG_ERROR 1191 << "Invalid redundancy interface"; 1192 messages::internalError( 1193 sensorsAsyncResp->asyncResp->res); 1194 return; 1195 } 1196 1197 const uint8_t* allowedFailures = 1198 std::get_if<uint8_t>( 1199 &(findFailures->second)); 1200 const std::vector<std::string>* collection = 1201 std::get_if<std::vector<std::string>>( 1202 &(findCollection->second)); 1203 const std::string* status = 1204 std::get_if<std::string>( 1205 &(findStatus->second)); 1206 1207 if (allowedFailures == nullptr || 1208 collection == nullptr || status == nullptr) 1209 { 1210 1211 BMCWEB_LOG_ERROR 1212 << "Invalid redundancy interface types"; 1213 messages::internalError( 1214 sensorsAsyncResp->asyncResp->res); 1215 return; 1216 } 1217 sdbusplus::message::object_path objectPath( 1218 path); 1219 std::string name = objectPath.filename(); 1220 if (name.empty()) 1221 { 1222 // this should be impossible 1223 messages::internalError( 1224 sensorsAsyncResp->asyncResp->res); 1225 return; 1226 } 1227 std::replace(name.begin(), name.end(), '_', 1228 ' '); 1229 1230 std::string health; 1231 1232 if (boost::ends_with(*status, "Full")) 1233 { 1234 health = "OK"; 1235 } 1236 else if (boost::ends_with(*status, "Degraded")) 1237 { 1238 health = "Warning"; 1239 } 1240 else 1241 { 1242 health = "Critical"; 1243 } 1244 std::vector<nlohmann::json> redfishCollection; 1245 const auto& fanRedfish = 1246 sensorsAsyncResp->asyncResp->res 1247 .jsonValue["Fans"]; 1248 for (const std::string& item : *collection) 1249 { 1250 sdbusplus::message::object_path path(item); 1251 std::string itemName = path.filename(); 1252 if (itemName.empty()) 1253 { 1254 continue; 1255 } 1256 /* 1257 todo(ed): merge patch that fixes the names 1258 std::replace(itemName.begin(), 1259 itemName.end(), '_', ' ');*/ 1260 auto schemaItem = std::find_if( 1261 fanRedfish.begin(), fanRedfish.end(), 1262 [itemName](const nlohmann::json& fan) { 1263 return fan["MemberId"] == itemName; 1264 }); 1265 if (schemaItem != fanRedfish.end()) 1266 { 1267 redfishCollection.push_back( 1268 {{"@odata.id", 1269 (*schemaItem)["@odata.id"]}}); 1270 } 1271 else 1272 { 1273 BMCWEB_LOG_ERROR 1274 << "failed to find fan in schema"; 1275 messages::internalError( 1276 sensorsAsyncResp->asyncResp->res); 1277 return; 1278 } 1279 } 1280 1281 size_t minNumNeeded = 1282 collection->empty() 1283 ? 0 1284 : collection->size() - *allowedFailures; 1285 nlohmann::json& jResp = 1286 sensorsAsyncResp->asyncResp->res 1287 .jsonValue["Redundancy"]; 1288 jResp.push_back( 1289 {{"@odata.id", 1290 "/redfish/v1/Chassis/" + 1291 sensorsAsyncResp->chassisId + "/" + 1292 sensorsAsyncResp->chassisSubNode + 1293 "#/Redundancy/" + 1294 std::to_string(jResp.size())}, 1295 {"@odata.type", 1296 "#Redundancy.v1_3_2.Redundancy"}, 1297 {"MinNumNeeded", minNumNeeded}, 1298 {"MemberId", name}, 1299 {"Mode", "N+m"}, 1300 {"Name", name}, 1301 {"RedundancySet", redfishCollection}, 1302 {"Status", 1303 {{"Health", health}, 1304 {"State", "Enabled"}}}}); 1305 }, 1306 owner, path, "org.freedesktop.DBus.Properties", 1307 "GetAll", 1308 "xyz.openbmc_project.Control.FanRedundancy"); 1309 }); 1310 } 1311 }, 1312 "xyz.openbmc_project.ObjectMapper", 1313 "/xyz/openbmc_project/object_mapper", 1314 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1315 "/xyz/openbmc_project/control", 2, 1316 std::array<const char*, 1>{ 1317 "xyz.openbmc_project.Control.FanRedundancy"}); 1318 } 1319 1320 inline void 1321 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1322 { 1323 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 1324 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 1325 if (sensorsAsyncResp->chassisSubNode == sensors::node::power) 1326 { 1327 sensorHeaders = {"Voltages", "PowerSupplies"}; 1328 } 1329 for (const std::string& sensorGroup : sensorHeaders) 1330 { 1331 nlohmann::json::iterator entry = response.find(sensorGroup); 1332 if (entry != response.end()) 1333 { 1334 std::sort(entry->begin(), entry->end(), 1335 [](nlohmann::json& c1, nlohmann::json& c2) { 1336 return c1["Name"] < c2["Name"]; 1337 }); 1338 1339 // add the index counts to the end of each entry 1340 size_t count = 0; 1341 for (nlohmann::json& sensorJson : *entry) 1342 { 1343 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 1344 if (odata == sensorJson.end()) 1345 { 1346 continue; 1347 } 1348 std::string* value = odata->get_ptr<std::string*>(); 1349 if (value != nullptr) 1350 { 1351 *value += std::to_string(count); 1352 count++; 1353 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 1354 } 1355 } 1356 } 1357 } 1358 } 1359 1360 /** 1361 * @brief Finds the inventory item with the specified object path. 1362 * @param inventoryItems D-Bus inventory items associated with sensors. 1363 * @param invItemObjPath D-Bus object path of inventory item. 1364 * @return Inventory item within vector, or nullptr if no match found. 1365 */ 1366 inline InventoryItem* findInventoryItem( 1367 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1368 const std::string& invItemObjPath) 1369 { 1370 for (InventoryItem& inventoryItem : *inventoryItems) 1371 { 1372 if (inventoryItem.objectPath == invItemObjPath) 1373 { 1374 return &inventoryItem; 1375 } 1376 } 1377 return nullptr; 1378 } 1379 1380 /** 1381 * @brief Finds the inventory item associated with the specified sensor. 1382 * @param inventoryItems D-Bus inventory items associated with sensors. 1383 * @param sensorObjPath D-Bus object path of sensor. 1384 * @return Inventory item within vector, or nullptr if no match found. 1385 */ 1386 inline InventoryItem* findInventoryItemForSensor( 1387 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1388 const std::string& sensorObjPath) 1389 { 1390 for (InventoryItem& inventoryItem : *inventoryItems) 1391 { 1392 if (inventoryItem.sensors.count(sensorObjPath) > 0) 1393 { 1394 return &inventoryItem; 1395 } 1396 } 1397 return nullptr; 1398 } 1399 1400 /** 1401 * @brief Finds the inventory item associated with the specified led path. 1402 * @param inventoryItems D-Bus inventory items associated with sensors. 1403 * @param ledObjPath D-Bus object path of led. 1404 * @return Inventory item within vector, or nullptr if no match found. 1405 */ 1406 inline InventoryItem* 1407 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems, 1408 const std::string& ledObjPath) 1409 { 1410 for (InventoryItem& inventoryItem : inventoryItems) 1411 { 1412 if (inventoryItem.ledObjectPath == ledObjPath) 1413 { 1414 return &inventoryItem; 1415 } 1416 } 1417 return nullptr; 1418 } 1419 1420 /** 1421 * @brief Adds inventory item and associated sensor to specified vector. 1422 * 1423 * Adds a new InventoryItem to the vector if necessary. Searches for an 1424 * existing InventoryItem with the specified object path. If not found, one is 1425 * added to the vector. 1426 * 1427 * Next, the specified sensor is added to the set of sensors associated with the 1428 * InventoryItem. 1429 * 1430 * @param inventoryItems D-Bus inventory items associated with sensors. 1431 * @param invItemObjPath D-Bus object path of inventory item. 1432 * @param sensorObjPath D-Bus object path of sensor 1433 */ 1434 inline void addInventoryItem( 1435 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1436 const std::string& invItemObjPath, const std::string& sensorObjPath) 1437 { 1438 // Look for inventory item in vector 1439 InventoryItem* inventoryItem = 1440 findInventoryItem(inventoryItems, invItemObjPath); 1441 1442 // If inventory item doesn't exist in vector, add it 1443 if (inventoryItem == nullptr) 1444 { 1445 inventoryItems->emplace_back(invItemObjPath); 1446 inventoryItem = &(inventoryItems->back()); 1447 } 1448 1449 // Add sensor to set of sensors associated with inventory item 1450 inventoryItem->sensors.emplace(sensorObjPath); 1451 } 1452 1453 /** 1454 * @brief Stores D-Bus data in the specified inventory item. 1455 * 1456 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 1457 * specified InventoryItem. 1458 * 1459 * This data is later used to provide sensor property values in the JSON 1460 * response. 1461 * 1462 * @param inventoryItem Inventory item where data will be stored. 1463 * @param interfacesDict Map containing D-Bus interfaces and their properties 1464 * for the specified inventory item. 1465 */ 1466 inline void storeInventoryItemData( 1467 InventoryItem& inventoryItem, 1468 const dbus::utility::DBusInteracesMap& interfacesDict) 1469 { 1470 // Get properties from Inventory.Item interface 1471 1472 for (const auto& [interface, values] : interfacesDict) 1473 { 1474 if (interface == "xyz.openbmc_project.Inventory.Item") 1475 { 1476 for (const auto& [name, dbusValue] : values) 1477 { 1478 if (name == "Present") 1479 { 1480 const bool* value = std::get_if<bool>(&dbusValue); 1481 if (value != nullptr) 1482 { 1483 inventoryItem.isPresent = *value; 1484 } 1485 } 1486 } 1487 } 1488 // Check if Inventory.Item.PowerSupply interface is present 1489 1490 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 1491 { 1492 inventoryItem.isPowerSupply = true; 1493 } 1494 1495 // Get properties from Inventory.Decorator.Asset interface 1496 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 1497 { 1498 for (const auto& [name, dbusValue] : values) 1499 { 1500 if (name == "Manufacturer") 1501 { 1502 const std::string* value = 1503 std::get_if<std::string>(&dbusValue); 1504 if (value != nullptr) 1505 { 1506 inventoryItem.manufacturer = *value; 1507 } 1508 } 1509 if (name == "Model") 1510 { 1511 const std::string* value = 1512 std::get_if<std::string>(&dbusValue); 1513 if (value != nullptr) 1514 { 1515 inventoryItem.model = *value; 1516 } 1517 } 1518 if (name == "SerialNumber") 1519 { 1520 const std::string* value = 1521 std::get_if<std::string>(&dbusValue); 1522 if (value != nullptr) 1523 { 1524 inventoryItem.serialNumber = *value; 1525 } 1526 } 1527 if (name == "PartNumber") 1528 { 1529 const std::string* value = 1530 std::get_if<std::string>(&dbusValue); 1531 if (value != nullptr) 1532 { 1533 inventoryItem.partNumber = *value; 1534 } 1535 } 1536 } 1537 } 1538 1539 if (interface == 1540 "xyz.openbmc_project.State.Decorator.OperationalStatus") 1541 { 1542 for (const auto& [name, dbusValue] : values) 1543 { 1544 if (name == "Functional") 1545 { 1546 const bool* value = std::get_if<bool>(&dbusValue); 1547 if (value != nullptr) 1548 { 1549 inventoryItem.isFunctional = *value; 1550 } 1551 } 1552 } 1553 } 1554 } 1555 } 1556 1557 /** 1558 * @brief Gets D-Bus data for inventory items associated with sensors. 1559 * 1560 * Uses the specified connections (services) to obtain D-Bus data for inventory 1561 * items associated with sensors. Stores the resulting data in the 1562 * inventoryItems vector. 1563 * 1564 * This data is later used to provide sensor property values in the JSON 1565 * response. 1566 * 1567 * Finds the inventory item data asynchronously. Invokes callback when data has 1568 * been obtained. 1569 * 1570 * The callback must have the following signature: 1571 * @code 1572 * callback(void) 1573 * @endcode 1574 * 1575 * This function is called recursively, obtaining data asynchronously from one 1576 * connection in each call. This ensures the callback is not invoked until the 1577 * last asynchronous function has completed. 1578 * 1579 * @param sensorsAsyncResp Pointer to object holding response data. 1580 * @param inventoryItems D-Bus inventory items associated with sensors. 1581 * @param invConnections Connections that provide data for the inventory items. 1582 * @param objectMgrPaths Mappings from connection name to DBus object path that 1583 * implements ObjectManager. 1584 * @param callback Callback to invoke when inventory data has been obtained. 1585 * @param invConnectionsIndex Current index in invConnections. Only specified 1586 * in recursive calls to this function. 1587 */ 1588 template <typename Callback> 1589 static void getInventoryItemsData( 1590 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1591 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1592 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1593 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1594 objectMgrPaths, 1595 Callback&& callback, size_t invConnectionsIndex = 0) 1596 { 1597 BMCWEB_LOG_DEBUG << "getInventoryItemsData enter"; 1598 1599 // If no more connections left, call callback 1600 if (invConnectionsIndex >= invConnections->size()) 1601 { 1602 callback(); 1603 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1604 return; 1605 } 1606 1607 // Get inventory item data from current connection 1608 auto it = invConnections->nth(invConnectionsIndex); 1609 if (it != invConnections->end()) 1610 { 1611 const std::string& invConnection = *it; 1612 1613 // Response handler for GetManagedObjects 1614 auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections, 1615 objectMgrPaths, 1616 callback{std::forward<Callback>(callback)}, 1617 invConnectionsIndex]( 1618 const boost::system::error_code ec, 1619 dbus::utility::ManagedObjectType& resp) { 1620 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter"; 1621 if (ec) 1622 { 1623 BMCWEB_LOG_ERROR 1624 << "getInventoryItemsData respHandler DBus error " << ec; 1625 messages::internalError(sensorsAsyncResp->asyncResp->res); 1626 return; 1627 } 1628 1629 // Loop through returned object paths 1630 for (const auto& objDictEntry : resp) 1631 { 1632 const std::string& objPath = 1633 static_cast<const std::string&>(objDictEntry.first); 1634 1635 // If this object path is one of the specified inventory items 1636 InventoryItem* inventoryItem = 1637 findInventoryItem(inventoryItems, objPath); 1638 if (inventoryItem != nullptr) 1639 { 1640 // Store inventory data in InventoryItem 1641 storeInventoryItemData(*inventoryItem, objDictEntry.second); 1642 } 1643 } 1644 1645 // Recurse to get inventory item data from next connection 1646 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1647 invConnections, objectMgrPaths, 1648 std::move(callback), invConnectionsIndex + 1); 1649 1650 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit"; 1651 }; 1652 1653 // Find DBus object path that implements ObjectManager for the current 1654 // connection. If no mapping found, default to "/". 1655 auto iter = objectMgrPaths->find(invConnection); 1656 const std::string& objectMgrPath = 1657 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1658 BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is " 1659 << objectMgrPath; 1660 1661 // Get all object paths and their interfaces for current connection 1662 crow::connections::systemBus->async_method_call( 1663 std::move(respHandler), invConnection, objectMgrPath, 1664 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1665 } 1666 1667 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1668 } 1669 1670 /** 1671 * @brief Gets connections that provide D-Bus data for inventory items. 1672 * 1673 * Gets the D-Bus connections (services) that provide data for the inventory 1674 * items that are associated with sensors. 1675 * 1676 * Finds the connections asynchronously. Invokes callback when information has 1677 * been obtained. 1678 * 1679 * The callback must have the following signature: 1680 * @code 1681 * callback(std::shared_ptr<boost::container::flat_set<std::string>> 1682 * invConnections) 1683 * @endcode 1684 * 1685 * @param sensorsAsyncResp Pointer to object holding response data. 1686 * @param inventoryItems D-Bus inventory items associated with sensors. 1687 * @param callback Callback to invoke when connections have been obtained. 1688 */ 1689 template <typename Callback> 1690 static void getInventoryItemsConnections( 1691 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1692 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1693 Callback&& callback) 1694 { 1695 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1696 1697 const std::string path = "/xyz/openbmc_project/inventory"; 1698 const std::array<std::string, 4> interfaces = { 1699 "xyz.openbmc_project.Inventory.Item", 1700 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1701 "xyz.openbmc_project.Inventory.Decorator.Asset", 1702 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1703 1704 // Response handler for parsing output from GetSubTree 1705 auto respHandler = [callback{std::forward<Callback>(callback)}, 1706 sensorsAsyncResp, 1707 inventoryItems](const boost::system::error_code ec, 1708 const GetSubTreeType& subtree) { 1709 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1710 if (ec) 1711 { 1712 messages::internalError(sensorsAsyncResp->asyncResp->res); 1713 BMCWEB_LOG_ERROR 1714 << "getInventoryItemsConnections respHandler DBus error " << ec; 1715 return; 1716 } 1717 1718 // Make unique list of connections for desired inventory items 1719 std::shared_ptr<boost::container::flat_set<std::string>> 1720 invConnections = 1721 std::make_shared<boost::container::flat_set<std::string>>(); 1722 invConnections->reserve(8); 1723 1724 // Loop through objects from GetSubTree 1725 for (const std::pair< 1726 std::string, 1727 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1728 object : subtree) 1729 { 1730 // Check if object path is one of the specified inventory items 1731 const std::string& objPath = object.first; 1732 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1733 { 1734 // Store all connections to inventory item 1735 for (const std::pair<std::string, std::vector<std::string>>& 1736 objData : object.second) 1737 { 1738 const std::string& invConnection = objData.first; 1739 invConnections->insert(invConnection); 1740 } 1741 } 1742 } 1743 1744 callback(invConnections); 1745 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1746 }; 1747 1748 // Make call to ObjectMapper to find all inventory items 1749 crow::connections::systemBus->async_method_call( 1750 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 1751 "/xyz/openbmc_project/object_mapper", 1752 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 1753 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1754 } 1755 1756 /** 1757 * @brief Gets associations from sensors to inventory items. 1758 * 1759 * Looks for ObjectMapper associations from the specified sensors to related 1760 * inventory items. Then finds the associations from those inventory items to 1761 * their LEDs, if any. 1762 * 1763 * Finds the inventory items asynchronously. Invokes callback when information 1764 * has been obtained. 1765 * 1766 * The callback must have the following signature: 1767 * @code 1768 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1769 * @endcode 1770 * 1771 * @param sensorsAsyncResp Pointer to object holding response data. 1772 * @param sensorNames All sensors within the current chassis. 1773 * @param objectMgrPaths Mappings from connection name to DBus object path that 1774 * implements ObjectManager. 1775 * @param callback Callback to invoke when inventory items have been obtained. 1776 */ 1777 template <typename Callback> 1778 static void getInventoryItemAssociations( 1779 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1780 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 1781 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 1782 objectMgrPaths, 1783 Callback&& callback) 1784 { 1785 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter"; 1786 1787 // Response handler for GetManagedObjects 1788 auto respHandler = [callback{std::forward<Callback>(callback)}, 1789 sensorsAsyncResp, 1790 sensorNames](const boost::system::error_code ec, 1791 dbus::utility::ManagedObjectType& resp) { 1792 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter"; 1793 if (ec) 1794 { 1795 BMCWEB_LOG_ERROR 1796 << "getInventoryItemAssociations respHandler DBus error " << ec; 1797 messages::internalError(sensorsAsyncResp->asyncResp->res); 1798 return; 1799 } 1800 1801 // Create vector to hold list of inventory items 1802 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1803 std::make_shared<std::vector<InventoryItem>>(); 1804 1805 // Loop through returned object paths 1806 std::string sensorAssocPath; 1807 sensorAssocPath.reserve(128); // avoid memory allocations 1808 for (const auto& objDictEntry : resp) 1809 { 1810 const std::string& objPath = 1811 static_cast<const std::string&>(objDictEntry.first); 1812 1813 // If path is inventory association for one of the specified sensors 1814 for (const std::string& sensorName : *sensorNames) 1815 { 1816 sensorAssocPath = sensorName; 1817 sensorAssocPath += "/inventory"; 1818 if (objPath == sensorAssocPath) 1819 { 1820 // Get Association interface for object path 1821 for (const auto& [interface, values] : objDictEntry.second) 1822 { 1823 if (interface == "xyz.openbmc_project.Association") 1824 { 1825 for (const auto& [valueName, value] : values) 1826 { 1827 if (valueName == "endpoints") 1828 { 1829 const std::vector<std::string>* endpoints = 1830 std::get_if<std::vector<std::string>>( 1831 &value); 1832 if ((endpoints != nullptr) && 1833 !endpoints->empty()) 1834 { 1835 // Add inventory item to vector 1836 const std::string& invItemPath = 1837 endpoints->front(); 1838 addInventoryItem(inventoryItems, 1839 invItemPath, 1840 sensorName); 1841 } 1842 } 1843 } 1844 } 1845 } 1846 break; 1847 } 1848 } 1849 } 1850 1851 // Now loop through the returned object paths again, this time to 1852 // find the leds associated with the inventory items we just found 1853 std::string inventoryAssocPath; 1854 inventoryAssocPath.reserve(128); // avoid memory allocations 1855 for (const auto& objDictEntry : resp) 1856 { 1857 const std::string& objPath = 1858 static_cast<const std::string&>(objDictEntry.first); 1859 1860 for (InventoryItem& inventoryItem : *inventoryItems) 1861 { 1862 inventoryAssocPath = inventoryItem.objectPath; 1863 inventoryAssocPath += "/leds"; 1864 if (objPath == inventoryAssocPath) 1865 { 1866 for (const auto& [interface, values] : objDictEntry.second) 1867 { 1868 if (interface == "xyz.openbmc_project.Association") 1869 { 1870 for (const auto& [valueName, value] : values) 1871 { 1872 if (valueName == "endpoints") 1873 { 1874 const std::vector<std::string>* endpoints = 1875 std::get_if<std::vector<std::string>>( 1876 &value); 1877 if ((endpoints != nullptr) && 1878 !endpoints->empty()) 1879 { 1880 // Add inventory item to vector 1881 // Store LED path in inventory item 1882 const std::string& ledPath = 1883 endpoints->front(); 1884 inventoryItem.ledObjectPath = ledPath; 1885 } 1886 } 1887 } 1888 } 1889 } 1890 1891 break; 1892 } 1893 } 1894 } 1895 callback(inventoryItems); 1896 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit"; 1897 }; 1898 1899 // Find DBus object path that implements ObjectManager for ObjectMapper 1900 std::string connection = "xyz.openbmc_project.ObjectMapper"; 1901 auto iter = objectMgrPaths->find(connection); 1902 const std::string& objectMgrPath = 1903 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1904 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1905 << objectMgrPath; 1906 1907 // Call GetManagedObjects on the ObjectMapper to get all associations 1908 crow::connections::systemBus->async_method_call( 1909 std::move(respHandler), connection, objectMgrPath, 1910 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1911 1912 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit"; 1913 } 1914 1915 /** 1916 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1917 * 1918 * Uses the specified connections (services) to obtain D-Bus data for inventory 1919 * item leds associated with sensors. Stores the resulting data in the 1920 * inventoryItems vector. 1921 * 1922 * This data is later used to provide sensor property values in the JSON 1923 * response. 1924 * 1925 * Finds the inventory item led data asynchronously. Invokes callback when data 1926 * has been obtained. 1927 * 1928 * The callback must have the following signature: 1929 * @code 1930 * callback() 1931 * @endcode 1932 * 1933 * This function is called recursively, obtaining data asynchronously from one 1934 * connection in each call. This ensures the callback is not invoked until the 1935 * last asynchronous function has completed. 1936 * 1937 * @param sensorsAsyncResp Pointer to object holding response data. 1938 * @param inventoryItems D-Bus inventory items associated with sensors. 1939 * @param ledConnections Connections that provide data for the inventory leds. 1940 * @param callback Callback to invoke when inventory data has been obtained. 1941 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1942 * in recursive calls to this function. 1943 */ 1944 template <typename Callback> 1945 void getInventoryLedData( 1946 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1947 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1948 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1949 ledConnections, 1950 Callback&& callback, size_t ledConnectionsIndex = 0) 1951 { 1952 BMCWEB_LOG_DEBUG << "getInventoryLedData enter"; 1953 1954 // If no more connections left, call callback 1955 if (ledConnectionsIndex >= ledConnections->size()) 1956 { 1957 callback(); 1958 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 1959 return; 1960 } 1961 1962 // Get inventory item data from current connection 1963 auto it = ledConnections->nth(ledConnectionsIndex); 1964 if (it != ledConnections->end()) 1965 { 1966 const std::string& ledPath = (*it).first; 1967 const std::string& ledConnection = (*it).second; 1968 // Response handler for Get State property 1969 auto respHandler = 1970 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1971 callback{std::forward<Callback>(callback)}, ledConnectionsIndex]( 1972 const boost::system::error_code ec, const std::string& state) { 1973 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter"; 1974 if (ec) 1975 { 1976 BMCWEB_LOG_ERROR 1977 << "getInventoryLedData respHandler DBus error " << ec; 1978 messages::internalError(sensorsAsyncResp->asyncResp->res); 1979 return; 1980 } 1981 1982 BMCWEB_LOG_DEBUG << "Led state: " << state; 1983 // Find inventory item with this LED object path 1984 InventoryItem* inventoryItem = 1985 findInventoryItemForLed(*inventoryItems, ledPath); 1986 if (inventoryItem != nullptr) 1987 { 1988 // Store LED state in InventoryItem 1989 if (boost::ends_with(state, "On")) 1990 { 1991 inventoryItem->ledState = LedState::ON; 1992 } 1993 else if (boost::ends_with(state, "Blink")) 1994 { 1995 inventoryItem->ledState = LedState::BLINK; 1996 } 1997 else if (boost::ends_with(state, "Off")) 1998 { 1999 inventoryItem->ledState = LedState::OFF; 2000 } 2001 else 2002 { 2003 inventoryItem->ledState = LedState::UNKNOWN; 2004 } 2005 } 2006 2007 // Recurse to get LED data from next connection 2008 getInventoryLedData(sensorsAsyncResp, inventoryItems, 2009 ledConnections, std::move(callback), 2010 ledConnectionsIndex + 1); 2011 2012 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit"; 2013 }; 2014 2015 // Get the State property for the current LED 2016 sdbusplus::asio::getProperty<std::string>( 2017 *crow::connections::systemBus, ledConnection, ledPath, 2018 "xyz.openbmc_project.Led.Physical", "State", 2019 std::move(respHandler)); 2020 } 2021 2022 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 2023 } 2024 2025 /** 2026 * @brief Gets LED data for LEDs associated with given inventory items. 2027 * 2028 * Gets the D-Bus connections (services) that provide LED data for the LEDs 2029 * associated with the specified inventory items. Then gets the LED data from 2030 * each connection and stores it in the inventory item. 2031 * 2032 * This data is later used to provide sensor property values in the JSON 2033 * response. 2034 * 2035 * Finds the LED data asynchronously. Invokes callback when information has 2036 * been obtained. 2037 * 2038 * The callback must have the following signature: 2039 * @code 2040 * callback() 2041 * @endcode 2042 * 2043 * @param sensorsAsyncResp Pointer to object holding response data. 2044 * @param inventoryItems D-Bus inventory items associated with sensors. 2045 * @param callback Callback to invoke when inventory items have been obtained. 2046 */ 2047 template <typename Callback> 2048 void getInventoryLeds( 2049 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2050 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2051 Callback&& callback) 2052 { 2053 BMCWEB_LOG_DEBUG << "getInventoryLeds enter"; 2054 2055 const std::string path = "/xyz/openbmc_project"; 2056 const std::array<std::string, 1> interfaces = { 2057 "xyz.openbmc_project.Led.Physical"}; 2058 2059 // Response handler for parsing output from GetSubTree 2060 auto respHandler = [callback{std::forward<Callback>(callback)}, 2061 sensorsAsyncResp, 2062 inventoryItems](const boost::system::error_code ec, 2063 const GetSubTreeType& subtree) { 2064 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter"; 2065 if (ec) 2066 { 2067 messages::internalError(sensorsAsyncResp->asyncResp->res); 2068 BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error " 2069 << ec; 2070 return; 2071 } 2072 2073 // Build map of LED object paths to connections 2074 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2075 ledConnections = std::make_shared< 2076 boost::container::flat_map<std::string, std::string>>(); 2077 2078 // Loop through objects from GetSubTree 2079 for (const std::pair< 2080 std::string, 2081 std::vector<std::pair<std::string, std::vector<std::string>>>>& 2082 object : subtree) 2083 { 2084 // Check if object path is LED for one of the specified inventory 2085 // items 2086 const std::string& ledPath = object.first; 2087 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr) 2088 { 2089 // Add mapping from ledPath to connection 2090 const std::string& connection = object.second.begin()->first; 2091 (*ledConnections)[ledPath] = connection; 2092 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> " 2093 << connection; 2094 } 2095 } 2096 2097 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections, 2098 std::move(callback)); 2099 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit"; 2100 }; 2101 // Make call to ObjectMapper to find all inventory items 2102 crow::connections::systemBus->async_method_call( 2103 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2104 "/xyz/openbmc_project/object_mapper", 2105 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 2106 BMCWEB_LOG_DEBUG << "getInventoryLeds exit"; 2107 } 2108 2109 /** 2110 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 2111 * 2112 * Uses the specified connections (services) (currently assumes just one) to 2113 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 2114 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 2115 * 2116 * This data is later used to provide sensor property values in the JSON 2117 * response. 2118 * 2119 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2120 * when data has been obtained. 2121 * 2122 * The callback must have the following signature: 2123 * @code 2124 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2125 * @endcode 2126 * 2127 * @param sensorsAsyncResp Pointer to object holding response data. 2128 * @param inventoryItems D-Bus inventory items associated with sensors. 2129 * @param psAttributesConnections Connections that provide data for the Power 2130 * Supply Attributes 2131 * @param callback Callback to invoke when data has been obtained. 2132 */ 2133 template <typename Callback> 2134 void getPowerSupplyAttributesData( 2135 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2136 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2137 const boost::container::flat_map<std::string, std::string>& 2138 psAttributesConnections, 2139 Callback&& callback) 2140 { 2141 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter"; 2142 2143 if (psAttributesConnections.empty()) 2144 { 2145 BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!"; 2146 callback(inventoryItems); 2147 return; 2148 } 2149 2150 // Assuming just one connection (service) for now 2151 auto it = psAttributesConnections.nth(0); 2152 2153 const std::string& psAttributesPath = (*it).first; 2154 const std::string& psAttributesConnection = (*it).second; 2155 2156 // Response handler for Get DeratingFactor property 2157 auto respHandler = [sensorsAsyncResp, inventoryItems, 2158 callback{std::forward<Callback>(callback)}]( 2159 const boost::system::error_code ec, 2160 const uint32_t value) { 2161 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter"; 2162 if (ec) 2163 { 2164 BMCWEB_LOG_ERROR 2165 << "getPowerSupplyAttributesData respHandler DBus error " << ec; 2166 messages::internalError(sensorsAsyncResp->asyncResp->res); 2167 return; 2168 } 2169 2170 BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value; 2171 // Store value in Power Supply Inventory Items 2172 for (InventoryItem& inventoryItem : *inventoryItems) 2173 { 2174 if (inventoryItem.isPowerSupply == true) 2175 { 2176 inventoryItem.powerSupplyEfficiencyPercent = 2177 static_cast<int>(value); 2178 } 2179 } 2180 2181 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit"; 2182 callback(inventoryItems); 2183 }; 2184 2185 // Get the DeratingFactor property for the PowerSupplyAttributes 2186 // Currently only property on the interface/only one we care about 2187 sdbusplus::asio::getProperty<uint32_t>( 2188 *crow::connections::systemBus, psAttributesConnection, psAttributesPath, 2189 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 2190 std::move(respHandler)); 2191 2192 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit"; 2193 } 2194 2195 /** 2196 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 2197 * 2198 * Gets the D-Bus connection (service) that provides Power Supply Attributes 2199 * data. Then gets the Power Supply Attributes data from the connection 2200 * (currently just assumes 1 connection) and stores the data in the inventory 2201 * item. 2202 * 2203 * This data is later used to provide sensor property values in the JSON 2204 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 2205 * 2206 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2207 * when information has been obtained. 2208 * 2209 * The callback must have the following signature: 2210 * @code 2211 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2212 * @endcode 2213 * 2214 * @param sensorsAsyncResp Pointer to object holding response data. 2215 * @param inventoryItems D-Bus inventory items associated with sensors. 2216 * @param callback Callback to invoke when data has been obtained. 2217 */ 2218 template <typename Callback> 2219 void getPowerSupplyAttributes( 2220 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2221 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2222 Callback&& callback) 2223 { 2224 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter"; 2225 2226 // Only need the power supply attributes when the Power Schema 2227 if (sensorsAsyncResp->chassisSubNode != sensors::node::power) 2228 { 2229 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power"; 2230 callback(inventoryItems); 2231 return; 2232 } 2233 2234 const std::array<std::string, 1> interfaces = { 2235 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 2236 2237 // Response handler for parsing output from GetSubTree 2238 auto respHandler = [callback{std::forward<Callback>(callback)}, 2239 sensorsAsyncResp, 2240 inventoryItems](const boost::system::error_code ec, 2241 const GetSubTreeType& subtree) { 2242 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter"; 2243 if (ec) 2244 { 2245 messages::internalError(sensorsAsyncResp->asyncResp->res); 2246 BMCWEB_LOG_ERROR 2247 << "getPowerSupplyAttributes respHandler DBus error " << ec; 2248 return; 2249 } 2250 if (subtree.empty()) 2251 { 2252 BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!"; 2253 callback(inventoryItems); 2254 return; 2255 } 2256 2257 // Currently we only support 1 power supply attribute, use this for 2258 // all the power supplies. Build map of object path to connection. 2259 // Assume just 1 connection and 1 path for now. 2260 boost::container::flat_map<std::string, std::string> 2261 psAttributesConnections; 2262 2263 if (subtree[0].first.empty() || subtree[0].second.empty()) 2264 { 2265 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2266 callback(inventoryItems); 2267 return; 2268 } 2269 2270 const std::string& psAttributesPath = subtree[0].first; 2271 const std::string& connection = subtree[0].second.begin()->first; 2272 2273 if (connection.empty()) 2274 { 2275 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2276 callback(inventoryItems); 2277 return; 2278 } 2279 2280 psAttributesConnections[psAttributesPath] = connection; 2281 BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> " 2282 << connection; 2283 2284 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 2285 psAttributesConnections, 2286 std::move(callback)); 2287 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit"; 2288 }; 2289 // Make call to ObjectMapper to find the PowerSupplyAttributes service 2290 crow::connections::systemBus->async_method_call( 2291 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2292 "/xyz/openbmc_project/object_mapper", 2293 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 2294 "/xyz/openbmc_project", 0, interfaces); 2295 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit"; 2296 } 2297 2298 /** 2299 * @brief Gets inventory items associated with sensors. 2300 * 2301 * Finds the inventory items that are associated with the specified sensors. 2302 * Then gets D-Bus data for the inventory items, such as presence and VPD. 2303 * 2304 * This data is later used to provide sensor property values in the JSON 2305 * response. 2306 * 2307 * Finds the inventory items asynchronously. Invokes callback when the 2308 * inventory items have been obtained. 2309 * 2310 * The callback must have the following signature: 2311 * @code 2312 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2313 * @endcode 2314 * 2315 * @param sensorsAsyncResp Pointer to object holding response data. 2316 * @param sensorNames All sensors within the current chassis. 2317 * @param objectMgrPaths Mappings from connection name to DBus object path that 2318 * implements ObjectManager. 2319 * @param callback Callback to invoke when inventory items have been obtained. 2320 */ 2321 template <typename Callback> 2322 static void getInventoryItems( 2323 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2324 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 2325 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2326 objectMgrPaths, 2327 Callback&& callback) 2328 { 2329 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 2330 auto getInventoryItemAssociationsCb = 2331 [sensorsAsyncResp, objectMgrPaths, 2332 callback{std::forward<Callback>(callback)}]( 2333 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) { 2334 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter"; 2335 auto getInventoryItemsConnectionsCb = 2336 [sensorsAsyncResp, inventoryItems, objectMgrPaths, 2337 callback{std::forward<const Callback>(callback)}]( 2338 std::shared_ptr<boost::container::flat_set<std::string>> 2339 invConnections) { 2340 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 2341 auto getInventoryItemsDataCb = 2342 [sensorsAsyncResp, inventoryItems, 2343 callback{std::move(callback)}]() { 2344 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter"; 2345 2346 auto getInventoryLedsCb = [sensorsAsyncResp, 2347 inventoryItems, 2348 callback{std::move( 2349 callback)}]() { 2350 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter"; 2351 // Find Power Supply Attributes and get the data 2352 getPowerSupplyAttributes(sensorsAsyncResp, 2353 inventoryItems, 2354 std::move(callback)); 2355 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit"; 2356 }; 2357 2358 // Find led connections and get the data 2359 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2360 std::move(getInventoryLedsCb)); 2361 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit"; 2362 }; 2363 2364 // Get inventory item data from connections 2365 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2366 invConnections, objectMgrPaths, 2367 std::move(getInventoryItemsDataCb)); 2368 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 2369 }; 2370 2371 // Get connections that provide inventory item data 2372 getInventoryItemsConnections( 2373 sensorsAsyncResp, inventoryItems, 2374 std::move(getInventoryItemsConnectionsCb)); 2375 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit"; 2376 }; 2377 2378 // Get associations from sensors to inventory items 2379 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths, 2380 std::move(getInventoryItemAssociationsCb)); 2381 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 2382 } 2383 2384 /** 2385 * @brief Returns JSON PowerSupply object for the specified inventory item. 2386 * 2387 * Searches for a JSON PowerSupply object that matches the specified inventory 2388 * item. If one is not found, a new PowerSupply object is added to the JSON 2389 * array. 2390 * 2391 * Multiple sensors are often associated with one power supply inventory item. 2392 * As a result, multiple sensor values are stored in one JSON PowerSupply 2393 * object. 2394 * 2395 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2396 * @param inventoryItem Inventory item for the power supply. 2397 * @param chassisId Chassis that contains the power supply. 2398 * @return JSON PowerSupply object for the specified inventory item. 2399 */ 2400 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2401 const InventoryItem& inventoryItem, 2402 const std::string& chassisId) 2403 { 2404 // Check if matching PowerSupply object already exists in JSON array 2405 for (nlohmann::json& powerSupply : powerSupplyArray) 2406 { 2407 if (powerSupply["MemberId"] == inventoryItem.name) 2408 { 2409 return powerSupply; 2410 } 2411 } 2412 2413 // Add new PowerSupply object to JSON array 2414 powerSupplyArray.push_back({}); 2415 nlohmann::json& powerSupply = powerSupplyArray.back(); 2416 powerSupply["@odata.id"] = 2417 "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/"; 2418 powerSupply["MemberId"] = inventoryItem.name; 2419 powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " "); 2420 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2421 powerSupply["Model"] = inventoryItem.model; 2422 powerSupply["PartNumber"] = inventoryItem.partNumber; 2423 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2424 setLedState(powerSupply, &inventoryItem); 2425 2426 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2427 { 2428 powerSupply["EfficiencyPercent"] = 2429 inventoryItem.powerSupplyEfficiencyPercent; 2430 } 2431 2432 powerSupply["Status"]["State"] = getState(&inventoryItem); 2433 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2434 powerSupply["Status"]["Health"] = health; 2435 2436 return powerSupply; 2437 } 2438 2439 /** 2440 * @brief Gets the values of the specified sensors. 2441 * 2442 * Stores the results as JSON in the SensorsAsyncResp. 2443 * 2444 * Gets the sensor values asynchronously. Stores the results later when the 2445 * information has been obtained. 2446 * 2447 * The sensorNames set contains all requested sensors for the current chassis. 2448 * 2449 * To minimize the number of DBus calls, the DBus method 2450 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2451 * values of all sensors provided by a connection (service). 2452 * 2453 * The connections set contains all the connections that provide sensor values. 2454 * 2455 * The objectMgrPaths map contains mappings from a connection name to the 2456 * corresponding DBus object path that implements ObjectManager. 2457 * 2458 * The InventoryItem vector contains D-Bus inventory items associated with the 2459 * sensors. Inventory item data is needed for some Redfish sensor properties. 2460 * 2461 * @param SensorsAsyncResp Pointer to object holding response data. 2462 * @param sensorNames All requested sensors within the current chassis. 2463 * @param connections Connections that provide sensor values. 2464 * @param objectMgrPaths Mappings from connection name to DBus object path that 2465 * implements ObjectManager. 2466 * @param inventoryItems Inventory items associated with the sensors. 2467 */ 2468 inline void getSensorData( 2469 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2470 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 2471 const boost::container::flat_set<std::string>& connections, 2472 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 2473 objectMgrPaths, 2474 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2475 { 2476 BMCWEB_LOG_DEBUG << "getSensorData enter"; 2477 // Get managed objects from all services exposing sensors 2478 for (const std::string& connection : connections) 2479 { 2480 // Response handler to process managed objects 2481 auto getManagedObjectsCb = [sensorsAsyncResp, sensorNames, 2482 inventoryItems]( 2483 const boost::system::error_code ec, 2484 dbus::utility::ManagedObjectType& resp) { 2485 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 2486 if (ec) 2487 { 2488 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 2489 messages::internalError(sensorsAsyncResp->asyncResp->res); 2490 return; 2491 } 2492 // Go through all objects and update response with sensor data 2493 for (const auto& objDictEntry : resp) 2494 { 2495 const std::string& objPath = 2496 static_cast<const std::string&>(objDictEntry.first); 2497 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 2498 << objPath; 2499 2500 std::vector<std::string> split; 2501 // Reserve space for 2502 // /xyz/openbmc_project/sensors/<name>/<subname> 2503 split.reserve(6); 2504 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 2505 if (split.size() < 6) 2506 { 2507 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 2508 << objPath; 2509 continue; 2510 } 2511 // These indexes aren't intuitive, as boost::split puts an empty 2512 // string at the beginning 2513 const std::string& sensorType = split[4]; 2514 const std::string& sensorName = split[5]; 2515 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 2516 << " sensorType " << sensorType; 2517 if (sensorNames->find(objPath) == sensorNames->end()) 2518 { 2519 BMCWEB_LOG_DEBUG << sensorName << " not in sensor list "; 2520 continue; 2521 } 2522 2523 // Find inventory item (if any) associated with sensor 2524 InventoryItem* inventoryItem = 2525 findInventoryItemForSensor(inventoryItems, objPath); 2526 2527 const std::string& sensorSchema = 2528 sensorsAsyncResp->chassisSubNode; 2529 2530 nlohmann::json* sensorJson = nullptr; 2531 2532 if (sensorSchema == sensors::node::sensors) 2533 { 2534 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2535 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + 2536 "/" + sensorsAsyncResp->chassisSubNode + "/" + 2537 sensorName; 2538 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2539 } 2540 else 2541 { 2542 std::string fieldName; 2543 if (sensorType == "temperature") 2544 { 2545 fieldName = "Temperatures"; 2546 } 2547 else if (sensorType == "fan" || sensorType == "fan_tach" || 2548 sensorType == "fan_pwm") 2549 { 2550 fieldName = "Fans"; 2551 } 2552 else if (sensorType == "voltage") 2553 { 2554 fieldName = "Voltages"; 2555 } 2556 else if (sensorType == "power") 2557 { 2558 if (!sensorName.compare("total_power")) 2559 { 2560 fieldName = "PowerControl"; 2561 } 2562 else if ((inventoryItem != nullptr) && 2563 (inventoryItem->isPowerSupply)) 2564 { 2565 fieldName = "PowerSupplies"; 2566 } 2567 else 2568 { 2569 // Other power sensors are in SensorCollection 2570 continue; 2571 } 2572 } 2573 else 2574 { 2575 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 2576 << sensorType; 2577 continue; 2578 } 2579 2580 nlohmann::json& tempArray = 2581 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2582 if (fieldName == "PowerControl") 2583 { 2584 if (tempArray.empty()) 2585 { 2586 // Put multiple "sensors" into a single 2587 // PowerControl. Follows MemberId naming and 2588 // naming in power.hpp. 2589 tempArray.push_back( 2590 {{"@odata.id", 2591 "/redfish/v1/Chassis/" + 2592 sensorsAsyncResp->chassisId + "/" + 2593 sensorsAsyncResp->chassisSubNode + "#/" + 2594 fieldName + "/0"}}); 2595 } 2596 sensorJson = &(tempArray.back()); 2597 } 2598 else if (fieldName == "PowerSupplies") 2599 { 2600 if (inventoryItem != nullptr) 2601 { 2602 sensorJson = 2603 &(getPowerSupply(tempArray, *inventoryItem, 2604 sensorsAsyncResp->chassisId)); 2605 } 2606 } 2607 else 2608 { 2609 tempArray.push_back( 2610 {{"@odata.id", 2611 "/redfish/v1/Chassis/" + 2612 sensorsAsyncResp->chassisId + "/" + 2613 sensorsAsyncResp->chassisSubNode + "#/" + 2614 fieldName + "/"}}); 2615 sensorJson = &(tempArray.back()); 2616 } 2617 } 2618 2619 if (sensorJson != nullptr) 2620 { 2621 objectInterfacesToJson( 2622 sensorName, sensorType, sensorsAsyncResp, 2623 objDictEntry.second, *sensorJson, inventoryItem); 2624 } 2625 } 2626 if (sensorsAsyncResp.use_count() == 1) 2627 { 2628 sortJSONResponse(sensorsAsyncResp); 2629 if (sensorsAsyncResp->chassisSubNode == sensors::node::thermal) 2630 { 2631 populateFanRedundancy(sensorsAsyncResp); 2632 } 2633 } 2634 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 2635 }; 2636 2637 // Find DBus object path that implements ObjectManager for the current 2638 // connection. If no mapping found, default to "/". 2639 auto iter = objectMgrPaths->find(connection); 2640 const std::string& objectMgrPath = 2641 (iter != objectMgrPaths->end()) ? iter->second : "/"; 2642 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 2643 << objectMgrPath; 2644 2645 crow::connections::systemBus->async_method_call( 2646 getManagedObjectsCb, connection, objectMgrPath, 2647 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2648 } 2649 BMCWEB_LOG_DEBUG << "getSensorData exit"; 2650 } 2651 2652 inline void processSensorList( 2653 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2654 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2655 { 2656 auto getConnectionCb = 2657 [sensorsAsyncResp, sensorNames]( 2658 const boost::container::flat_set<std::string>& connections) { 2659 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 2660 auto getObjectManagerPathsCb = 2661 [sensorsAsyncResp, sensorNames, 2662 connections](const std::shared_ptr<boost::container::flat_map< 2663 std::string, std::string>>& objectMgrPaths) { 2664 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 2665 auto getInventoryItemsCb = 2666 [sensorsAsyncResp, sensorNames, connections, 2667 objectMgrPaths]( 2668 const std::shared_ptr<std::vector<InventoryItem>>& 2669 inventoryItems) { 2670 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 2671 // Get sensor data and store results in JSON 2672 getSensorData(sensorsAsyncResp, sensorNames, 2673 connections, objectMgrPaths, 2674 inventoryItems); 2675 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 2676 }; 2677 2678 // Get inventory items associated with sensors 2679 getInventoryItems(sensorsAsyncResp, sensorNames, 2680 objectMgrPaths, 2681 std::move(getInventoryItemsCb)); 2682 2683 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 2684 }; 2685 2686 // Get mapping from connection names to the DBus object 2687 // paths that implement the ObjectManager interface 2688 getObjectManagerPaths(sensorsAsyncResp, 2689 std::move(getObjectManagerPathsCb)); 2690 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 2691 }; 2692 2693 // Get set of connections that provide sensor values 2694 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2695 } 2696 2697 /** 2698 * @brief Entry point for retrieving sensors data related to requested 2699 * chassis. 2700 * @param SensorsAsyncResp Pointer to object holding response data 2701 */ 2702 inline void 2703 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2704 { 2705 BMCWEB_LOG_DEBUG << "getChassisData enter"; 2706 auto getChassisCb = 2707 [sensorsAsyncResp]( 2708 const std::shared_ptr<boost::container::flat_set<std::string>>& 2709 sensorNames) { 2710 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2711 processSensorList(sensorsAsyncResp, sensorNames); 2712 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2713 }; 2714 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2715 nlohmann::json::array(); 2716 2717 // Get set of sensors in chassis 2718 getChassis(sensorsAsyncResp, std::move(getChassisCb)); 2719 BMCWEB_LOG_DEBUG << "getChassisData exit"; 2720 } 2721 2722 /** 2723 * @brief Find the requested sensorName in the list of all sensors supplied by 2724 * the chassis node 2725 * 2726 * @param sensorName The sensor name supplied in the PATCH request 2727 * @param sensorsList The list of sensors managed by the chassis node 2728 * @param sensorsModified The list of sensors that were found as a result of 2729 * repeated calls to this function 2730 */ 2731 inline bool findSensorNameUsingSensorPath( 2732 std::string_view sensorName, 2733 boost::container::flat_set<std::string>& sensorsList, 2734 boost::container::flat_set<std::string>& sensorsModified) 2735 { 2736 for (auto& chassisSensor : sensorsList) 2737 { 2738 sdbusplus::message::object_path path(chassisSensor); 2739 std::string thisSensorName = path.filename(); 2740 if (thisSensorName.empty()) 2741 { 2742 continue; 2743 } 2744 if (thisSensorName == sensorName) 2745 { 2746 sensorsModified.emplace(chassisSensor); 2747 return true; 2748 } 2749 } 2750 return false; 2751 } 2752 2753 /** 2754 * @brief Entry point for overriding sensor values of given sensor 2755 * 2756 * @param sensorAsyncResp response object 2757 * @param allCollections Collections extract from sensors' request patch info 2758 * @param chassisSubNode Chassis Node for which the query has to happen 2759 */ 2760 inline void setSensorsOverride( 2761 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2762 std::unordered_map<std::string, std::vector<nlohmann::json>>& 2763 allCollections) 2764 { 2765 BMCWEB_LOG_INFO << "setSensorsOverride for subNode" 2766 << sensorAsyncResp->chassisSubNode << "\n"; 2767 2768 const char* propertyValueName = nullptr; 2769 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2770 std::string memberId; 2771 double value = 0.0; 2772 for (auto& collectionItems : allCollections) 2773 { 2774 if (collectionItems.first == "Temperatures") 2775 { 2776 propertyValueName = "ReadingCelsius"; 2777 } 2778 else if (collectionItems.first == "Fans") 2779 { 2780 propertyValueName = "Reading"; 2781 } 2782 else 2783 { 2784 propertyValueName = "ReadingVolts"; 2785 } 2786 for (auto& item : collectionItems.second) 2787 { 2788 if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res, 2789 "MemberId", memberId, propertyValueName, 2790 value)) 2791 { 2792 return; 2793 } 2794 overrideMap.emplace(memberId, 2795 std::make_pair(value, collectionItems.first)); 2796 } 2797 } 2798 2799 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap]( 2800 const std::shared_ptr< 2801 boost::container::flat_set< 2802 std::string>>& sensorsList) { 2803 // Match sensor names in the PATCH request to those managed by the 2804 // chassis node 2805 const std::shared_ptr<boost::container::flat_set<std::string>> 2806 sensorNames = 2807 std::make_shared<boost::container::flat_set<std::string>>(); 2808 for (const auto& item : overrideMap) 2809 { 2810 const auto& sensor = item.first; 2811 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 2812 *sensorNames)) 2813 { 2814 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 2815 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2816 item.second.second, item.first); 2817 return; 2818 } 2819 } 2820 // Get the connection to which the memberId belongs 2821 auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap]( 2822 const boost::container::flat_set< 2823 std::string>& /*connections*/, 2824 const std::set<std::pair< 2825 std::string, std::string>>& 2826 objectsWithConnection) { 2827 if (objectsWithConnection.size() != overrideMap.size()) 2828 { 2829 BMCWEB_LOG_INFO 2830 << "Unable to find all objects with proper connection " 2831 << objectsWithConnection.size() << " requested " 2832 << overrideMap.size() << "\n"; 2833 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2834 sensorAsyncResp->chassisSubNode == 2835 sensors::node::thermal 2836 ? "Temperatures" 2837 : "Voltages", 2838 "Count"); 2839 return; 2840 } 2841 for (const auto& item : objectsWithConnection) 2842 { 2843 sdbusplus::message::object_path path(item.first); 2844 std::string sensorName = path.filename(); 2845 if (sensorName.empty()) 2846 { 2847 messages::internalError(sensorAsyncResp->asyncResp->res); 2848 return; 2849 } 2850 2851 const auto& iterator = overrideMap.find(sensorName); 2852 if (iterator == overrideMap.end()) 2853 { 2854 BMCWEB_LOG_INFO << "Unable to find sensor object" 2855 << item.first << "\n"; 2856 messages::internalError(sensorAsyncResp->asyncResp->res); 2857 return; 2858 } 2859 crow::connections::systemBus->async_method_call( 2860 [sensorAsyncResp](const boost::system::error_code ec) { 2861 if (ec) 2862 { 2863 if (ec.value() == 2864 boost::system::errc::permission_denied) 2865 { 2866 BMCWEB_LOG_WARNING 2867 << "Manufacturing mode is not Enabled...can't " 2868 "Override the sensor value. "; 2869 2870 messages::insufficientPrivilege( 2871 sensorAsyncResp->asyncResp->res); 2872 return; 2873 } 2874 BMCWEB_LOG_DEBUG 2875 << "setOverrideValueStatus DBUS error: " << ec; 2876 messages::internalError( 2877 sensorAsyncResp->asyncResp->res); 2878 } 2879 }, 2880 item.second, item.first, "org.freedesktop.DBus.Properties", 2881 "Set", "xyz.openbmc_project.Sensor.Value", "Value", 2882 dbus::utility::DbusVariantType(iterator->second.first)); 2883 } 2884 }; 2885 // Get object with connection for the given sensor name 2886 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2887 std::move(getObjectsWithConnectionCb)); 2888 }; 2889 // get full sensor list for the given chassisId and cross verify the sensor. 2890 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 2891 } 2892 2893 /** 2894 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2895 * path of the sensor. 2896 * 2897 * Function builds valid Redfish response for sensor query of given chassis and 2898 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2899 * it to caller in a callback. 2900 * 2901 * @param chassis Chassis for which retrieval should be performed 2902 * @param node Node (group) of sensors. See sensors::node for supported values 2903 * @param mapComplete Callback to be called with retrieval result 2904 */ 2905 inline void retrieveUriToDbusMap(const std::string& chassis, 2906 const std::string& node, 2907 SensorsAsyncResp::DataCompleteCb&& mapComplete) 2908 { 2909 auto pathIt = sensors::dbus::paths.find(node); 2910 if (pathIt == sensors::dbus::paths.end()) 2911 { 2912 BMCWEB_LOG_ERROR << "Wrong node provided : " << node; 2913 mapComplete(boost::beast::http::status::bad_request, {}); 2914 return; 2915 } 2916 2917 auto res = std::make_shared<crow::Response>(); 2918 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(*res); 2919 auto callback = 2920 [res, asyncResp, mapCompleteCb{std::move(mapComplete)}]( 2921 const boost::beast::http::status status, 2922 const boost::container::flat_map<std::string, std::string>& 2923 uriToDbus) { mapCompleteCb(status, uriToDbus); }; 2924 2925 auto resp = std::make_shared<SensorsAsyncResp>( 2926 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2927 getChassisData(resp); 2928 } 2929 2930 inline void requestRoutesSensorCollection(App& app) 2931 { 2932 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2933 .privileges(redfish::privileges::getSensorCollection) 2934 .methods( 2935 boost::beast::http::verb::get)([](const crow::Request&, 2936 const std::shared_ptr< 2937 bmcweb::AsyncResp>& aResp, 2938 const std::string& chassisId) { 2939 BMCWEB_LOG_DEBUG << "SensorCollection doGet enter"; 2940 2941 std::shared_ptr<SensorsAsyncResp> asyncResp = 2942 std::make_shared<SensorsAsyncResp>( 2943 aResp, chassisId, 2944 sensors::dbus::paths.at(sensors::node::sensors), 2945 sensors::node::sensors); 2946 2947 auto getChassisCb = 2948 [asyncResp]( 2949 const std::shared_ptr< 2950 boost::container::flat_set<std::string>>& sensorNames) { 2951 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2952 2953 nlohmann::json& entriesArray = 2954 asyncResp->asyncResp->res.jsonValue["Members"]; 2955 for (auto& sensor : *sensorNames) 2956 { 2957 BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; 2958 2959 sdbusplus::message::object_path path(sensor); 2960 std::string sensorName = path.filename(); 2961 if (sensorName.empty()) 2962 { 2963 BMCWEB_LOG_ERROR << "Invalid sensor path: " 2964 << sensor; 2965 messages::internalError(asyncResp->asyncResp->res); 2966 return; 2967 } 2968 entriesArray.push_back( 2969 {{"@odata.id", "/redfish/v1/Chassis/" + 2970 asyncResp->chassisId + "/" + 2971 asyncResp->chassisSubNode + "/" + 2972 sensorName}}); 2973 } 2974 2975 asyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 2976 entriesArray.size(); 2977 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2978 }; 2979 2980 // Get set of sensors in chassis 2981 getChassis(asyncResp, std::move(getChassisCb)); 2982 BMCWEB_LOG_DEBUG << "SensorCollection doGet exit"; 2983 }); 2984 } 2985 2986 inline void requestRoutesSensor(App& app) 2987 { 2988 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2989 .privileges(redfish::privileges::getSensor) 2990 .methods( 2991 boost::beast::http::verb::get)([](const crow::Request&, 2992 const std::shared_ptr< 2993 bmcweb::AsyncResp>& aResp, 2994 const std::string& chassisId, 2995 const std::string& sensorName) { 2996 BMCWEB_LOG_DEBUG << "Sensor doGet enter"; 2997 std::shared_ptr<SensorsAsyncResp> asyncResp = 2998 std::make_shared<SensorsAsyncResp>(aResp, chassisId, 2999 std::vector<const char*>(), 3000 sensors::node::sensors); 3001 3002 const std::array<const char*, 1> interfaces = { 3003 "xyz.openbmc_project.Sensor.Value"}; 3004 3005 // Get a list of all of the sensors that implement Sensor.Value 3006 // and get the path and service name associated with the sensor 3007 crow::connections::systemBus->async_method_call( 3008 [asyncResp, sensorName](const boost::system::error_code ec, 3009 const GetSubTreeType& subtree) { 3010 BMCWEB_LOG_DEBUG << "respHandler1 enter"; 3011 if (ec) 3012 { 3013 messages::internalError(asyncResp->asyncResp->res); 3014 BMCWEB_LOG_ERROR 3015 << "Sensor getSensorPaths resp_handler: " 3016 << "Dbus error " << ec; 3017 return; 3018 } 3019 3020 GetSubTreeType::const_iterator it = std::find_if( 3021 subtree.begin(), subtree.end(), 3022 [sensorName]( 3023 const std::pair< 3024 std::string, 3025 std::vector<std::pair< 3026 std::string, std::vector<std::string>>>>& 3027 object) { 3028 sdbusplus::message::object_path path(object.first); 3029 std::string name = path.filename(); 3030 if (name.empty()) 3031 { 3032 BMCWEB_LOG_ERROR << "Invalid sensor path: " 3033 << object.first; 3034 return false; 3035 } 3036 3037 return name == sensorName; 3038 }); 3039 3040 if (it == subtree.end()) 3041 { 3042 BMCWEB_LOG_ERROR << "Could not find path for sensor: " 3043 << sensorName; 3044 messages::resourceNotFound(asyncResp->asyncResp->res, 3045 "Sensor", sensorName); 3046 return; 3047 } 3048 std::string_view sensorPath = (*it).first; 3049 BMCWEB_LOG_DEBUG << "Found sensor path for sensor '" 3050 << sensorName << "': " << sensorPath; 3051 3052 const std::shared_ptr< 3053 boost::container::flat_set<std::string>> 3054 sensorList = std::make_shared< 3055 boost::container::flat_set<std::string>>(); 3056 3057 sensorList->emplace(sensorPath); 3058 processSensorList(asyncResp, sensorList); 3059 BMCWEB_LOG_DEBUG << "respHandler1 exit"; 3060 }, 3061 "xyz.openbmc_project.ObjectMapper", 3062 "/xyz/openbmc_project/object_mapper", 3063 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 3064 "/xyz/openbmc_project/sensors", 2, interfaces); 3065 }); 3066 } 3067 3068 } // namespace redfish 3069