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