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