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