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