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