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