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