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