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