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