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