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