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 (auto& [interface, values] : interfacesDict) 765 { 766 if (interface == "xyz.openbmc_project.Sensor.Threshold.Critical") 767 { 768 for (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 (auto& [interface, values] : interfacesDict) 802 { 803 if (interface == "xyz.openbmc_project.Sensor.Threshold.Warning") 804 { 805 for (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 (auto& [interface, values] : interfacesDict) 870 { 871 if (interface == "xyz.openbmc_project.Sensor.Value") 872 { 873 for (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 auto allowedFailures = std::get_if<uint8_t>( 1198 &(findFailures->second)); 1199 auto collection = 1200 std::get_if<std::vector<std::string>>( 1201 &(findCollection->second)); 1202 auto status = std::get_if<std::string>( 1203 &(findStatus->second)); 1204 1205 if (allowedFailures == nullptr || 1206 collection == nullptr || status == nullptr) 1207 { 1208 1209 BMCWEB_LOG_ERROR 1210 << "Invalid redundancy interface types"; 1211 messages::internalError( 1212 sensorsAsyncResp->asyncResp->res); 1213 return; 1214 } 1215 sdbusplus::message::object_path objectPath( 1216 path); 1217 std::string name = objectPath.filename(); 1218 if (name.empty()) 1219 { 1220 // this should be impossible 1221 messages::internalError( 1222 sensorsAsyncResp->asyncResp->res); 1223 return; 1224 } 1225 std::replace(name.begin(), name.end(), '_', 1226 ' '); 1227 1228 std::string health; 1229 1230 if (boost::ends_with(*status, "Full")) 1231 { 1232 health = "OK"; 1233 } 1234 else if (boost::ends_with(*status, "Degraded")) 1235 { 1236 health = "Warning"; 1237 } 1238 else 1239 { 1240 health = "Critical"; 1241 } 1242 std::vector<nlohmann::json> redfishCollection; 1243 const auto& fanRedfish = 1244 sensorsAsyncResp->asyncResp->res 1245 .jsonValue["Fans"]; 1246 for (const std::string& item : *collection) 1247 { 1248 sdbusplus::message::object_path path(item); 1249 std::string itemName = path.filename(); 1250 if (itemName.empty()) 1251 { 1252 continue; 1253 } 1254 /* 1255 todo(ed): merge patch that fixes the names 1256 std::replace(itemName.begin(), 1257 itemName.end(), '_', ' ');*/ 1258 auto schemaItem = std::find_if( 1259 fanRedfish.begin(), fanRedfish.end(), 1260 [itemName](const nlohmann::json& fan) { 1261 return fan["MemberId"] == itemName; 1262 }); 1263 if (schemaItem != fanRedfish.end()) 1264 { 1265 redfishCollection.push_back( 1266 {{"@odata.id", 1267 (*schemaItem)["@odata.id"]}}); 1268 } 1269 else 1270 { 1271 BMCWEB_LOG_ERROR 1272 << "failed to find fan in schema"; 1273 messages::internalError( 1274 sensorsAsyncResp->asyncResp->res); 1275 return; 1276 } 1277 } 1278 1279 size_t minNumNeeded = 1280 collection->size() > 0 1281 ? collection->size() - *allowedFailures 1282 : 0; 1283 nlohmann::json& jResp = 1284 sensorsAsyncResp->asyncResp->res 1285 .jsonValue["Redundancy"]; 1286 jResp.push_back( 1287 {{"@odata.id", 1288 "/redfish/v1/Chassis/" + 1289 sensorsAsyncResp->chassisId + "/" + 1290 sensorsAsyncResp->chassisSubNode + 1291 "#/Redundancy/" + 1292 std::to_string(jResp.size())}, 1293 {"@odata.type", 1294 "#Redundancy.v1_3_2.Redundancy"}, 1295 {"MinNumNeeded", minNumNeeded}, 1296 {"MemberId", name}, 1297 {"Mode", "N+m"}, 1298 {"Name", name}, 1299 {"RedundancySet", redfishCollection}, 1300 {"Status", 1301 {{"Health", health}, 1302 {"State", "Enabled"}}}}); 1303 }, 1304 owner, path, "org.freedesktop.DBus.Properties", 1305 "GetAll", 1306 "xyz.openbmc_project.Control.FanRedundancy"); 1307 }); 1308 } 1309 }, 1310 "xyz.openbmc_project.ObjectMapper", 1311 "/xyz/openbmc_project/object_mapper", 1312 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1313 "/xyz/openbmc_project/control", 2, 1314 std::array<const char*, 1>{ 1315 "xyz.openbmc_project.Control.FanRedundancy"}); 1316 } 1317 1318 inline void 1319 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1320 { 1321 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 1322 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 1323 if (sensorsAsyncResp->chassisSubNode == sensors::node::power) 1324 { 1325 sensorHeaders = {"Voltages", "PowerSupplies"}; 1326 } 1327 for (const std::string& sensorGroup : sensorHeaders) 1328 { 1329 nlohmann::json::iterator entry = response.find(sensorGroup); 1330 if (entry != response.end()) 1331 { 1332 std::sort(entry->begin(), entry->end(), 1333 [](nlohmann::json& c1, nlohmann::json& c2) { 1334 return c1["Name"] < c2["Name"]; 1335 }); 1336 1337 // add the index counts to the end of each entry 1338 size_t count = 0; 1339 for (nlohmann::json& sensorJson : *entry) 1340 { 1341 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 1342 if (odata == sensorJson.end()) 1343 { 1344 continue; 1345 } 1346 std::string* value = odata->get_ptr<std::string*>(); 1347 if (value != nullptr) 1348 { 1349 *value += std::to_string(count); 1350 count++; 1351 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 1352 } 1353 } 1354 } 1355 } 1356 } 1357 1358 /** 1359 * @brief Finds the inventory item with the specified object path. 1360 * @param inventoryItems D-Bus inventory items associated with sensors. 1361 * @param invItemObjPath D-Bus object path of inventory item. 1362 * @return Inventory item within vector, or nullptr if no match found. 1363 */ 1364 inline InventoryItem* findInventoryItem( 1365 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1366 const std::string& invItemObjPath) 1367 { 1368 for (InventoryItem& inventoryItem : *inventoryItems) 1369 { 1370 if (inventoryItem.objectPath == invItemObjPath) 1371 { 1372 return &inventoryItem; 1373 } 1374 } 1375 return nullptr; 1376 } 1377 1378 /** 1379 * @brief Finds the inventory item associated with the specified sensor. 1380 * @param inventoryItems D-Bus inventory items associated with sensors. 1381 * @param sensorObjPath D-Bus object path of sensor. 1382 * @return Inventory item within vector, or nullptr if no match found. 1383 */ 1384 inline InventoryItem* findInventoryItemForSensor( 1385 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1386 const std::string& sensorObjPath) 1387 { 1388 for (InventoryItem& inventoryItem : *inventoryItems) 1389 { 1390 if (inventoryItem.sensors.count(sensorObjPath) > 0) 1391 { 1392 return &inventoryItem; 1393 } 1394 } 1395 return nullptr; 1396 } 1397 1398 /** 1399 * @brief Finds the inventory item associated with the specified led path. 1400 * @param inventoryItems D-Bus inventory items associated with sensors. 1401 * @param ledObjPath D-Bus object path of led. 1402 * @return Inventory item within vector, or nullptr if no match found. 1403 */ 1404 inline InventoryItem* 1405 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems, 1406 const std::string& ledObjPath) 1407 { 1408 for (InventoryItem& inventoryItem : inventoryItems) 1409 { 1410 if (inventoryItem.ledObjectPath == ledObjPath) 1411 { 1412 return &inventoryItem; 1413 } 1414 } 1415 return nullptr; 1416 } 1417 1418 /** 1419 * @brief Adds inventory item and associated sensor to specified vector. 1420 * 1421 * Adds a new InventoryItem to the vector if necessary. Searches for an 1422 * existing InventoryItem with the specified object path. If not found, one is 1423 * added to the vector. 1424 * 1425 * Next, the specified sensor is added to the set of sensors associated with the 1426 * InventoryItem. 1427 * 1428 * @param inventoryItems D-Bus inventory items associated with sensors. 1429 * @param invItemObjPath D-Bus object path of inventory item. 1430 * @param sensorObjPath D-Bus object path of sensor 1431 */ 1432 inline void addInventoryItem( 1433 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1434 const std::string& invItemObjPath, const std::string& sensorObjPath) 1435 { 1436 // Look for inventory item in vector 1437 InventoryItem* inventoryItem = 1438 findInventoryItem(inventoryItems, invItemObjPath); 1439 1440 // If inventory item doesn't exist in vector, add it 1441 if (inventoryItem == nullptr) 1442 { 1443 inventoryItems->emplace_back(invItemObjPath); 1444 inventoryItem = &(inventoryItems->back()); 1445 } 1446 1447 // Add sensor to set of sensors associated with inventory item 1448 inventoryItem->sensors.emplace(sensorObjPath); 1449 } 1450 1451 /** 1452 * @brief Stores D-Bus data in the specified inventory item. 1453 * 1454 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 1455 * specified InventoryItem. 1456 * 1457 * This data is later used to provide sensor property values in the JSON 1458 * response. 1459 * 1460 * @param inventoryItem Inventory item where data will be stored. 1461 * @param interfacesDict Map containing D-Bus interfaces and their properties 1462 * for the specified inventory item. 1463 */ 1464 inline void storeInventoryItemData( 1465 InventoryItem& inventoryItem, 1466 const dbus::utility::DBusInteracesMap& interfacesDict) 1467 { 1468 // Get properties from Inventory.Item interface 1469 1470 for (auto& [interface, values] : interfacesDict) 1471 { 1472 if (interface == "xyz.openbmc_project.Inventory.Item") 1473 { 1474 for (auto& [name, dbusValue] : values) 1475 { 1476 if (name == "Present") 1477 { 1478 const bool* value = std::get_if<bool>(&dbusValue); 1479 if (value != nullptr) 1480 { 1481 inventoryItem.isPresent = *value; 1482 } 1483 } 1484 } 1485 } 1486 // Check if Inventory.Item.PowerSupply interface is present 1487 1488 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 1489 { 1490 inventoryItem.isPowerSupply = true; 1491 } 1492 1493 // Get properties from Inventory.Decorator.Asset interface 1494 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 1495 { 1496 for (auto& [name, dbusValue] : values) 1497 { 1498 if (name == "Manufacturer") 1499 { 1500 const std::string* value = 1501 std::get_if<std::string>(&dbusValue); 1502 if (value != nullptr) 1503 { 1504 inventoryItem.manufacturer = *value; 1505 } 1506 } 1507 if (name == "Model") 1508 { 1509 const std::string* value = 1510 std::get_if<std::string>(&dbusValue); 1511 if (value != nullptr) 1512 { 1513 inventoryItem.model = *value; 1514 } 1515 } 1516 if (name == "SerialNumber") 1517 { 1518 const std::string* value = 1519 std::get_if<std::string>(&dbusValue); 1520 if (value != nullptr) 1521 { 1522 inventoryItem.serialNumber = *value; 1523 } 1524 } 1525 if (name == "PartNumber") 1526 { 1527 const std::string* value = 1528 std::get_if<std::string>(&dbusValue); 1529 if (value != nullptr) 1530 { 1531 inventoryItem.partNumber = *value; 1532 } 1533 } 1534 } 1535 } 1536 1537 if (interface == 1538 "xyz.openbmc_project.State.Decorator.OperationalStatus") 1539 { 1540 for (auto& [name, dbusValue] : values) 1541 { 1542 if (name == "Functional") 1543 { 1544 const bool* value = std::get_if<bool>(&dbusValue); 1545 if (value != nullptr) 1546 { 1547 inventoryItem.isFunctional = *value; 1548 } 1549 } 1550 } 1551 } 1552 } 1553 } 1554 1555 /** 1556 * @brief Gets D-Bus data for inventory items associated with sensors. 1557 * 1558 * Uses the specified connections (services) to obtain D-Bus data for inventory 1559 * items associated with sensors. Stores the resulting data in the 1560 * inventoryItems vector. 1561 * 1562 * This data is later used to provide sensor property values in the JSON 1563 * response. 1564 * 1565 * Finds the inventory item data asynchronously. Invokes callback when data has 1566 * been obtained. 1567 * 1568 * The callback must have the following signature: 1569 * @code 1570 * callback(void) 1571 * @endcode 1572 * 1573 * This function is called recursively, obtaining data asynchronously from one 1574 * connection in each call. This ensures the callback is not invoked until the 1575 * last asynchronous function has completed. 1576 * 1577 * @param sensorsAsyncResp Pointer to object holding response data. 1578 * @param inventoryItems D-Bus inventory items associated with sensors. 1579 * @param invConnections Connections that provide data for the inventory items. 1580 * @param objectMgrPaths Mappings from connection name to DBus object path that 1581 * implements ObjectManager. 1582 * @param callback Callback to invoke when inventory data has been obtained. 1583 * @param invConnectionsIndex Current index in invConnections. Only specified 1584 * in recursive calls to this function. 1585 */ 1586 template <typename Callback> 1587 static void getInventoryItemsData( 1588 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1589 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1590 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1591 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1592 objectMgrPaths, 1593 Callback&& callback, size_t invConnectionsIndex = 0) 1594 { 1595 BMCWEB_LOG_DEBUG << "getInventoryItemsData enter"; 1596 1597 // If no more connections left, call callback 1598 if (invConnectionsIndex >= invConnections->size()) 1599 { 1600 callback(); 1601 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1602 return; 1603 } 1604 1605 // Get inventory item data from current connection 1606 auto it = invConnections->nth(invConnectionsIndex); 1607 if (it != invConnections->end()) 1608 { 1609 const std::string& invConnection = *it; 1610 1611 // Response handler for GetManagedObjects 1612 auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections, 1613 objectMgrPaths, 1614 callback{std::forward<Callback>(callback)}, 1615 invConnectionsIndex]( 1616 const boost::system::error_code ec, 1617 dbus::utility::ManagedObjectType& resp) { 1618 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter"; 1619 if (ec) 1620 { 1621 BMCWEB_LOG_ERROR 1622 << "getInventoryItemsData respHandler DBus error " << ec; 1623 messages::internalError(sensorsAsyncResp->asyncResp->res); 1624 return; 1625 } 1626 1627 // Loop through returned object paths 1628 for (const auto& objDictEntry : resp) 1629 { 1630 const std::string& objPath = 1631 static_cast<const std::string&>(objDictEntry.first); 1632 1633 // If this object path is one of the specified inventory items 1634 InventoryItem* inventoryItem = 1635 findInventoryItem(inventoryItems, objPath); 1636 if (inventoryItem != nullptr) 1637 { 1638 // Store inventory data in InventoryItem 1639 storeInventoryItemData(*inventoryItem, objDictEntry.second); 1640 } 1641 } 1642 1643 // Recurse to get inventory item data from next connection 1644 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1645 invConnections, objectMgrPaths, 1646 std::move(callback), invConnectionsIndex + 1); 1647 1648 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit"; 1649 }; 1650 1651 // Find DBus object path that implements ObjectManager for the current 1652 // connection. If no mapping found, default to "/". 1653 auto iter = objectMgrPaths->find(invConnection); 1654 const std::string& objectMgrPath = 1655 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1656 BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is " 1657 << objectMgrPath; 1658 1659 // Get all object paths and their interfaces for current connection 1660 crow::connections::systemBus->async_method_call( 1661 std::move(respHandler), invConnection, objectMgrPath, 1662 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1663 } 1664 1665 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1666 } 1667 1668 /** 1669 * @brief Gets connections that provide D-Bus data for inventory items. 1670 * 1671 * Gets the D-Bus connections (services) that provide data for the inventory 1672 * items that are associated with sensors. 1673 * 1674 * Finds the connections asynchronously. Invokes callback when information has 1675 * been obtained. 1676 * 1677 * The callback must have the following signature: 1678 * @code 1679 * callback(std::shared_ptr<boost::container::flat_set<std::string>> 1680 * invConnections) 1681 * @endcode 1682 * 1683 * @param sensorsAsyncResp Pointer to object holding response data. 1684 * @param inventoryItems D-Bus inventory items associated with sensors. 1685 * @param callback Callback to invoke when connections have been obtained. 1686 */ 1687 template <typename Callback> 1688 static void getInventoryItemsConnections( 1689 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1690 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1691 Callback&& callback) 1692 { 1693 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1694 1695 const std::string path = "/xyz/openbmc_project/inventory"; 1696 const std::array<std::string, 4> interfaces = { 1697 "xyz.openbmc_project.Inventory.Item", 1698 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1699 "xyz.openbmc_project.Inventory.Decorator.Asset", 1700 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1701 1702 // Response handler for parsing output from GetSubTree 1703 auto respHandler = [callback{std::forward<Callback>(callback)}, 1704 sensorsAsyncResp, 1705 inventoryItems](const boost::system::error_code ec, 1706 const GetSubTreeType& subtree) { 1707 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1708 if (ec) 1709 { 1710 messages::internalError(sensorsAsyncResp->asyncResp->res); 1711 BMCWEB_LOG_ERROR 1712 << "getInventoryItemsConnections respHandler DBus error " << ec; 1713 return; 1714 } 1715 1716 // Make unique list of connections for desired inventory items 1717 std::shared_ptr<boost::container::flat_set<std::string>> 1718 invConnections = 1719 std::make_shared<boost::container::flat_set<std::string>>(); 1720 invConnections->reserve(8); 1721 1722 // Loop through objects from GetSubTree 1723 for (const std::pair< 1724 std::string, 1725 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1726 object : subtree) 1727 { 1728 // Check if object path is one of the specified inventory items 1729 const std::string& objPath = object.first; 1730 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1731 { 1732 // Store all connections to inventory item 1733 for (const std::pair<std::string, std::vector<std::string>>& 1734 objData : object.second) 1735 { 1736 const std::string& invConnection = objData.first; 1737 invConnections->insert(invConnection); 1738 } 1739 } 1740 } 1741 1742 callback(invConnections); 1743 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1744 }; 1745 1746 // Make call to ObjectMapper to find all inventory items 1747 crow::connections::systemBus->async_method_call( 1748 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 1749 "/xyz/openbmc_project/object_mapper", 1750 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 1751 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1752 } 1753 1754 /** 1755 * @brief Gets associations from sensors to inventory items. 1756 * 1757 * Looks for ObjectMapper associations from the specified sensors to related 1758 * inventory items. Then finds the associations from those inventory items to 1759 * their LEDs, if any. 1760 * 1761 * Finds the inventory items asynchronously. Invokes callback when information 1762 * has been obtained. 1763 * 1764 * The callback must have the following signature: 1765 * @code 1766 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1767 * @endcode 1768 * 1769 * @param sensorsAsyncResp Pointer to object holding response data. 1770 * @param sensorNames All sensors within the current chassis. 1771 * @param objectMgrPaths Mappings from connection name to DBus object path that 1772 * implements ObjectManager. 1773 * @param callback Callback to invoke when inventory items have been obtained. 1774 */ 1775 template <typename Callback> 1776 static void getInventoryItemAssociations( 1777 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1778 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 1779 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 1780 objectMgrPaths, 1781 Callback&& callback) 1782 { 1783 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter"; 1784 1785 // Response handler for GetManagedObjects 1786 auto respHandler = [callback{std::forward<Callback>(callback)}, 1787 sensorsAsyncResp, 1788 sensorNames](const boost::system::error_code ec, 1789 dbus::utility::ManagedObjectType& resp) { 1790 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter"; 1791 if (ec) 1792 { 1793 BMCWEB_LOG_ERROR 1794 << "getInventoryItemAssociations respHandler DBus error " << ec; 1795 messages::internalError(sensorsAsyncResp->asyncResp->res); 1796 return; 1797 } 1798 1799 // Create vector to hold list of inventory items 1800 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1801 std::make_shared<std::vector<InventoryItem>>(); 1802 1803 // Loop through returned object paths 1804 std::string sensorAssocPath; 1805 sensorAssocPath.reserve(128); // avoid memory allocations 1806 for (const auto& objDictEntry : resp) 1807 { 1808 const std::string& objPath = 1809 static_cast<const std::string&>(objDictEntry.first); 1810 1811 // If path is inventory association for one of the specified sensors 1812 for (const std::string& sensorName : *sensorNames) 1813 { 1814 sensorAssocPath = sensorName; 1815 sensorAssocPath += "/inventory"; 1816 if (objPath == sensorAssocPath) 1817 { 1818 // Get Association interface for object path 1819 for (const auto& [interface, values] : objDictEntry.second) 1820 { 1821 if (interface == "xyz.openbmc_project.Association") 1822 { 1823 for (const auto& [valueName, value] : values) 1824 { 1825 if (valueName == "endpoints") 1826 { 1827 const std::vector<std::string>* endpoints = 1828 std::get_if<std::vector<std::string>>( 1829 &value); 1830 if ((endpoints != nullptr) && 1831 !endpoints->empty()) 1832 { 1833 // Add inventory item to vector 1834 const std::string& invItemPath = 1835 endpoints->front(); 1836 addInventoryItem(inventoryItems, 1837 invItemPath, 1838 sensorName); 1839 } 1840 } 1841 } 1842 } 1843 } 1844 break; 1845 } 1846 } 1847 } 1848 1849 // Now loop through the returned object paths again, this time to 1850 // find the leds associated with the inventory items we just found 1851 std::string inventoryAssocPath; 1852 inventoryAssocPath.reserve(128); // avoid memory allocations 1853 for (const auto& objDictEntry : resp) 1854 { 1855 const std::string& objPath = 1856 static_cast<const std::string&>(objDictEntry.first); 1857 1858 for (InventoryItem& inventoryItem : *inventoryItems) 1859 { 1860 inventoryAssocPath = inventoryItem.objectPath; 1861 inventoryAssocPath += "/leds"; 1862 if (objPath == inventoryAssocPath) 1863 { 1864 for (const auto& [interface, values] : objDictEntry.second) 1865 { 1866 if (interface == "xyz.openbmc_project.Association") 1867 { 1868 for (const auto& [valueName, value] : values) 1869 { 1870 if (valueName == "endpoints") 1871 { 1872 const std::vector<std::string>* endpoints = 1873 std::get_if<std::vector<std::string>>( 1874 &value); 1875 if ((endpoints != nullptr) && 1876 !endpoints->empty()) 1877 { 1878 // Add inventory item to vector 1879 // Store LED path in inventory item 1880 const std::string& ledPath = 1881 endpoints->front(); 1882 inventoryItem.ledObjectPath = ledPath; 1883 } 1884 } 1885 } 1886 } 1887 } 1888 1889 break; 1890 } 1891 } 1892 } 1893 callback(inventoryItems); 1894 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit"; 1895 }; 1896 1897 // Find DBus object path that implements ObjectManager for ObjectMapper 1898 std::string connection = "xyz.openbmc_project.ObjectMapper"; 1899 auto iter = objectMgrPaths->find(connection); 1900 const std::string& objectMgrPath = 1901 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1902 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1903 << objectMgrPath; 1904 1905 // Call GetManagedObjects on the ObjectMapper to get all associations 1906 crow::connections::systemBus->async_method_call( 1907 std::move(respHandler), connection, objectMgrPath, 1908 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1909 1910 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit"; 1911 } 1912 1913 /** 1914 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1915 * 1916 * Uses the specified connections (services) to obtain D-Bus data for inventory 1917 * item leds associated with sensors. Stores the resulting data in the 1918 * inventoryItems vector. 1919 * 1920 * This data is later used to provide sensor property values in the JSON 1921 * response. 1922 * 1923 * Finds the inventory item led data asynchronously. Invokes callback when data 1924 * has been obtained. 1925 * 1926 * The callback must have the following signature: 1927 * @code 1928 * callback() 1929 * @endcode 1930 * 1931 * This function is called recursively, obtaining data asynchronously from one 1932 * connection in each call. This ensures the callback is not invoked until the 1933 * last asynchronous function has completed. 1934 * 1935 * @param sensorsAsyncResp Pointer to object holding response data. 1936 * @param inventoryItems D-Bus inventory items associated with sensors. 1937 * @param ledConnections Connections that provide data for the inventory leds. 1938 * @param callback Callback to invoke when inventory data has been obtained. 1939 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1940 * in recursive calls to this function. 1941 */ 1942 template <typename Callback> 1943 void getInventoryLedData( 1944 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1945 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1946 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1947 ledConnections, 1948 Callback&& callback, size_t ledConnectionsIndex = 0) 1949 { 1950 BMCWEB_LOG_DEBUG << "getInventoryLedData enter"; 1951 1952 // If no more connections left, call callback 1953 if (ledConnectionsIndex >= ledConnections->size()) 1954 { 1955 callback(); 1956 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 1957 return; 1958 } 1959 1960 // Get inventory item data from current connection 1961 auto it = ledConnections->nth(ledConnectionsIndex); 1962 if (it != ledConnections->end()) 1963 { 1964 const std::string& ledPath = (*it).first; 1965 const std::string& ledConnection = (*it).second; 1966 // Response handler for Get State property 1967 auto respHandler = 1968 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1969 callback{std::forward<Callback>(callback)}, ledConnectionsIndex]( 1970 const boost::system::error_code ec, const std::string& state) { 1971 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter"; 1972 if (ec) 1973 { 1974 BMCWEB_LOG_ERROR 1975 << "getInventoryLedData respHandler DBus error " << ec; 1976 messages::internalError(sensorsAsyncResp->asyncResp->res); 1977 return; 1978 } 1979 1980 BMCWEB_LOG_DEBUG << "Led state: " << state; 1981 // Find inventory item with this LED object path 1982 InventoryItem* inventoryItem = 1983 findInventoryItemForLed(*inventoryItems, ledPath); 1984 if (inventoryItem != nullptr) 1985 { 1986 // Store LED state in InventoryItem 1987 if (boost::ends_with(state, "On")) 1988 { 1989 inventoryItem->ledState = LedState::ON; 1990 } 1991 else if (boost::ends_with(state, "Blink")) 1992 { 1993 inventoryItem->ledState = LedState::BLINK; 1994 } 1995 else if (boost::ends_with(state, "Off")) 1996 { 1997 inventoryItem->ledState = LedState::OFF; 1998 } 1999 else 2000 { 2001 inventoryItem->ledState = LedState::UNKNOWN; 2002 } 2003 } 2004 2005 // Recurse to get LED data from next connection 2006 getInventoryLedData(sensorsAsyncResp, inventoryItems, 2007 ledConnections, std::move(callback), 2008 ledConnectionsIndex + 1); 2009 2010 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit"; 2011 }; 2012 2013 // Get the State property for the current LED 2014 sdbusplus::asio::getProperty<std::string>( 2015 *crow::connections::systemBus, ledConnection, ledPath, 2016 "xyz.openbmc_project.Led.Physical", "State", 2017 std::move(respHandler)); 2018 } 2019 2020 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 2021 } 2022 2023 /** 2024 * @brief Gets LED data for LEDs associated with given inventory items. 2025 * 2026 * Gets the D-Bus connections (services) that provide LED data for the LEDs 2027 * associated with the specified inventory items. Then gets the LED data from 2028 * each connection and stores it in the inventory item. 2029 * 2030 * This data is later used to provide sensor property values in the JSON 2031 * response. 2032 * 2033 * Finds the LED data asynchronously. Invokes callback when information has 2034 * been obtained. 2035 * 2036 * The callback must have the following signature: 2037 * @code 2038 * callback() 2039 * @endcode 2040 * 2041 * @param sensorsAsyncResp Pointer to object holding response data. 2042 * @param inventoryItems D-Bus inventory items associated with sensors. 2043 * @param callback Callback to invoke when inventory items have been obtained. 2044 */ 2045 template <typename Callback> 2046 void getInventoryLeds( 2047 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2048 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2049 Callback&& callback) 2050 { 2051 BMCWEB_LOG_DEBUG << "getInventoryLeds enter"; 2052 2053 const std::string path = "/xyz/openbmc_project"; 2054 const std::array<std::string, 1> interfaces = { 2055 "xyz.openbmc_project.Led.Physical"}; 2056 2057 // Response handler for parsing output from GetSubTree 2058 auto respHandler = [callback{std::forward<Callback>(callback)}, 2059 sensorsAsyncResp, 2060 inventoryItems](const boost::system::error_code ec, 2061 const GetSubTreeType& subtree) { 2062 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter"; 2063 if (ec) 2064 { 2065 messages::internalError(sensorsAsyncResp->asyncResp->res); 2066 BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error " 2067 << ec; 2068 return; 2069 } 2070 2071 // Build map of LED object paths to connections 2072 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2073 ledConnections = std::make_shared< 2074 boost::container::flat_map<std::string, std::string>>(); 2075 2076 // Loop through objects from GetSubTree 2077 for (const std::pair< 2078 std::string, 2079 std::vector<std::pair<std::string, std::vector<std::string>>>>& 2080 object : subtree) 2081 { 2082 // Check if object path is LED for one of the specified inventory 2083 // items 2084 const std::string& ledPath = object.first; 2085 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr) 2086 { 2087 // Add mapping from ledPath to connection 2088 const std::string& connection = object.second.begin()->first; 2089 (*ledConnections)[ledPath] = connection; 2090 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> " 2091 << connection; 2092 } 2093 } 2094 2095 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections, 2096 std::move(callback)); 2097 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit"; 2098 }; 2099 // Make call to ObjectMapper to find all inventory items 2100 crow::connections::systemBus->async_method_call( 2101 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2102 "/xyz/openbmc_project/object_mapper", 2103 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 2104 BMCWEB_LOG_DEBUG << "getInventoryLeds exit"; 2105 } 2106 2107 /** 2108 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 2109 * 2110 * Uses the specified connections (services) (currently assumes just one) to 2111 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 2112 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 2113 * 2114 * This data is later used to provide sensor property values in the JSON 2115 * response. 2116 * 2117 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2118 * when data has been obtained. 2119 * 2120 * The callback must have the following signature: 2121 * @code 2122 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2123 * @endcode 2124 * 2125 * @param sensorsAsyncResp Pointer to object holding response data. 2126 * @param inventoryItems D-Bus inventory items associated with sensors. 2127 * @param psAttributesConnections Connections that provide data for the Power 2128 * Supply Attributes 2129 * @param callback Callback to invoke when data has been obtained. 2130 */ 2131 template <typename Callback> 2132 void getPowerSupplyAttributesData( 2133 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2134 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2135 const boost::container::flat_map<std::string, std::string>& 2136 psAttributesConnections, 2137 Callback&& callback) 2138 { 2139 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter"; 2140 2141 if (psAttributesConnections.empty()) 2142 { 2143 BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!"; 2144 callback(inventoryItems); 2145 return; 2146 } 2147 2148 // Assuming just one connection (service) for now 2149 auto it = psAttributesConnections.nth(0); 2150 2151 const std::string& psAttributesPath = (*it).first; 2152 const std::string& psAttributesConnection = (*it).second; 2153 2154 // Response handler for Get DeratingFactor property 2155 auto respHandler = [sensorsAsyncResp, inventoryItems, 2156 callback{std::forward<Callback>(callback)}]( 2157 const boost::system::error_code ec, 2158 const uint32_t value) { 2159 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter"; 2160 if (ec) 2161 { 2162 BMCWEB_LOG_ERROR 2163 << "getPowerSupplyAttributesData respHandler DBus error " << ec; 2164 messages::internalError(sensorsAsyncResp->asyncResp->res); 2165 return; 2166 } 2167 2168 BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value; 2169 // Store value in Power Supply Inventory Items 2170 for (InventoryItem& inventoryItem : *inventoryItems) 2171 { 2172 if (inventoryItem.isPowerSupply == true) 2173 { 2174 inventoryItem.powerSupplyEfficiencyPercent = 2175 static_cast<int>(value); 2176 } 2177 } 2178 2179 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit"; 2180 callback(inventoryItems); 2181 }; 2182 2183 // Get the DeratingFactor property for the PowerSupplyAttributes 2184 // Currently only property on the interface/only one we care about 2185 sdbusplus::asio::getProperty<uint32_t>( 2186 *crow::connections::systemBus, psAttributesConnection, psAttributesPath, 2187 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 2188 std::move(respHandler)); 2189 2190 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit"; 2191 } 2192 2193 /** 2194 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 2195 * 2196 * Gets the D-Bus connection (service) that provides Power Supply Attributes 2197 * data. Then gets the Power Supply Attributes data from the connection 2198 * (currently just assumes 1 connection) and stores the data in the inventory 2199 * item. 2200 * 2201 * This data is later used to provide sensor property values in the JSON 2202 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 2203 * 2204 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2205 * when information has been obtained. 2206 * 2207 * The callback must have the following signature: 2208 * @code 2209 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2210 * @endcode 2211 * 2212 * @param sensorsAsyncResp Pointer to object holding response data. 2213 * @param inventoryItems D-Bus inventory items associated with sensors. 2214 * @param callback Callback to invoke when data has been obtained. 2215 */ 2216 template <typename Callback> 2217 void getPowerSupplyAttributes( 2218 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2219 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2220 Callback&& callback) 2221 { 2222 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter"; 2223 2224 // Only need the power supply attributes when the Power Schema 2225 if (sensorsAsyncResp->chassisSubNode != sensors::node::power) 2226 { 2227 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power"; 2228 callback(inventoryItems); 2229 return; 2230 } 2231 2232 const std::array<std::string, 1> interfaces = { 2233 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 2234 2235 // Response handler for parsing output from GetSubTree 2236 auto respHandler = [callback{std::forward<Callback>(callback)}, 2237 sensorsAsyncResp, 2238 inventoryItems](const boost::system::error_code ec, 2239 const GetSubTreeType& subtree) { 2240 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter"; 2241 if (ec) 2242 { 2243 messages::internalError(sensorsAsyncResp->asyncResp->res); 2244 BMCWEB_LOG_ERROR 2245 << "getPowerSupplyAttributes respHandler DBus error " << ec; 2246 return; 2247 } 2248 if (subtree.size() == 0) 2249 { 2250 BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!"; 2251 callback(inventoryItems); 2252 return; 2253 } 2254 2255 // Currently we only support 1 power supply attribute, use this for 2256 // all the power supplies. Build map of object path to connection. 2257 // Assume just 1 connection and 1 path for now. 2258 boost::container::flat_map<std::string, std::string> 2259 psAttributesConnections; 2260 2261 if (subtree[0].first.empty() || subtree[0].second.empty()) 2262 { 2263 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2264 callback(inventoryItems); 2265 return; 2266 } 2267 2268 const std::string& psAttributesPath = subtree[0].first; 2269 const std::string& connection = subtree[0].second.begin()->first; 2270 2271 if (connection.empty()) 2272 { 2273 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2274 callback(inventoryItems); 2275 return; 2276 } 2277 2278 psAttributesConnections[psAttributesPath] = connection; 2279 BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> " 2280 << connection; 2281 2282 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 2283 psAttributesConnections, 2284 std::move(callback)); 2285 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit"; 2286 }; 2287 // Make call to ObjectMapper to find the PowerSupplyAttributes service 2288 crow::connections::systemBus->async_method_call( 2289 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2290 "/xyz/openbmc_project/object_mapper", 2291 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 2292 "/xyz/openbmc_project", 0, interfaces); 2293 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit"; 2294 } 2295 2296 /** 2297 * @brief Gets inventory items associated with sensors. 2298 * 2299 * Finds the inventory items that are associated with the specified sensors. 2300 * Then gets D-Bus data for the inventory items, such as presence and VPD. 2301 * 2302 * This data is later used to provide sensor property values in the JSON 2303 * response. 2304 * 2305 * Finds the inventory items asynchronously. Invokes callback when the 2306 * inventory items have been obtained. 2307 * 2308 * The callback must have the following signature: 2309 * @code 2310 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2311 * @endcode 2312 * 2313 * @param sensorsAsyncResp Pointer to object holding response data. 2314 * @param sensorNames All sensors within the current chassis. 2315 * @param objectMgrPaths Mappings from connection name to DBus object path that 2316 * implements ObjectManager. 2317 * @param callback Callback to invoke when inventory items have been obtained. 2318 */ 2319 template <typename Callback> 2320 static void getInventoryItems( 2321 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2322 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 2323 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2324 objectMgrPaths, 2325 Callback&& callback) 2326 { 2327 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 2328 auto getInventoryItemAssociationsCb = 2329 [sensorsAsyncResp, objectMgrPaths, 2330 callback{std::forward<Callback>(callback)}]( 2331 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) { 2332 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter"; 2333 auto getInventoryItemsConnectionsCb = 2334 [sensorsAsyncResp, inventoryItems, objectMgrPaths, 2335 callback{std::forward<const Callback>(callback)}]( 2336 std::shared_ptr<boost::container::flat_set<std::string>> 2337 invConnections) { 2338 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 2339 auto getInventoryItemsDataCb = 2340 [sensorsAsyncResp, inventoryItems, 2341 callback{std::move(callback)}]() { 2342 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter"; 2343 2344 auto getInventoryLedsCb = [sensorsAsyncResp, 2345 inventoryItems, 2346 callback{std::move( 2347 callback)}]() { 2348 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter"; 2349 // Find Power Supply Attributes and get the data 2350 getPowerSupplyAttributes(sensorsAsyncResp, 2351 inventoryItems, 2352 std::move(callback)); 2353 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit"; 2354 }; 2355 2356 // Find led connections and get the data 2357 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2358 std::move(getInventoryLedsCb)); 2359 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit"; 2360 }; 2361 2362 // Get inventory item data from connections 2363 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2364 invConnections, objectMgrPaths, 2365 std::move(getInventoryItemsDataCb)); 2366 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 2367 }; 2368 2369 // Get connections that provide inventory item data 2370 getInventoryItemsConnections( 2371 sensorsAsyncResp, inventoryItems, 2372 std::move(getInventoryItemsConnectionsCb)); 2373 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit"; 2374 }; 2375 2376 // Get associations from sensors to inventory items 2377 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths, 2378 std::move(getInventoryItemAssociationsCb)); 2379 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 2380 } 2381 2382 /** 2383 * @brief Returns JSON PowerSupply object for the specified inventory item. 2384 * 2385 * Searches for a JSON PowerSupply object that matches the specified inventory 2386 * item. If one is not found, a new PowerSupply object is added to the JSON 2387 * array. 2388 * 2389 * Multiple sensors are often associated with one power supply inventory item. 2390 * As a result, multiple sensor values are stored in one JSON PowerSupply 2391 * object. 2392 * 2393 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2394 * @param inventoryItem Inventory item for the power supply. 2395 * @param chassisId Chassis that contains the power supply. 2396 * @return JSON PowerSupply object for the specified inventory item. 2397 */ 2398 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2399 const InventoryItem& inventoryItem, 2400 const std::string& chassisId) 2401 { 2402 // Check if matching PowerSupply object already exists in JSON array 2403 for (nlohmann::json& powerSupply : powerSupplyArray) 2404 { 2405 if (powerSupply["MemberId"] == inventoryItem.name) 2406 { 2407 return powerSupply; 2408 } 2409 } 2410 2411 // Add new PowerSupply object to JSON array 2412 powerSupplyArray.push_back({}); 2413 nlohmann::json& powerSupply = powerSupplyArray.back(); 2414 powerSupply["@odata.id"] = 2415 "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/"; 2416 powerSupply["MemberId"] = inventoryItem.name; 2417 powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " "); 2418 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2419 powerSupply["Model"] = inventoryItem.model; 2420 powerSupply["PartNumber"] = inventoryItem.partNumber; 2421 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2422 setLedState(powerSupply, &inventoryItem); 2423 2424 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2425 { 2426 powerSupply["EfficiencyPercent"] = 2427 inventoryItem.powerSupplyEfficiencyPercent; 2428 } 2429 2430 powerSupply["Status"]["State"] = getState(&inventoryItem); 2431 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2432 powerSupply["Status"]["Health"] = health; 2433 2434 return powerSupply; 2435 } 2436 2437 /** 2438 * @brief Gets the values of the specified sensors. 2439 * 2440 * Stores the results as JSON in the SensorsAsyncResp. 2441 * 2442 * Gets the sensor values asynchronously. Stores the results later when the 2443 * information has been obtained. 2444 * 2445 * The sensorNames set contains all requested sensors for the current chassis. 2446 * 2447 * To minimize the number of DBus calls, the DBus method 2448 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2449 * values of all sensors provided by a connection (service). 2450 * 2451 * The connections set contains all the connections that provide sensor values. 2452 * 2453 * The objectMgrPaths map contains mappings from a connection name to the 2454 * corresponding DBus object path that implements ObjectManager. 2455 * 2456 * The InventoryItem vector contains D-Bus inventory items associated with the 2457 * sensors. Inventory item data is needed for some Redfish sensor properties. 2458 * 2459 * @param SensorsAsyncResp Pointer to object holding response data. 2460 * @param sensorNames All requested sensors within the current chassis. 2461 * @param connections Connections that provide sensor values. 2462 * @param objectMgrPaths Mappings from connection name to DBus object path that 2463 * implements ObjectManager. 2464 * @param inventoryItems Inventory items associated with the sensors. 2465 */ 2466 inline void getSensorData( 2467 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2468 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 2469 const boost::container::flat_set<std::string>& connections, 2470 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 2471 objectMgrPaths, 2472 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2473 { 2474 BMCWEB_LOG_DEBUG << "getSensorData enter"; 2475 // Get managed objects from all services exposing sensors 2476 for (const std::string& connection : connections) 2477 { 2478 // Response handler to process managed objects 2479 auto getManagedObjectsCb = [sensorsAsyncResp, sensorNames, 2480 inventoryItems]( 2481 const boost::system::error_code ec, 2482 dbus::utility::ManagedObjectType& resp) { 2483 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 2484 if (ec) 2485 { 2486 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 2487 messages::internalError(sensorsAsyncResp->asyncResp->res); 2488 return; 2489 } 2490 // Go through all objects and update response with sensor data 2491 for (const auto& objDictEntry : resp) 2492 { 2493 const std::string& objPath = 2494 static_cast<const std::string&>(objDictEntry.first); 2495 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 2496 << objPath; 2497 2498 std::vector<std::string> split; 2499 // Reserve space for 2500 // /xyz/openbmc_project/sensors/<name>/<subname> 2501 split.reserve(6); 2502 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 2503 if (split.size() < 6) 2504 { 2505 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 2506 << objPath; 2507 continue; 2508 } 2509 // These indexes aren't intuitive, as boost::split puts an empty 2510 // string at the beginning 2511 const std::string& sensorType = split[4]; 2512 const std::string& sensorName = split[5]; 2513 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 2514 << " sensorType " << sensorType; 2515 if (sensorNames->find(objPath) == sensorNames->end()) 2516 { 2517 BMCWEB_LOG_DEBUG << sensorName << " not in sensor list "; 2518 continue; 2519 } 2520 2521 // Find inventory item (if any) associated with sensor 2522 InventoryItem* inventoryItem = 2523 findInventoryItemForSensor(inventoryItems, objPath); 2524 2525 const std::string& sensorSchema = 2526 sensorsAsyncResp->chassisSubNode; 2527 2528 nlohmann::json* sensorJson = nullptr; 2529 2530 if (sensorSchema == sensors::node::sensors) 2531 { 2532 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2533 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + 2534 "/" + sensorsAsyncResp->chassisSubNode + "/" + 2535 sensorName; 2536 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2537 } 2538 else 2539 { 2540 std::string fieldName; 2541 if (sensorType == "temperature") 2542 { 2543 fieldName = "Temperatures"; 2544 } 2545 else if (sensorType == "fan" || sensorType == "fan_tach" || 2546 sensorType == "fan_pwm") 2547 { 2548 fieldName = "Fans"; 2549 } 2550 else if (sensorType == "voltage") 2551 { 2552 fieldName = "Voltages"; 2553 } 2554 else if (sensorType == "power") 2555 { 2556 if (!sensorName.compare("total_power")) 2557 { 2558 fieldName = "PowerControl"; 2559 } 2560 else if ((inventoryItem != nullptr) && 2561 (inventoryItem->isPowerSupply)) 2562 { 2563 fieldName = "PowerSupplies"; 2564 } 2565 else 2566 { 2567 // Other power sensors are in SensorCollection 2568 continue; 2569 } 2570 } 2571 else 2572 { 2573 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 2574 << sensorType; 2575 continue; 2576 } 2577 2578 nlohmann::json& tempArray = 2579 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2580 if (fieldName == "PowerControl") 2581 { 2582 if (tempArray.empty()) 2583 { 2584 // Put multiple "sensors" into a single 2585 // PowerControl. Follows MemberId naming and 2586 // naming in power.hpp. 2587 tempArray.push_back( 2588 {{"@odata.id", 2589 "/redfish/v1/Chassis/" + 2590 sensorsAsyncResp->chassisId + "/" + 2591 sensorsAsyncResp->chassisSubNode + "#/" + 2592 fieldName + "/0"}}); 2593 } 2594 sensorJson = &(tempArray.back()); 2595 } 2596 else if (fieldName == "PowerSupplies") 2597 { 2598 if (inventoryItem != nullptr) 2599 { 2600 sensorJson = 2601 &(getPowerSupply(tempArray, *inventoryItem, 2602 sensorsAsyncResp->chassisId)); 2603 } 2604 } 2605 else 2606 { 2607 tempArray.push_back( 2608 {{"@odata.id", 2609 "/redfish/v1/Chassis/" + 2610 sensorsAsyncResp->chassisId + "/" + 2611 sensorsAsyncResp->chassisSubNode + "#/" + 2612 fieldName + "/"}}); 2613 sensorJson = &(tempArray.back()); 2614 } 2615 } 2616 2617 if (sensorJson != nullptr) 2618 { 2619 objectInterfacesToJson( 2620 sensorName, sensorType, sensorsAsyncResp, 2621 objDictEntry.second, *sensorJson, inventoryItem); 2622 } 2623 } 2624 if (sensorsAsyncResp.use_count() == 1) 2625 { 2626 sortJSONResponse(sensorsAsyncResp); 2627 if (sensorsAsyncResp->chassisSubNode == sensors::node::thermal) 2628 { 2629 populateFanRedundancy(sensorsAsyncResp); 2630 } 2631 } 2632 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 2633 }; 2634 2635 // Find DBus object path that implements ObjectManager for the current 2636 // connection. If no mapping found, default to "/". 2637 auto iter = objectMgrPaths->find(connection); 2638 const std::string& objectMgrPath = 2639 (iter != objectMgrPaths->end()) ? iter->second : "/"; 2640 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 2641 << objectMgrPath; 2642 2643 crow::connections::systemBus->async_method_call( 2644 getManagedObjectsCb, connection, objectMgrPath, 2645 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2646 } 2647 BMCWEB_LOG_DEBUG << "getSensorData exit"; 2648 } 2649 2650 inline void processSensorList( 2651 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2652 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2653 { 2654 auto getConnectionCb = 2655 [sensorsAsyncResp, sensorNames]( 2656 const boost::container::flat_set<std::string>& connections) { 2657 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 2658 auto getObjectManagerPathsCb = 2659 [sensorsAsyncResp, sensorNames, 2660 connections](const std::shared_ptr<boost::container::flat_map< 2661 std::string, std::string>>& objectMgrPaths) { 2662 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 2663 auto getInventoryItemsCb = 2664 [sensorsAsyncResp, sensorNames, connections, 2665 objectMgrPaths]( 2666 const std::shared_ptr<std::vector<InventoryItem>>& 2667 inventoryItems) { 2668 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 2669 // Get sensor data and store results in JSON 2670 getSensorData(sensorsAsyncResp, sensorNames, 2671 connections, objectMgrPaths, 2672 inventoryItems); 2673 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 2674 }; 2675 2676 // Get inventory items associated with sensors 2677 getInventoryItems(sensorsAsyncResp, sensorNames, 2678 objectMgrPaths, 2679 std::move(getInventoryItemsCb)); 2680 2681 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 2682 }; 2683 2684 // Get mapping from connection names to the DBus object 2685 // paths that implement the ObjectManager interface 2686 getObjectManagerPaths(sensorsAsyncResp, 2687 std::move(getObjectManagerPathsCb)); 2688 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 2689 }; 2690 2691 // Get set of connections that provide sensor values 2692 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2693 } 2694 2695 /** 2696 * @brief Entry point for retrieving sensors data related to requested 2697 * chassis. 2698 * @param SensorsAsyncResp Pointer to object holding response data 2699 */ 2700 inline void 2701 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2702 { 2703 BMCWEB_LOG_DEBUG << "getChassisData enter"; 2704 auto getChassisCb = 2705 [sensorsAsyncResp]( 2706 const std::shared_ptr<boost::container::flat_set<std::string>>& 2707 sensorNames) { 2708 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2709 processSensorList(sensorsAsyncResp, sensorNames); 2710 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2711 }; 2712 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2713 nlohmann::json::array(); 2714 2715 // Get set of sensors in chassis 2716 getChassis(sensorsAsyncResp, std::move(getChassisCb)); 2717 BMCWEB_LOG_DEBUG << "getChassisData exit"; 2718 } 2719 2720 /** 2721 * @brief Find the requested sensorName in the list of all sensors supplied by 2722 * the chassis node 2723 * 2724 * @param sensorName The sensor name supplied in the PATCH request 2725 * @param sensorsList The list of sensors managed by the chassis node 2726 * @param sensorsModified The list of sensors that were found as a result of 2727 * repeated calls to this function 2728 */ 2729 inline bool findSensorNameUsingSensorPath( 2730 std::string_view sensorName, 2731 boost::container::flat_set<std::string>& sensorsList, 2732 boost::container::flat_set<std::string>& sensorsModified) 2733 { 2734 for (auto& chassisSensor : sensorsList) 2735 { 2736 sdbusplus::message::object_path path(chassisSensor); 2737 std::string thisSensorName = path.filename(); 2738 if (thisSensorName.empty()) 2739 { 2740 continue; 2741 } 2742 if (thisSensorName == sensorName) 2743 { 2744 sensorsModified.emplace(chassisSensor); 2745 return true; 2746 } 2747 } 2748 return false; 2749 } 2750 2751 /** 2752 * @brief Entry point for overriding sensor values of given sensor 2753 * 2754 * @param sensorAsyncResp response object 2755 * @param allCollections Collections extract from sensors' request patch info 2756 * @param chassisSubNode Chassis Node for which the query has to happen 2757 */ 2758 inline void setSensorsOverride( 2759 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2760 std::unordered_map<std::string, std::vector<nlohmann::json>>& 2761 allCollections) 2762 { 2763 BMCWEB_LOG_INFO << "setSensorsOverride for subNode" 2764 << sensorAsyncResp->chassisSubNode << "\n"; 2765 2766 const char* propertyValueName = nullptr; 2767 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2768 std::string memberId; 2769 double value = 0.0; 2770 for (auto& collectionItems : allCollections) 2771 { 2772 if (collectionItems.first == "Temperatures") 2773 { 2774 propertyValueName = "ReadingCelsius"; 2775 } 2776 else if (collectionItems.first == "Fans") 2777 { 2778 propertyValueName = "Reading"; 2779 } 2780 else 2781 { 2782 propertyValueName = "ReadingVolts"; 2783 } 2784 for (auto& item : collectionItems.second) 2785 { 2786 if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res, 2787 "MemberId", memberId, propertyValueName, 2788 value)) 2789 { 2790 return; 2791 } 2792 overrideMap.emplace(memberId, 2793 std::make_pair(value, collectionItems.first)); 2794 } 2795 } 2796 2797 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap]( 2798 const std::shared_ptr< 2799 boost::container::flat_set< 2800 std::string>>& sensorsList) { 2801 // Match sensor names in the PATCH request to those managed by the 2802 // chassis node 2803 const std::shared_ptr<boost::container::flat_set<std::string>> 2804 sensorNames = 2805 std::make_shared<boost::container::flat_set<std::string>>(); 2806 for (const auto& item : overrideMap) 2807 { 2808 const auto& sensor = item.first; 2809 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 2810 *sensorNames)) 2811 { 2812 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 2813 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2814 item.second.second, item.first); 2815 return; 2816 } 2817 } 2818 // Get the connection to which the memberId belongs 2819 auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap]( 2820 const boost::container::flat_set< 2821 std::string>& /*connections*/, 2822 const std::set<std::pair< 2823 std::string, std::string>>& 2824 objectsWithConnection) { 2825 if (objectsWithConnection.size() != overrideMap.size()) 2826 { 2827 BMCWEB_LOG_INFO 2828 << "Unable to find all objects with proper connection " 2829 << objectsWithConnection.size() << " requested " 2830 << overrideMap.size() << "\n"; 2831 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2832 sensorAsyncResp->chassisSubNode == 2833 sensors::node::thermal 2834 ? "Temperatures" 2835 : "Voltages", 2836 "Count"); 2837 return; 2838 } 2839 for (const auto& item : objectsWithConnection) 2840 { 2841 sdbusplus::message::object_path path(item.first); 2842 std::string sensorName = path.filename(); 2843 if (sensorName.empty()) 2844 { 2845 messages::internalError(sensorAsyncResp->asyncResp->res); 2846 return; 2847 } 2848 2849 const auto& iterator = overrideMap.find(sensorName); 2850 if (iterator == overrideMap.end()) 2851 { 2852 BMCWEB_LOG_INFO << "Unable to find sensor object" 2853 << item.first << "\n"; 2854 messages::internalError(sensorAsyncResp->asyncResp->res); 2855 return; 2856 } 2857 crow::connections::systemBus->async_method_call( 2858 [sensorAsyncResp](const boost::system::error_code ec) { 2859 if (ec) 2860 { 2861 if (ec.value() == 2862 boost::system::errc::permission_denied) 2863 { 2864 BMCWEB_LOG_WARNING 2865 << "Manufacturing mode is not Enabled...can't " 2866 "Override the sensor value. "; 2867 2868 messages::insufficientPrivilege( 2869 sensorAsyncResp->asyncResp->res); 2870 return; 2871 } 2872 BMCWEB_LOG_DEBUG 2873 << "setOverrideValueStatus DBUS error: " << ec; 2874 messages::internalError( 2875 sensorAsyncResp->asyncResp->res); 2876 } 2877 }, 2878 item.second, item.first, "org.freedesktop.DBus.Properties", 2879 "Set", "xyz.openbmc_project.Sensor.Value", "Value", 2880 dbus::utility::DbusVariantType(iterator->second.first)); 2881 } 2882 }; 2883 // Get object with connection for the given sensor name 2884 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2885 std::move(getObjectsWithConnectionCb)); 2886 }; 2887 // get full sensor list for the given chassisId and cross verify the sensor. 2888 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 2889 } 2890 2891 /** 2892 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2893 * path of the sensor. 2894 * 2895 * Function builds valid Redfish response for sensor query of given chassis and 2896 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2897 * it to caller in a callback. 2898 * 2899 * @param chassis Chassis for which retrieval should be performed 2900 * @param node Node (group) of sensors. See sensors::node for supported values 2901 * @param mapComplete Callback to be called with retrieval result 2902 */ 2903 inline void retrieveUriToDbusMap(const std::string& chassis, 2904 const std::string& node, 2905 SensorsAsyncResp::DataCompleteCb&& mapComplete) 2906 { 2907 auto pathIt = sensors::dbus::paths.find(node); 2908 if (pathIt == sensors::dbus::paths.end()) 2909 { 2910 BMCWEB_LOG_ERROR << "Wrong node provided : " << node; 2911 mapComplete(boost::beast::http::status::bad_request, {}); 2912 return; 2913 } 2914 2915 auto res = std::make_shared<crow::Response>(); 2916 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(*res); 2917 auto callback = 2918 [res, asyncResp, mapCompleteCb{std::move(mapComplete)}]( 2919 const boost::beast::http::status status, 2920 const boost::container::flat_map<std::string, std::string>& 2921 uriToDbus) { mapCompleteCb(status, uriToDbus); }; 2922 2923 auto resp = std::make_shared<SensorsAsyncResp>( 2924 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2925 getChassisData(resp); 2926 } 2927 2928 inline void requestRoutesSensorCollection(App& app) 2929 { 2930 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2931 .privileges(redfish::privileges::getSensorCollection) 2932 .methods( 2933 boost::beast::http::verb::get)([](const crow::Request&, 2934 const std::shared_ptr< 2935 bmcweb::AsyncResp>& aResp, 2936 const std::string& chassisId) { 2937 BMCWEB_LOG_DEBUG << "SensorCollection doGet enter"; 2938 2939 std::shared_ptr<SensorsAsyncResp> asyncResp = 2940 std::make_shared<SensorsAsyncResp>( 2941 aResp, chassisId, 2942 sensors::dbus::paths.at(sensors::node::sensors), 2943 sensors::node::sensors); 2944 2945 auto getChassisCb = 2946 [asyncResp]( 2947 const std::shared_ptr< 2948 boost::container::flat_set<std::string>>& sensorNames) { 2949 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2950 2951 nlohmann::json& entriesArray = 2952 asyncResp->asyncResp->res.jsonValue["Members"]; 2953 for (auto& sensor : *sensorNames) 2954 { 2955 BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; 2956 2957 sdbusplus::message::object_path path(sensor); 2958 std::string sensorName = path.filename(); 2959 if (sensorName.empty()) 2960 { 2961 BMCWEB_LOG_ERROR << "Invalid sensor path: " 2962 << sensor; 2963 messages::internalError(asyncResp->asyncResp->res); 2964 return; 2965 } 2966 entriesArray.push_back( 2967 {{"@odata.id", "/redfish/v1/Chassis/" + 2968 asyncResp->chassisId + "/" + 2969 asyncResp->chassisSubNode + "/" + 2970 sensorName}}); 2971 } 2972 2973 asyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 2974 entriesArray.size(); 2975 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2976 }; 2977 2978 // Get set of sensors in chassis 2979 getChassis(asyncResp, std::move(getChassisCb)); 2980 BMCWEB_LOG_DEBUG << "SensorCollection doGet exit"; 2981 }); 2982 } 2983 2984 inline void requestRoutesSensor(App& app) 2985 { 2986 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2987 .privileges(redfish::privileges::getSensor) 2988 .methods( 2989 boost::beast::http::verb::get)([](const crow::Request&, 2990 const std::shared_ptr< 2991 bmcweb::AsyncResp>& aResp, 2992 const std::string& chassisId, 2993 const std::string& sensorName) { 2994 BMCWEB_LOG_DEBUG << "Sensor doGet enter"; 2995 std::shared_ptr<SensorsAsyncResp> asyncResp = 2996 std::make_shared<SensorsAsyncResp>(aResp, chassisId, 2997 std::vector<const char*>(), 2998 sensors::node::sensors); 2999 3000 const std::array<const char*, 1> interfaces = { 3001 "xyz.openbmc_project.Sensor.Value"}; 3002 3003 // Get a list of all of the sensors that implement Sensor.Value 3004 // and get the path and service name associated with the sensor 3005 crow::connections::systemBus->async_method_call( 3006 [asyncResp, sensorName](const boost::system::error_code ec, 3007 const GetSubTreeType& subtree) { 3008 BMCWEB_LOG_DEBUG << "respHandler1 enter"; 3009 if (ec) 3010 { 3011 messages::internalError(asyncResp->asyncResp->res); 3012 BMCWEB_LOG_ERROR 3013 << "Sensor getSensorPaths resp_handler: " 3014 << "Dbus error " << ec; 3015 return; 3016 } 3017 3018 GetSubTreeType::const_iterator it = std::find_if( 3019 subtree.begin(), subtree.end(), 3020 [sensorName]( 3021 const std::pair< 3022 std::string, 3023 std::vector<std::pair< 3024 std::string, std::vector<std::string>>>>& 3025 object) { 3026 sdbusplus::message::object_path path(object.first); 3027 std::string name = path.filename(); 3028 if (name.empty()) 3029 { 3030 BMCWEB_LOG_ERROR << "Invalid sensor path: " 3031 << object.first; 3032 return false; 3033 } 3034 3035 return name == sensorName; 3036 }); 3037 3038 if (it == subtree.end()) 3039 { 3040 BMCWEB_LOG_ERROR << "Could not find path for sensor: " 3041 << sensorName; 3042 messages::resourceNotFound(asyncResp->asyncResp->res, 3043 "Sensor", sensorName); 3044 return; 3045 } 3046 std::string_view sensorPath = (*it).first; 3047 BMCWEB_LOG_DEBUG << "Found sensor path for sensor '" 3048 << sensorName << "': " << sensorPath; 3049 3050 const std::shared_ptr< 3051 boost::container::flat_set<std::string>> 3052 sensorList = std::make_shared< 3053 boost::container::flat_set<std::string>>(); 3054 3055 sensorList->emplace(sensorPath); 3056 processSensorList(asyncResp, sensorList); 3057 BMCWEB_LOG_DEBUG << "respHandler1 exit"; 3058 }, 3059 "xyz.openbmc_project.ObjectMapper", 3060 "/xyz/openbmc_project/object_mapper", 3061 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 3062 "/xyz/openbmc_project/sensors", 2, interfaces); 3063 }); 3064 } 3065 3066 } // namespace redfish 3067