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