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