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