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 <math.h> 19 20 #include <boost/algorithm/string/predicate.hpp> 21 #include <boost/algorithm/string/split.hpp> 22 #include <boost/container/flat_map.hpp> 23 #include <boost/range/algorithm/replace_copy_if.hpp> 24 #include <dbus_singleton.hpp> 25 #include <utils/json_utils.hpp> 26 #include <variant> 27 28 namespace redfish 29 { 30 31 using GetSubTreeType = std::vector< 32 std::pair<std::string, 33 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 34 35 using SensorVariant = std::variant<int64_t, double, uint32_t, bool>; 36 37 using ManagedObjectsVectorType = std::vector<std::pair< 38 sdbusplus::message::object_path, 39 boost::container::flat_map< 40 std::string, boost::container::flat_map<std::string, SensorVariant>>>>; 41 42 /** 43 * SensorsAsyncResp 44 * Gathers data needed for response processing after async calls are done 45 */ 46 class SensorsAsyncResp 47 { 48 public: 49 SensorsAsyncResp(crow::Response& response, const std::string& chassisId, 50 const std::vector<const char*> types, 51 const std::string& subNode) : 52 res(response), 53 chassisId(chassisId), types(types), chassisSubNode(subNode) 54 { 55 } 56 57 ~SensorsAsyncResp() 58 { 59 if (res.result() == boost::beast::http::status::internal_server_error) 60 { 61 // Reset the json object to clear out any data that made it in 62 // before the error happened todo(ed) handle error condition with 63 // proper code 64 res.jsonValue = nlohmann::json::object(); 65 } 66 res.end(); 67 } 68 69 crow::Response& res; 70 std::string chassisId{}; 71 const std::vector<const char*> types; 72 std::string chassisSubNode{}; 73 }; 74 75 /** 76 * @brief Get objects with connection necessary for sensors 77 * @param SensorsAsyncResp Pointer to object holding response data 78 * @param sensorNames Sensors retrieved from chassis 79 * @param callback Callback for processing gathered connections 80 */ 81 template <typename Callback> 82 void getObjectsWithConnection( 83 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 84 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 85 Callback&& callback) 86 { 87 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 88 const std::string path = "/xyz/openbmc_project/sensors"; 89 const std::array<std::string, 1> interfaces = { 90 "xyz.openbmc_project.Sensor.Value"}; 91 92 // Response handler for parsing objects subtree 93 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp, 94 sensorNames](const boost::system::error_code ec, 95 const GetSubTreeType& subtree) { 96 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 97 if (ec) 98 { 99 messages::internalError(SensorsAsyncResp->res); 100 BMCWEB_LOG_ERROR 101 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 102 return; 103 } 104 105 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 106 107 // Make unique list of connections only for requested sensor types and 108 // found in the chassis 109 boost::container::flat_set<std::string> connections; 110 std::set<std::pair<std::string, std::string>> objectsWithConnection; 111 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 112 // producers 113 connections.reserve(8); 114 115 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size(); 116 for (const std::string& tsensor : *sensorNames) 117 { 118 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 119 } 120 121 for (const std::pair< 122 std::string, 123 std::vector<std::pair<std::string, std::vector<std::string>>>>& 124 object : subtree) 125 { 126 if (sensorNames->find(object.first) != sensorNames->end()) 127 { 128 for (const std::pair<std::string, std::vector<std::string>>& 129 objData : object.second) 130 { 131 BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first; 132 connections.insert(objData.first); 133 objectsWithConnection.insert( 134 std::make_pair(object.first, objData.first)); 135 } 136 } 137 } 138 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 139 callback(std::move(connections), std::move(objectsWithConnection)); 140 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 141 }; 142 // Make call to ObjectMapper to find all sensors objects 143 crow::connections::systemBus->async_method_call( 144 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 145 "/xyz/openbmc_project/object_mapper", 146 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 147 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 148 } 149 150 /** 151 * @brief Create connections necessary for sensors 152 * @param SensorsAsyncResp Pointer to object holding response data 153 * @param sensorNames Sensors retrieved from chassis 154 * @param callback Callback for processing gathered connections 155 */ 156 template <typename Callback> 157 void getConnections( 158 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 159 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 160 Callback&& callback) 161 { 162 auto objectsWithConnectionCb = 163 [callback](const boost::container::flat_set<std::string>& connections, 164 const std::set<std::pair<std::string, std::string>>& 165 objectsWithConnection) { 166 callback(std::move(connections)); 167 }; 168 getObjectsWithConnection(SensorsAsyncResp, sensorNames, 169 std::move(objectsWithConnectionCb)); 170 } 171 172 /** 173 * @brief Shrinks the list of sensors for processing 174 * @param SensorsAysncResp The class holding the Redfish response 175 * @param allSensors A list of all the sensors associated to the 176 * chassis element (i.e. baseboard, front panel, etc...) 177 * @param activeSensors A list that is a reduction of the incoming 178 * allSensors list. Eliminate Thermal sensors when a Power request is 179 * made, and eliminate Power sensors when a Thermal request is made. 180 */ 181 void reduceSensorList( 182 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 183 const std::vector<std::string>* allSensors, 184 std::shared_ptr<boost::container::flat_set<std::string>> activeSensors) 185 { 186 if (SensorsAsyncResp == nullptr) 187 { 188 return; 189 } 190 if ((allSensors == nullptr) || (activeSensors == nullptr)) 191 { 192 messages::resourceNotFound( 193 SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode, 194 SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures" 195 : "Voltages"); 196 197 return; 198 } 199 if (allSensors->empty()) 200 { 201 // Nothing to do, the activeSensors object is also empty 202 return; 203 } 204 205 for (const char* type : SensorsAsyncResp->types) 206 { 207 for (const std::string& sensor : *allSensors) 208 { 209 if (boost::starts_with(sensor, type)) 210 { 211 activeSensors->emplace(sensor); 212 } 213 } 214 } 215 } 216 217 /** 218 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 219 * @param SensorsAsyncResp Pointer to object holding response data 220 * @param callback Callback for next step in gathered sensor processing 221 */ 222 template <typename Callback> 223 void getChassis(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 224 Callback&& callback) 225 { 226 BMCWEB_LOG_DEBUG << "getChassis enter"; 227 const std::array<const char*, 3> interfaces = { 228 "xyz.openbmc_project.Inventory.Item.Board", 229 "xyz.openbmc_project.Inventory.Item.Chassis", 230 "xyz.openbmc_project.Inventory.Item.PowerSupply"}; 231 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp]( 232 const boost::system::error_code ec, 233 const std::vector<std::string>& chassisPaths) { 234 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 235 if (ec) 236 { 237 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 238 messages::internalError(sensorsAsyncResp->res); 239 return; 240 } 241 242 const std::string* chassisPath = nullptr; 243 std::string chassisName; 244 for (const std::string& chassis : chassisPaths) 245 { 246 std::size_t lastPos = chassis.rfind("/"); 247 if (lastPos == std::string::npos) 248 { 249 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 250 continue; 251 } 252 chassisName = chassis.substr(lastPos + 1); 253 if (chassisName == sensorsAsyncResp->chassisId) 254 { 255 chassisPath = &chassis; 256 break; 257 } 258 } 259 if (chassisPath == nullptr) 260 { 261 messages::resourceNotFound(sensorsAsyncResp->res, "Chassis", 262 sensorsAsyncResp->chassisId); 263 return; 264 } 265 266 const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode; 267 if (chassisSubNode == "Power") 268 { 269 sensorsAsyncResp->res.jsonValue["@odata.type"] = 270 "#Power.v1_5_2.Power"; 271 } 272 else if (chassisSubNode == "Thermal") 273 { 274 sensorsAsyncResp->res.jsonValue["@odata.type"] = 275 "#Thermal.v1_4_0.Thermal"; 276 sensorsAsyncResp->res.jsonValue["Fans"] = nlohmann::json::array(); 277 sensorsAsyncResp->res.jsonValue["Temperatures"] = 278 nlohmann::json::array(); 279 } 280 sensorsAsyncResp->res.jsonValue["@odata.id"] = 281 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + 282 chassisSubNode; 283 284 sensorsAsyncResp->res.jsonValue["@odata.context"] = 285 "/redfish/v1/$metadata#" + chassisSubNode + "." + chassisSubNode; 286 sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode; 287 sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode; 288 289 // Get the list of all sensors for this Chassis element 290 std::string sensorPath = *chassisPath + "/all_sensors"; 291 crow::connections::systemBus->async_method_call( 292 [sensorsAsyncResp, callback{std::move(callback)}]( 293 const boost::system::error_code ec, 294 const std::variant<std::vector<std::string>>& 295 variantEndpoints) { 296 if (ec) 297 { 298 if (ec.value() != EBADR) 299 { 300 messages::internalError(sensorsAsyncResp->res); 301 return; 302 } 303 } 304 const std::vector<std::string>* nodeSensorList = 305 std::get_if<std::vector<std::string>>(&(variantEndpoints)); 306 if (nodeSensorList == nullptr) 307 { 308 messages::resourceNotFound( 309 sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode, 310 sensorsAsyncResp->chassisSubNode == "Thermal" 311 ? "Temperatures" 312 : "Voltages"); 313 return; 314 } 315 const std::shared_ptr<boost::container::flat_set<std::string>> 316 culledSensorList = std::make_shared< 317 boost::container::flat_set<std::string>>(); 318 reduceSensorList(sensorsAsyncResp, nodeSensorList, 319 culledSensorList); 320 callback(culledSensorList); 321 }, 322 "xyz.openbmc_project.ObjectMapper", sensorPath, 323 "org.freedesktop.DBus.Properties", "Get", 324 "xyz.openbmc_project.Association", "endpoints"); 325 }; 326 327 // Get the Chassis Collection 328 crow::connections::systemBus->async_method_call( 329 respHandler, "xyz.openbmc_project.ObjectMapper", 330 "/xyz/openbmc_project/object_mapper", 331 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 332 "/xyz/openbmc_project/inventory", int32_t(0), interfaces); 333 BMCWEB_LOG_DEBUG << "getChassis exit"; 334 } 335 336 /** 337 * @brief Finds all DBus object paths that implement ObjectManager. 338 * 339 * Creates a mapping from the associated connection name to the object path. 340 * 341 * Finds the object paths asynchronously. Invokes callback when information has 342 * been obtained. 343 * 344 * The callback must have the following signature: 345 * @code 346 * callback(std::shared_ptr<boost::container::flat_map<std::string, 347 * std::string>> objectMgrPaths) 348 * @endcode 349 * 350 * @param sensorsAsyncResp Pointer to object holding response data. 351 * @param callback Callback to invoke when object paths obtained. 352 */ 353 template <typename Callback> 354 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 355 Callback&& callback) 356 { 357 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 358 const std::array<std::string, 1> interfaces = { 359 "org.freedesktop.DBus.ObjectManager"}; 360 361 // Response handler for GetSubTree DBus method 362 auto respHandler = [callback{std::move(callback)}, 363 SensorsAsyncResp](const boost::system::error_code ec, 364 const GetSubTreeType& subtree) { 365 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 366 if (ec) 367 { 368 messages::internalError(SensorsAsyncResp->res); 369 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 370 << ec; 371 return; 372 } 373 374 // Loop over returned object paths 375 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 376 objectMgrPaths = std::make_shared< 377 boost::container::flat_map<std::string, std::string>>(); 378 for (const std::pair< 379 std::string, 380 std::vector<std::pair<std::string, std::vector<std::string>>>>& 381 object : subtree) 382 { 383 // Loop over connections for current object path 384 const std::string& objectPath = object.first; 385 for (const std::pair<std::string, std::vector<std::string>>& 386 objData : object.second) 387 { 388 // Add mapping from connection to object path 389 const std::string& connection = objData.first; 390 (*objectMgrPaths)[connection] = objectPath; 391 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 392 << objectPath; 393 } 394 } 395 callback(objectMgrPaths); 396 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 397 }; 398 399 // Query mapper for all DBus object paths that implement ObjectManager 400 crow::connections::systemBus->async_method_call( 401 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 402 "/xyz/openbmc_project/object_mapper", 403 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0), 404 interfaces); 405 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 406 } 407 408 /** 409 * @brief Retrieves the health from a sensor . 410 * @param interfacesDict Map of all sensor interfaces 411 */ 412 413 static std::string getHealth( 414 const boost::container::flat_map< 415 std::string, boost::container::flat_map<std::string, SensorVariant>>& 416 interfacesDict) 417 { 418 auto criticalThresholdIt = 419 interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 420 if (criticalThresholdIt != interfacesDict.end()) 421 { 422 auto thresholdHighIt = 423 criticalThresholdIt->second.find("CriticalAlarmHigh"); 424 auto thresholdLowIt = 425 criticalThresholdIt->second.find("CriticalAlarmLow"); 426 if (thresholdHighIt != criticalThresholdIt->second.end()) 427 { 428 const bool* asserted = std::get_if<bool>(&thresholdHighIt->second); 429 if (asserted == nullptr) 430 { 431 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 432 } 433 else if (*asserted) 434 { 435 return "Critical"; 436 } 437 } 438 if (thresholdLowIt != criticalThresholdIt->second.end()) 439 { 440 const bool* asserted = std::get_if<bool>(&thresholdLowIt->second); 441 if (asserted == nullptr) 442 { 443 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 444 } 445 else if (*asserted) 446 { 447 return "Critical"; 448 } 449 } 450 } 451 452 auto warningThresholdIt = 453 interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 454 if (warningThresholdIt != interfacesDict.end()) 455 { 456 auto thresholdHighIt = 457 warningThresholdIt->second.find("WarningAlarmHigh"); 458 auto thresholdLowIt = 459 warningThresholdIt->second.find("WarningAlarmLow"); 460 if (thresholdHighIt != warningThresholdIt->second.end()) 461 { 462 const bool* asserted = std::get_if<bool>(&thresholdHighIt->second); 463 if (asserted == nullptr) 464 { 465 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 466 } 467 else if (*asserted) 468 { 469 return "Warning"; 470 } 471 } 472 if (thresholdLowIt != warningThresholdIt->second.end()) 473 { 474 const bool* asserted = std::get_if<bool>(&thresholdLowIt->second); 475 if (asserted == nullptr) 476 { 477 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 478 } 479 else if (*asserted) 480 { 481 return "Warning"; 482 } 483 } 484 } 485 return "OK"; 486 } 487 488 /** 489 * @brief Builds a json sensor representation of a sensor. 490 * @param sensorName The name of the sensor to be built 491 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 492 * build 493 * @param interfacesDict A dictionary of the interfaces and properties of said 494 * interfaces to be built from 495 * @param sensor_json The json object to fill 496 */ 497 void objectInterfacesToJson( 498 const std::string& sensorName, const std::string& sensorType, 499 const boost::container::flat_map< 500 std::string, boost::container::flat_map<std::string, SensorVariant>>& 501 interfacesDict, 502 nlohmann::json& sensor_json) 503 { 504 // We need a value interface before we can do anything with it 505 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 506 if (valueIt == interfacesDict.end()) 507 { 508 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 509 return; 510 } 511 512 // Assume values exist as is (10^0 == 1) if no scale exists 513 int64_t scaleMultiplier = 0; 514 515 auto scaleIt = valueIt->second.find("Scale"); 516 // If a scale exists, pull value as int64, and use the scaling. 517 if (scaleIt != valueIt->second.end()) 518 { 519 const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second); 520 if (int64Value != nullptr) 521 { 522 scaleMultiplier = *int64Value; 523 } 524 } 525 526 sensor_json["MemberId"] = sensorName; 527 sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " "); 528 529 sensor_json["Status"]["State"] = "Enabled"; 530 sensor_json["Status"]["Health"] = getHealth(interfacesDict); 531 532 // Parameter to set to override the type we get from dbus, and force it to 533 // int, regardless of what is available. This is used for schemas like fan, 534 // that require integers, not floats. 535 bool forceToInt = false; 536 537 const char* unit = "Reading"; 538 if (sensorType == "temperature") 539 { 540 unit = "ReadingCelsius"; 541 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 542 // TODO(ed) Documentation says that path should be type fan_tach, 543 // implementation seems to implement fan 544 } 545 else if (sensorType == "fan" || sensorType == "fan_tach") 546 { 547 unit = "Reading"; 548 sensor_json["ReadingUnits"] = "RPM"; 549 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 550 forceToInt = true; 551 } 552 else if (sensorType == "fan_pwm") 553 { 554 unit = "Reading"; 555 sensor_json["ReadingUnits"] = "Percent"; 556 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 557 forceToInt = true; 558 } 559 else if (sensorType == "voltage") 560 { 561 unit = "ReadingVolts"; 562 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 563 } 564 else if (sensorType == "power") 565 { 566 std::string sensorNameLower = 567 boost::algorithm::to_lower_copy(sensorName); 568 569 if (!sensorName.compare("total_power")) 570 { 571 sensor_json["@odata.type"] = "#Power.v1_0_0.PowerControl"; 572 // Put multiple "sensors" into a single PowerControl, so have 573 // generic names for MemberId and Name. Follows Redfish mockup. 574 sensor_json["MemberId"] = "0"; 575 sensor_json["Name"] = "Chassis Power Control"; 576 unit = "PowerConsumedWatts"; 577 } 578 else if (sensorNameLower.find("input") != std::string::npos) 579 { 580 unit = "PowerInputWatts"; 581 } 582 else 583 { 584 unit = "PowerOutputWatts"; 585 } 586 } 587 else 588 { 589 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 590 return; 591 } 592 // Map of dbus interface name, dbus property name and redfish property_name 593 std::vector<std::tuple<const char*, const char*, const char*>> properties; 594 properties.reserve(7); 595 596 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 597 598 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 599 if ((sensorType != "current") && (sensorType != "power")) 600 { 601 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 602 "WarningHigh", "UpperThresholdNonCritical"); 603 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 604 "WarningLow", "LowerThresholdNonCritical"); 605 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 606 "CriticalHigh", "UpperThresholdCritical"); 607 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 608 "CriticalLow", "LowerThresholdCritical"); 609 } 610 611 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 612 613 if (sensorType == "temperature") 614 { 615 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 616 "MinReadingRangeTemp"); 617 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 618 "MaxReadingRangeTemp"); 619 } 620 else if ((sensorType != "current") && (sensorType != "power")) 621 { 622 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 623 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 624 "MinReadingRange"); 625 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 626 "MaxReadingRange"); 627 } 628 629 for (const std::tuple<const char*, const char*, const char*>& p : 630 properties) 631 { 632 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 633 if (interfaceProperties != interfacesDict.end()) 634 { 635 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 636 if (valueIt != interfaceProperties->second.end()) 637 { 638 const SensorVariant& valueVariant = valueIt->second; 639 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 640 // Attempt to pull the int64 directly 641 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 642 643 const double* doubleValue = std::get_if<double>(&valueVariant); 644 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant); 645 double temp = 0.0; 646 if (int64Value != nullptr) 647 { 648 temp = *int64Value; 649 } 650 else if (doubleValue != nullptr) 651 { 652 temp = *doubleValue; 653 } 654 else if (uValue != nullptr) 655 { 656 temp = *uValue; 657 } 658 else 659 { 660 BMCWEB_LOG_ERROR 661 << "Got value interface that wasn't int or double"; 662 continue; 663 } 664 temp = temp * std::pow(10, scaleMultiplier); 665 if (forceToInt) 666 { 667 valueIt = static_cast<int64_t>(temp); 668 } 669 else 670 { 671 valueIt = temp; 672 } 673 } 674 } 675 } 676 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 677 } 678 679 static void 680 populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp) 681 { 682 crow::connections::systemBus->async_method_call( 683 [sensorsAsyncResp](const boost::system::error_code ec, 684 const GetSubTreeType& resp) { 685 if (ec) 686 { 687 return; // don't have to have this interface 688 } 689 for (const std::pair<std::string, 690 std::vector<std::pair< 691 std::string, std::vector<std::string>>>>& 692 pathPair : resp) 693 { 694 const std::string& path = pathPair.first; 695 const std::vector< 696 std::pair<std::string, std::vector<std::string>>>& objDict = 697 pathPair.second; 698 if (objDict.empty()) 699 { 700 continue; // this should be impossible 701 } 702 703 const std::string& owner = objDict.begin()->first; 704 crow::connections::systemBus->async_method_call( 705 [path, owner, 706 sensorsAsyncResp](const boost::system::error_code ec, 707 std::variant<std::vector<std::string>> 708 variantEndpoints) { 709 if (ec) 710 { 711 return; // if they don't have an association we 712 // can't tell what chassis is 713 } 714 // verify part of the right chassis 715 auto endpoints = std::get_if<std::vector<std::string>>( 716 &variantEndpoints); 717 718 if (endpoints == nullptr) 719 { 720 BMCWEB_LOG_ERROR << "Invalid association interface"; 721 messages::internalError(sensorsAsyncResp->res); 722 return; 723 } 724 725 auto found = std::find_if( 726 endpoints->begin(), endpoints->end(), 727 [sensorsAsyncResp](const std::string& entry) { 728 return entry.find( 729 sensorsAsyncResp->chassisId) != 730 std::string::npos; 731 }); 732 733 if (found == endpoints->end()) 734 { 735 return; 736 } 737 crow::connections::systemBus->async_method_call( 738 [path, sensorsAsyncResp]( 739 const boost::system::error_code ec, 740 const boost::container::flat_map< 741 std::string, 742 std::variant<uint8_t, 743 std::vector<std::string>, 744 std::string>>& ret) { 745 if (ec) 746 { 747 return; // don't have to have this 748 // interface 749 } 750 auto findFailures = ret.find("AllowedFailures"); 751 auto findCollection = ret.find("Collection"); 752 auto findStatus = ret.find("Status"); 753 754 if (findFailures == ret.end() || 755 findCollection == ret.end() || 756 findStatus == ret.end()) 757 { 758 BMCWEB_LOG_ERROR 759 << "Invalid redundancy interface"; 760 messages::internalError( 761 sensorsAsyncResp->res); 762 return; 763 } 764 765 auto allowedFailures = std::get_if<uint8_t>( 766 &(findFailures->second)); 767 auto collection = 768 std::get_if<std::vector<std::string>>( 769 &(findCollection->second)); 770 auto status = std::get_if<std::string>( 771 &(findStatus->second)); 772 773 if (allowedFailures == nullptr || 774 collection == nullptr || status == nullptr) 775 { 776 777 BMCWEB_LOG_ERROR 778 << "Invalid redundancy interface " 779 "types"; 780 messages::internalError( 781 sensorsAsyncResp->res); 782 return; 783 } 784 size_t lastSlash = path.rfind("/"); 785 if (lastSlash == std::string::npos) 786 { 787 // this should be impossible 788 messages::internalError( 789 sensorsAsyncResp->res); 790 return; 791 } 792 std::string name = path.substr(lastSlash + 1); 793 std::replace(name.begin(), name.end(), '_', 794 ' '); 795 796 std::string health; 797 798 if (boost::ends_with(*status, "Full")) 799 { 800 health = "OK"; 801 } 802 else if (boost::ends_with(*status, "Degraded")) 803 { 804 health = "Warning"; 805 } 806 else 807 { 808 health = "Critical"; 809 } 810 std::vector<nlohmann::json> redfishCollection; 811 const auto& fanRedfish = 812 sensorsAsyncResp->res.jsonValue["Fans"]; 813 for (const std::string& item : *collection) 814 { 815 lastSlash = item.rfind("/"); 816 // make a copy as collection is const 817 std::string itemName = 818 item.substr(lastSlash + 1); 819 /* 820 todo(ed): merge patch that fixes the names 821 std::replace(itemName.begin(), 822 itemName.end(), '_', ' ');*/ 823 auto schemaItem = std::find_if( 824 fanRedfish.begin(), fanRedfish.end(), 825 [itemName](const nlohmann::json& fan) { 826 return fan["MemberId"] == itemName; 827 }); 828 if (schemaItem != fanRedfish.end()) 829 { 830 redfishCollection.push_back( 831 {{"@odata.id", 832 (*schemaItem)["@odata.id"]}}); 833 } 834 else 835 { 836 BMCWEB_LOG_ERROR 837 << "failed to find fan in schema"; 838 messages::internalError( 839 sensorsAsyncResp->res); 840 return; 841 } 842 } 843 844 auto& resp = sensorsAsyncResp->res 845 .jsonValue["Redundancy"]; 846 resp.push_back( 847 {{"@odata.id", 848 "/refish/v1/Chassis/" + 849 sensorsAsyncResp->chassisId + "/" + 850 sensorsAsyncResp->chassisSubNode + 851 "#/Redundancy/" + 852 std::to_string(resp.size())}, 853 {"@odata.type", 854 "#Redundancy.v1_3_2.Redundancy"}, 855 {"MinNumNeeded", 856 collection->size() - *allowedFailures}, 857 {"MemberId", name}, 858 {"Mode", "N+m"}, 859 {"Name", name}, 860 {"RedundancySet", redfishCollection}, 861 {"Status", 862 {{"Health", health}, 863 {"State", "Enabled"}}}}); 864 }, 865 owner, path, "org.freedesktop.DBus.Properties", 866 "GetAll", 867 "xyz.openbmc_project.Control.FanRedundancy"); 868 }, 869 "xyz.openbmc_project.ObjectMapper", path + "/chassis", 870 "org.freedesktop.DBus.Properties", "Get", 871 "xyz.openbmc_project.Association", "endpoints"); 872 } 873 }, 874 "xyz.openbmc_project.ObjectMapper", 875 "/xyz/openbmc_project/object_mapper", 876 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 877 "/xyz/openbmc_project/control", 2, 878 std::array<const char*, 1>{ 879 "xyz.openbmc_project.Control.FanRedundancy"}); 880 } 881 882 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 883 { 884 nlohmann::json& response = SensorsAsyncResp->res.jsonValue; 885 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 886 if (SensorsAsyncResp->chassisSubNode == "Power") 887 { 888 sensorHeaders = {"Voltages", "PowerSupplies"}; 889 } 890 for (const std::string& sensorGroup : sensorHeaders) 891 { 892 nlohmann::json::iterator entry = response.find(sensorGroup); 893 if (entry != response.end()) 894 { 895 std::sort(entry->begin(), entry->end(), 896 [](nlohmann::json& c1, nlohmann::json& c2) { 897 return c1["Name"] < c2["Name"]; 898 }); 899 900 // add the index counts to the end of each entry 901 size_t count = 0; 902 for (nlohmann::json& sensorJson : *entry) 903 { 904 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 905 if (odata == sensorJson.end()) 906 { 907 continue; 908 } 909 std::string* value = odata->get_ptr<std::string*>(); 910 if (value != nullptr) 911 { 912 *value += std::to_string(count); 913 count++; 914 } 915 } 916 } 917 } 918 } 919 920 /** 921 * @brief Finds the JSON object for the specified sensor. 922 * 923 * Searches the JSON response in sensorsAsyncResp for an object corresponding to 924 * the specified sensor. 925 * 926 * @param sensorsAsyncResp Pointer to object holding response data. 927 * @param sensorName DBus object path to the sensor. 928 * @return Pointer to JSON object, or nullptr if object not found. 929 */ 930 static nlohmann::json* 931 findSensorJson(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 932 const std::string& sensorName) 933 { 934 // Get base name of sensor 935 std::size_t lastSlash = sensorName.rfind('/'); 936 if (lastSlash != std::string::npos) 937 { 938 std::string baseSensorName = sensorName.substr(lastSlash + 1); 939 940 // Loop through JSON sensor groups that could contain sensor 941 nlohmann::json& response = sensorsAsyncResp->res.jsonValue; 942 std::array<std::string, 4> sensorGroups{"Temperatures", "Fans", 943 "Voltages", "PowerSupplies"}; 944 for (const std::string& sensorGroup : sensorGroups) 945 { 946 nlohmann::json::iterator groupIt = response.find(sensorGroup); 947 if (groupIt != response.end()) 948 { 949 // Loop through sensors in current group 950 for (nlohmann::json& sensorJson : *groupIt) 951 { 952 // Check if this is the sensor we are looking for 953 nlohmann::json::iterator memberIdIt = 954 sensorJson.find("MemberId"); 955 if (memberIdIt != sensorJson.end()) 956 { 957 std::string* memberId = 958 memberIdIt->get_ptr<std::string*>(); 959 if ((memberId != nullptr) && 960 (*memberId == baseSensorName)) 961 { 962 return &sensorJson; 963 } 964 } 965 } 966 } 967 } 968 } 969 970 // Unable to find JSON object for specified sensor 971 return nullptr; 972 } 973 974 /** 975 * @brief Updates sensor status in JSON response based on inventory item status. 976 * 977 * Updates the status of the specified sensor based on the status of a related 978 * inventory item. 979 * 980 * Modifies the Redfish Status property in the JSON response if the inventory 981 * item indicates the hardware is not present or not functional. 982 * 983 * The D-Bus Present and Functional properties are typically on the inventory 984 * item rather than the sensor. 985 * 986 * @param sensorsAsyncResp Pointer to object holding response data. 987 * @param sensorName DBus object path to the sensor. 988 * @param interfacesDict Map containing the interfaces and properties of the 989 * inventory item associated with this sensor. 990 */ 991 static void updateSensorStatus( 992 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 993 const std::string& sensorName, 994 const boost::container::flat_map< 995 std::string, boost::container::flat_map<std::string, SensorVariant>>& 996 interfacesDict) 997 { 998 // Find the JSON object in the response for this sensor 999 nlohmann::json* sensorJson = findSensorJson(sensorsAsyncResp, sensorName); 1000 if (sensorJson != nullptr) 1001 { 1002 // Get Inventory.Item.Present property of inventory item 1003 auto itemIt = interfacesDict.find("xyz.openbmc_project.Inventory.Item"); 1004 if (itemIt != interfacesDict.end()) 1005 { 1006 auto presentIt = itemIt->second.find("Present"); 1007 if (presentIt != itemIt->second.end()) 1008 { 1009 const bool* present = std::get_if<bool>(&presentIt->second); 1010 if ((present != nullptr) && (*present == false)) 1011 { 1012 // Inventory item is not present; update sensor State 1013 (*sensorJson)["Status"]["State"] = "Absent"; 1014 } 1015 } 1016 } 1017 1018 // Get OperationalStatus.Functional property of inventory item 1019 auto opStatusIt = interfacesDict.find( 1020 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 1021 if (opStatusIt != interfacesDict.end()) 1022 { 1023 auto functionalIt = opStatusIt->second.find("Functional"); 1024 if (functionalIt != opStatusIt->second.end()) 1025 { 1026 const bool* functional = 1027 std::get_if<bool>(&functionalIt->second); 1028 if ((functional != nullptr) && (*functional == false)) 1029 { 1030 // Inventory item is not functional; update sensor Health 1031 (*sensorJson)["Status"]["Health"] = "Critical"; 1032 } 1033 } 1034 } 1035 } 1036 } 1037 1038 /** 1039 * @brief Gets status of inventory items associated with sensors. 1040 * 1041 * Gets the D-Bus status properties for the inventory items associated with 1042 * sensors. 1043 * 1044 * Updates the Redfish sensors status in the JSON response, if needed, based on 1045 * the inventory items status. 1046 * 1047 * @param sensorsAsyncResp Pointer to object holding response data. 1048 * @param sensorToInvMap Mappings from sensor object path to the associated 1049 * inventory object path. 1050 * @param invConnections Connections that provide the status 1051 * interfaces/properties for the inventory items. 1052 * @param objectMgrPaths Mappings from connection name to DBus object path that 1053 * implements ObjectManager. 1054 */ 1055 static void getInventoryItemsStatus( 1056 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1057 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1058 sensorToInvMap, 1059 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1060 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1061 objectMgrPaths) 1062 { 1063 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus enter"; 1064 1065 // Loop through all connections providing inventory item status 1066 for (const std::string& invConnection : *invConnections) 1067 { 1068 // Response handler for GetManagedObjects 1069 auto respHandler = [sensorsAsyncResp, 1070 sensorToInvMap](const boost::system::error_code ec, 1071 ManagedObjectsVectorType& resp) { 1072 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler enter"; 1073 if (ec) 1074 { 1075 BMCWEB_LOG_ERROR 1076 << "getInventoryItemsStatus respHandler DBus error " << ec; 1077 messages::internalError(sensorsAsyncResp->res); 1078 return; 1079 } 1080 1081 // Loop through returned object paths 1082 for (const auto& objDictEntry : resp) 1083 { 1084 const std::string& objPath = 1085 static_cast<const std::string&>(objDictEntry.first); 1086 1087 // Find all sensors associated with this inventory item 1088 for (const std::pair<std::string, std::string>& pair : 1089 *sensorToInvMap) 1090 { 1091 if (pair.second == objPath) 1092 { 1093 // Update sensor status based on inventory item status 1094 updateSensorStatus(sensorsAsyncResp, pair.first, 1095 objDictEntry.second); 1096 } 1097 } 1098 } 1099 1100 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler exit"; 1101 }; 1102 1103 // Find DBus object path that implements ObjectManager for the current 1104 // connection. If no mapping found, default to "/". 1105 auto iter = objectMgrPaths->find(invConnection); 1106 const std::string& objectMgrPath = 1107 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1108 BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is " 1109 << objectMgrPath; 1110 1111 // Get all object paths and their interfaces for current connection 1112 crow::connections::systemBus->async_method_call( 1113 std::move(respHandler), invConnection, objectMgrPath, 1114 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1115 } 1116 1117 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus exit"; 1118 } 1119 1120 /** 1121 * @brief Gets connections that provide status information on inventory items. 1122 * 1123 * Gets the D-Bus connections (services) that provide the interfaces and 1124 * properties containing status information for the inventory items. 1125 * 1126 * Finds the connections asynchronously. Invokes callback when information has 1127 * been obtained. 1128 * 1129 * The callback must have the following signature: 1130 * @code 1131 * callback(std::shared_ptr<boost::container::flat_set<std::string>> 1132 * invConnections) 1133 * @endcode 1134 * 1135 * @param sensorsAsyncResp Pointer to object holding response data. 1136 * @param sensorToInvMap Mappings from sensor object path to the associated 1137 * inventory object path. 1138 * @param callback Callback to invoke when connections have been obtained. 1139 */ 1140 template <typename Callback> 1141 static void getInventoryItemsConnections( 1142 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1143 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1144 sensorToInvMap, 1145 Callback&& callback) 1146 { 1147 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1148 1149 const std::string path = "/xyz/openbmc_project/inventory"; 1150 const std::array<std::string, 2> interfaces = { 1151 "xyz.openbmc_project.Inventory.Item", 1152 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1153 1154 // Response handler for parsing output from GetSubTree 1155 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp, 1156 sensorToInvMap](const boost::system::error_code ec, 1157 const GetSubTreeType& subtree) { 1158 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1159 if (ec) 1160 { 1161 messages::internalError(sensorsAsyncResp->res); 1162 BMCWEB_LOG_ERROR 1163 << "getInventoryItemsConnections respHandler DBus error " << ec; 1164 return; 1165 } 1166 1167 // Make unique list of connections for desired inventory items 1168 std::shared_ptr<boost::container::flat_set<std::string>> 1169 invConnections = 1170 std::make_shared<boost::container::flat_set<std::string>>(); 1171 invConnections->reserve(8); 1172 1173 // Loop through objects from GetSubTree 1174 for (const std::pair< 1175 std::string, 1176 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1177 object : subtree) 1178 { 1179 // Look for inventory item object path in the sensor->inventory map 1180 const std::string& objPath = object.first; 1181 for (const std::pair<std::string, std::string>& pair : 1182 *sensorToInvMap) 1183 { 1184 if (pair.second == objPath) 1185 { 1186 // Store all connections to inventory item 1187 for (const std::pair<std::string, std::vector<std::string>>& 1188 objData : object.second) 1189 { 1190 const std::string& invConnection = objData.first; 1191 invConnections->insert(invConnection); 1192 } 1193 break; 1194 } 1195 } 1196 } 1197 callback(invConnections); 1198 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1199 }; 1200 1201 // Make call to ObjectMapper to find all inventory items 1202 crow::connections::systemBus->async_method_call( 1203 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 1204 "/xyz/openbmc_project/object_mapper", 1205 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 1206 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1207 } 1208 1209 /** 1210 * @brief Gets inventory items associated with the specified sensors. 1211 * 1212 * Looks for ObjectMapper associations from the specified sensors to related 1213 * inventory items. Builds map where key is sensor object path and value is 1214 * inventory item object path. 1215 * 1216 * Finds the inventory items asynchronously. Invokes callback when information 1217 * has been obtained. 1218 * 1219 * The callback must have the following signature: 1220 * @code 1221 * callback(std::shared_ptr<boost::container::flat_map< 1222 std::string, std::string>> sensorToInvMap) 1223 * @endcode 1224 * 1225 * @param sensorsAsyncResp Pointer to object holding response data. 1226 * @param sensorNames All sensors within the current chassis. 1227 * @param objectMgrPaths Mappings from connection name to DBus object path that 1228 * implements ObjectManager. 1229 * @param callback Callback to invoke when inventory items have been obtained. 1230 */ 1231 template <typename Callback> 1232 static void getInventoryItems( 1233 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1234 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1235 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1236 objectMgrPaths, 1237 Callback&& callback) 1238 { 1239 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 1240 1241 // Response handler for GetManagedObjects 1242 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp, 1243 sensorNames](const boost::system::error_code ec, 1244 dbus::utility::ManagedObjectType& resp) { 1245 BMCWEB_LOG_DEBUG << "getInventoryItems respHandler enter"; 1246 if (ec) 1247 { 1248 BMCWEB_LOG_ERROR << "getInventoryItems respHandler DBus error " 1249 << ec; 1250 messages::internalError(sensorsAsyncResp->res); 1251 return; 1252 } 1253 1254 // Loop through returned object paths 1255 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1256 sensorToInvMap = std::make_shared< 1257 boost::container::flat_map<std::string, std::string>>(); 1258 std::string sensorAssocPath; 1259 sensorAssocPath.reserve(128); // avoid memory allocations 1260 for (const auto& objDictEntry : resp) 1261 { 1262 const std::string& objPath = 1263 static_cast<const std::string&>(objDictEntry.first); 1264 const boost::container::flat_map< 1265 std::string, boost::container::flat_map< 1266 std::string, dbus::utility::DbusVariantType>>& 1267 interfacesDict = objDictEntry.second; 1268 1269 // If path is inventory association for one of the specified sensors 1270 for (const std::string& sensorName : *sensorNames) 1271 { 1272 sensorAssocPath = sensorName; 1273 sensorAssocPath += "/inventory"; 1274 if (objPath == sensorAssocPath) 1275 { 1276 // Get Association interface for object path 1277 auto assocIt = 1278 interfacesDict.find("xyz.openbmc_project.Association"); 1279 if (assocIt != interfacesDict.end()) 1280 { 1281 // Get inventory item from end point 1282 auto endpointsIt = assocIt->second.find("endpoints"); 1283 if (endpointsIt != assocIt->second.end()) 1284 { 1285 const std::vector<std::string>* endpoints = 1286 std::get_if<std::vector<std::string>>( 1287 &endpointsIt->second); 1288 if ((endpoints != nullptr) && !endpoints->empty()) 1289 { 1290 // Store sensor -> inventory item mapping 1291 const std::string& invItem = endpoints->front(); 1292 (*sensorToInvMap)[sensorName] = invItem; 1293 } 1294 } 1295 } 1296 break; 1297 } 1298 } 1299 } 1300 1301 // Call callback if at least one inventory item was found 1302 if (!sensorToInvMap->empty()) 1303 { 1304 callback(sensorToInvMap); 1305 } 1306 BMCWEB_LOG_DEBUG << "getInventoryItems respHandler exit"; 1307 }; 1308 1309 // Find DBus object path that implements ObjectManager for ObjectMapper 1310 std::string connection = "xyz.openbmc_project.ObjectMapper"; 1311 auto iter = objectMgrPaths->find(connection); 1312 const std::string& objectMgrPath = 1313 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1314 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1315 << objectMgrPath; 1316 1317 // Call GetManagedObjects on the ObjectMapper to get all associations 1318 crow::connections::systemBus->async_method_call( 1319 std::move(respHandler), connection, objectMgrPath, 1320 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1321 1322 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 1323 } 1324 1325 /** 1326 * @brief Checks the status of inventory items associated with sensors. 1327 * 1328 * Finds the inventory items that are associated with the specified sensors. 1329 * Gets the status of those inventory items. 1330 * 1331 * If the inventory items are not present or functional, the sensor status is 1332 * updated in the JSON response. 1333 * 1334 * In D-Bus, the hardware present and functional properties are typically on the 1335 * inventory item rather than the sensor. 1336 * 1337 * @param sensorsAsyncResp Pointer to object holding response data. 1338 * @param sensorNames All sensors within the current chassis. 1339 * @param objectMgrPaths Mappings from connection name to DBus object path that 1340 * implements ObjectManager. 1341 */ 1342 static void checkInventoryItemsStatus( 1343 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1344 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1345 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1346 objectMgrPaths) 1347 { 1348 BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus enter"; 1349 auto getInventoryItemsCb = 1350 [sensorsAsyncResp, 1351 objectMgrPaths](std::shared_ptr< 1352 boost::container::flat_map<std::string, std::string>> 1353 sensorToInvMap) { 1354 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 1355 auto getInventoryItemsConnectionsCb = 1356 [sensorsAsyncResp, sensorToInvMap, objectMgrPaths]( 1357 std::shared_ptr<boost::container::flat_set<std::string>> 1358 invConnections) { 1359 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 1360 1361 // Get status of inventory items and update sensors 1362 getInventoryItemsStatus(sensorsAsyncResp, sensorToInvMap, 1363 invConnections, objectMgrPaths); 1364 1365 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 1366 }; 1367 1368 // Get connections that provide status of inventory items 1369 getInventoryItemsConnections( 1370 sensorsAsyncResp, sensorToInvMap, 1371 std::move(getInventoryItemsConnectionsCb)); 1372 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 1373 }; 1374 1375 // Get inventory items that are associated with specified sensors 1376 getInventoryItems(sensorsAsyncResp, sensorNames, objectMgrPaths, 1377 std::move(getInventoryItemsCb)); 1378 BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus exit"; 1379 } 1380 1381 /** 1382 * @brief Gets the values of the specified sensors. 1383 * 1384 * Stores the results as JSON in the SensorsAsyncResp. 1385 * 1386 * Gets the sensor values asynchronously. Stores the results later when the 1387 * information has been obtained. 1388 * 1389 * The sensorNames set contains all sensors for the current chassis. 1390 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 1391 * requested type are included in the JSON output. 1392 * 1393 * To minimize the number of DBus calls, the DBus method 1394 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 1395 * values of all sensors provided by a connection (service). 1396 * 1397 * The connections set contains all the connections that provide sensor values. 1398 * 1399 * The objectMgrPaths map contains mappings from a connection name to the 1400 * corresponding DBus object path that implements ObjectManager. 1401 * 1402 * @param SensorsAsyncResp Pointer to object holding response data. 1403 * @param sensorNames All sensors within the current chassis. 1404 * @param connections Connections that provide sensor values. 1405 * @param objectMgrPaths Mappings from connection name to DBus object path that 1406 * implements ObjectManager. 1407 */ 1408 void getSensorData( 1409 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 1410 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1411 const boost::container::flat_set<std::string>& connections, 1412 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1413 objectMgrPaths) 1414 { 1415 BMCWEB_LOG_DEBUG << "getSensorData enter"; 1416 // Get managed objects from all services exposing sensors 1417 for (const std::string& connection : connections) 1418 { 1419 // Response handler to process managed objects 1420 auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames, 1421 objectMgrPaths]( 1422 const boost::system::error_code ec, 1423 ManagedObjectsVectorType& resp) { 1424 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 1425 if (ec) 1426 { 1427 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 1428 messages::internalError(SensorsAsyncResp->res); 1429 return; 1430 } 1431 // Go through all objects and update response with sensor data 1432 for (const auto& objDictEntry : resp) 1433 { 1434 const std::string& objPath = 1435 static_cast<const std::string&>(objDictEntry.first); 1436 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 1437 << objPath; 1438 1439 std::vector<std::string> split; 1440 // Reserve space for 1441 // /xyz/openbmc_project/sensors/<name>/<subname> 1442 split.reserve(6); 1443 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 1444 if (split.size() < 6) 1445 { 1446 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 1447 << objPath; 1448 continue; 1449 } 1450 // These indexes aren't intuitive, as boost::split puts an empty 1451 // string at the beginning 1452 const std::string& sensorType = split[4]; 1453 const std::string& sensorName = split[5]; 1454 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 1455 << " sensorType " << sensorType; 1456 if (sensorNames->find(objPath) == sensorNames->end()) 1457 { 1458 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 1459 continue; 1460 } 1461 1462 const char* fieldName = nullptr; 1463 if (sensorType == "temperature") 1464 { 1465 fieldName = "Temperatures"; 1466 } 1467 else if (sensorType == "fan" || sensorType == "fan_tach" || 1468 sensorType == "fan_pwm") 1469 { 1470 fieldName = "Fans"; 1471 } 1472 else if (sensorType == "voltage") 1473 { 1474 fieldName = "Voltages"; 1475 } 1476 else if (sensorType == "current") 1477 { 1478 fieldName = "PowerSupplies"; 1479 } 1480 else if (sensorType == "power") 1481 { 1482 if (!sensorName.compare("total_power")) 1483 { 1484 fieldName = "PowerControl"; 1485 } 1486 else 1487 { 1488 fieldName = "PowerSupplies"; 1489 } 1490 } 1491 else 1492 { 1493 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 1494 << sensorType; 1495 continue; 1496 } 1497 1498 nlohmann::json& tempArray = 1499 SensorsAsyncResp->res.jsonValue[fieldName]; 1500 1501 if ((fieldName == "PowerSupplies" || 1502 fieldName == "PowerControl") && 1503 !tempArray.empty()) 1504 { 1505 // For power supplies and power control put multiple 1506 // "sensors" into a single power supply or power control 1507 // entry, so only create the first one 1508 } 1509 else if (fieldName == "PowerControl") 1510 { 1511 // Put multiple "sensors" into a single PowerControl. 1512 // Follows MemberId naming and naming in power.hpp. 1513 tempArray.push_back( 1514 {{"@odata.id", "/redfish/v1/Chassis/" + 1515 SensorsAsyncResp->chassisId + "/" + 1516 SensorsAsyncResp->chassisSubNode + 1517 "#/" + fieldName + "/0"}}); 1518 } 1519 else 1520 { 1521 tempArray.push_back( 1522 {{"@odata.id", "/redfish/v1/Chassis/" + 1523 SensorsAsyncResp->chassisId + "/" + 1524 SensorsAsyncResp->chassisSubNode + 1525 "#/" + fieldName + "/"}}); 1526 } 1527 nlohmann::json& sensorJson = tempArray.back(); 1528 1529 objectInterfacesToJson(sensorName, sensorType, 1530 objDictEntry.second, sensorJson); 1531 } 1532 if (SensorsAsyncResp.use_count() == 1) 1533 { 1534 sortJSONResponse(SensorsAsyncResp); 1535 checkInventoryItemsStatus(SensorsAsyncResp, sensorNames, 1536 objectMgrPaths); 1537 if (SensorsAsyncResp->chassisSubNode == "Thermal") 1538 { 1539 populateFanRedundancy(SensorsAsyncResp); 1540 } 1541 } 1542 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 1543 }; 1544 1545 // Find DBus object path that implements ObjectManager for the current 1546 // connection. If no mapping found, default to "/". 1547 auto iter = objectMgrPaths->find(connection); 1548 const std::string& objectMgrPath = 1549 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1550 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1551 << objectMgrPath; 1552 1553 crow::connections::systemBus->async_method_call( 1554 getManagedObjectsCb, connection, objectMgrPath, 1555 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1556 }; 1557 BMCWEB_LOG_DEBUG << "getSensorData exit"; 1558 } 1559 1560 /** 1561 * @brief Entry point for retrieving sensors data related to requested 1562 * chassis. 1563 * @param SensorsAsyncResp Pointer to object holding response data 1564 */ 1565 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 1566 { 1567 BMCWEB_LOG_DEBUG << "getChassisData enter"; 1568 auto getChassisCb = 1569 [SensorsAsyncResp]( 1570 std::shared_ptr<boost::container::flat_set<std::string>> 1571 sensorNames) { 1572 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 1573 auto getConnectionCb = [SensorsAsyncResp, sensorNames]( 1574 const boost::container::flat_set< 1575 std::string>& connections) { 1576 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 1577 auto getObjectManagerPathsCb = 1578 [SensorsAsyncResp, sensorNames, connections]( 1579 std::shared_ptr<boost::container::flat_map<std::string, 1580 std::string>> 1581 objectMgrPaths) { 1582 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 1583 // Get sensor data and store results in JSON 1584 // response 1585 getSensorData(SensorsAsyncResp, sensorNames, 1586 connections, objectMgrPaths); 1587 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 1588 }; 1589 1590 // Get mapping from connection names to the DBus object 1591 // paths that implement the ObjectManager interface 1592 getObjectManagerPaths(SensorsAsyncResp, 1593 std::move(getObjectManagerPathsCb)); 1594 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 1595 }; 1596 1597 // Get set of connections that provide sensor values 1598 getConnections(SensorsAsyncResp, sensorNames, 1599 std::move(getConnectionCb)); 1600 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 1601 }; 1602 SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array(); 1603 1604 // Get set of sensors in chassis 1605 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 1606 BMCWEB_LOG_DEBUG << "getChassisData exit"; 1607 }; 1608 1609 /** 1610 * @brief Find the requested sensorName in the list of all sensors supplied by 1611 * the chassis node 1612 * 1613 * @param sensorName The sensor name supplied in the PATCH request 1614 * @param sensorsList The list of sensors managed by the chassis node 1615 * @param sensorsModified The list of sensors that were found as a result of 1616 * repeated calls to this function 1617 */ 1618 bool findSensorNameUsingSensorPath( 1619 std::string_view sensorName, 1620 boost::container::flat_set<std::string>& sensorsList, 1621 boost::container::flat_set<std::string>& sensorsModified) 1622 { 1623 for (std::string_view chassisSensor : sensorsList) 1624 { 1625 std::size_t pos = chassisSensor.rfind("/"); 1626 if (pos >= (chassisSensor.size() - 1)) 1627 { 1628 continue; 1629 } 1630 std::string_view thisSensorName = chassisSensor.substr(pos + 1); 1631 if (thisSensorName == sensorName) 1632 { 1633 sensorsModified.emplace(chassisSensor); 1634 return true; 1635 } 1636 } 1637 return false; 1638 } 1639 1640 /** 1641 * @brief Entry point for overriding sensor values of given sensor 1642 * 1643 * @param res response object 1644 * @param req request object 1645 * @param params parameter passed for CRUD 1646 * @param typeList TypeList of sensors for the resource queried 1647 * @param chassisSubNode Chassis Node for which the query has to happen 1648 */ 1649 void setSensorOverride(crow::Response& res, const crow::Request& req, 1650 const std::vector<std::string>& params, 1651 const std::vector<const char*> typeList, 1652 const std::string& chassisSubNode) 1653 { 1654 1655 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 1656 // override) based on another d-bus announcement to be more generic. 1657 if (params.size() != 1) 1658 { 1659 messages::internalError(res); 1660 res.end(); 1661 return; 1662 } 1663 1664 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1665 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1666 std::optional<std::vector<nlohmann::json>> fanCollections; 1667 std::vector<nlohmann::json> voltageCollections; 1668 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1669 << "\n"; 1670 1671 if (chassisSubNode == "Thermal") 1672 { 1673 if (!json_util::readJson(req, res, "Temperatures", 1674 temperatureCollections, "Fans", 1675 fanCollections)) 1676 { 1677 return; 1678 } 1679 if (!temperatureCollections && !fanCollections) 1680 { 1681 messages::resourceNotFound(res, "Thermal", 1682 "Temperatures / Voltages"); 1683 res.end(); 1684 return; 1685 } 1686 if (temperatureCollections) 1687 { 1688 allCollections.emplace("Temperatures", 1689 *std::move(temperatureCollections)); 1690 } 1691 if (fanCollections) 1692 { 1693 allCollections.emplace("Fans", *std::move(fanCollections)); 1694 } 1695 } 1696 else if (chassisSubNode == "Power") 1697 { 1698 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1699 { 1700 return; 1701 } 1702 allCollections.emplace("Voltages", std::move(voltageCollections)); 1703 } 1704 else 1705 { 1706 res.result(boost::beast::http::status::not_found); 1707 res.end(); 1708 return; 1709 } 1710 1711 const char* propertyValueName; 1712 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1713 std::string memberId; 1714 double value; 1715 for (auto& collectionItems : allCollections) 1716 { 1717 if (collectionItems.first == "Temperatures") 1718 { 1719 propertyValueName = "ReadingCelsius"; 1720 } 1721 else if (collectionItems.first == "Fans") 1722 { 1723 propertyValueName = "Reading"; 1724 } 1725 else 1726 { 1727 propertyValueName = "ReadingVolts"; 1728 } 1729 for (auto& item : collectionItems.second) 1730 { 1731 if (!json_util::readJson(item, res, "MemberId", memberId, 1732 propertyValueName, value)) 1733 { 1734 return; 1735 } 1736 overrideMap.emplace(memberId, 1737 std::make_pair(value, collectionItems.first)); 1738 } 1739 } 1740 const std::string& chassisName = params[0]; 1741 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1742 res, chassisName, typeList, chassisSubNode); 1743 auto getChassisSensorListCb = [sensorAsyncResp, 1744 overrideMap](const std::shared_ptr< 1745 boost::container::flat_set< 1746 std::string>> 1747 sensorsList) { 1748 // Match sensor names in the PATCH request to those managed by the 1749 // chassis node 1750 const std::shared_ptr<boost::container::flat_set<std::string>> 1751 sensorNames = 1752 std::make_shared<boost::container::flat_set<std::string>>(); 1753 for (const auto& item : overrideMap) 1754 { 1755 const auto& sensor = item.first; 1756 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 1757 *sensorNames)) 1758 { 1759 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1760 messages::resourceNotFound(sensorAsyncResp->res, 1761 item.second.second, item.first); 1762 return; 1763 } 1764 } 1765 // Get the connection to which the memberId belongs 1766 auto getObjectsWithConnectionCb = 1767 [sensorAsyncResp, overrideMap]( 1768 const boost::container::flat_set<std::string>& connections, 1769 const std::set<std::pair<std::string, std::string>>& 1770 objectsWithConnection) { 1771 if (objectsWithConnection.size() != overrideMap.size()) 1772 { 1773 BMCWEB_LOG_INFO 1774 << "Unable to find all objects with proper connection " 1775 << objectsWithConnection.size() << " requested " 1776 << overrideMap.size() << "\n"; 1777 messages::resourceNotFound( 1778 sensorAsyncResp->res, 1779 sensorAsyncResp->chassisSubNode == "Thermal" 1780 ? "Temperatures" 1781 : "Voltages", 1782 "Count"); 1783 return; 1784 } 1785 for (const auto& item : objectsWithConnection) 1786 { 1787 1788 auto lastPos = item.first.rfind('/'); 1789 if (lastPos == std::string::npos) 1790 { 1791 messages::internalError(sensorAsyncResp->res); 1792 return; 1793 } 1794 std::string sensorName = item.first.substr(lastPos + 1); 1795 1796 const auto& iterator = overrideMap.find(sensorName); 1797 if (iterator == overrideMap.end()) 1798 { 1799 BMCWEB_LOG_INFO << "Unable to find sensor object" 1800 << item.first << "\n"; 1801 messages::internalError(sensorAsyncResp->res); 1802 return; 1803 } 1804 crow::connections::systemBus->async_method_call( 1805 [sensorAsyncResp](const boost::system::error_code ec) { 1806 if (ec) 1807 { 1808 BMCWEB_LOG_DEBUG 1809 << "setOverrideValueStatus DBUS error: " 1810 << ec; 1811 messages::internalError(sensorAsyncResp->res); 1812 return; 1813 } 1814 }, 1815 item.second, item.first, 1816 "org.freedesktop.DBus.Properties", "Set", 1817 "xyz.openbmc_project.Sensor.Value", "Value", 1818 sdbusplus::message::variant<double>( 1819 iterator->second.first)); 1820 } 1821 }; 1822 // Get object with connection for the given sensor name 1823 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1824 std::move(getObjectsWithConnectionCb)); 1825 }; 1826 // get full sensor list for the given chassisId and cross verify the sensor. 1827 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1828 } 1829 1830 } // namespace redfish 1831