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 unit = "PowerConsumedWatts"; 572 } 573 else if (sensorNameLower.find("input") != std::string::npos) 574 { 575 unit = "PowerInputWatts"; 576 } 577 else 578 { 579 unit = "PowerOutputWatts"; 580 } 581 } 582 else 583 { 584 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 585 return; 586 } 587 // Map of dbus interface name, dbus property name and redfish property_name 588 std::vector<std::tuple<const char*, const char*, const char*>> properties; 589 properties.reserve(7); 590 591 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 592 593 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 594 if ((sensorType != "current") && (sensorType != "power")) 595 { 596 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 597 "WarningHigh", "UpperThresholdNonCritical"); 598 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 599 "WarningLow", "LowerThresholdNonCritical"); 600 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 601 "CriticalHigh", "UpperThresholdCritical"); 602 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 603 "CriticalLow", "LowerThresholdCritical"); 604 } 605 606 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 607 608 if (sensorType == "temperature") 609 { 610 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 611 "MinReadingRangeTemp"); 612 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 613 "MaxReadingRangeTemp"); 614 } 615 else if ((sensorType != "current") && (sensorType != "power")) 616 { 617 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 618 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 619 "MinReadingRange"); 620 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 621 "MaxReadingRange"); 622 } 623 624 for (const std::tuple<const char*, const char*, const char*>& p : 625 properties) 626 { 627 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 628 if (interfaceProperties != interfacesDict.end()) 629 { 630 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 631 if (valueIt != interfaceProperties->second.end()) 632 { 633 const SensorVariant& valueVariant = valueIt->second; 634 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 635 // Attempt to pull the int64 directly 636 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 637 638 const double* doubleValue = std::get_if<double>(&valueVariant); 639 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant); 640 double temp = 0.0; 641 if (int64Value != nullptr) 642 { 643 temp = *int64Value; 644 } 645 else if (doubleValue != nullptr) 646 { 647 temp = *doubleValue; 648 } 649 else if (uValue != nullptr) 650 { 651 temp = *uValue; 652 } 653 else 654 { 655 BMCWEB_LOG_ERROR 656 << "Got value interface that wasn't int or double"; 657 continue; 658 } 659 temp = temp * std::pow(10, scaleMultiplier); 660 if (forceToInt) 661 { 662 valueIt = static_cast<int64_t>(temp); 663 } 664 else 665 { 666 valueIt = temp; 667 } 668 } 669 } 670 } 671 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 672 } 673 674 static void 675 populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp) 676 { 677 crow::connections::systemBus->async_method_call( 678 [sensorsAsyncResp](const boost::system::error_code ec, 679 const GetSubTreeType& resp) { 680 if (ec) 681 { 682 return; // don't have to have this interface 683 } 684 for (const std::pair<std::string, 685 std::vector<std::pair< 686 std::string, std::vector<std::string>>>>& 687 pathPair : resp) 688 { 689 const std::string& path = pathPair.first; 690 const std::vector< 691 std::pair<std::string, std::vector<std::string>>>& objDict = 692 pathPair.second; 693 if (objDict.empty()) 694 { 695 continue; // this should be impossible 696 } 697 698 const std::string& owner = objDict.begin()->first; 699 crow::connections::systemBus->async_method_call( 700 [path, owner, 701 sensorsAsyncResp](const boost::system::error_code ec, 702 std::variant<std::vector<std::string>> 703 variantEndpoints) { 704 if (ec) 705 { 706 return; // if they don't have an association we 707 // can't tell what chassis is 708 } 709 // verify part of the right chassis 710 auto endpoints = std::get_if<std::vector<std::string>>( 711 &variantEndpoints); 712 713 if (endpoints == nullptr) 714 { 715 BMCWEB_LOG_ERROR << "Invalid association interface"; 716 messages::internalError(sensorsAsyncResp->res); 717 return; 718 } 719 720 auto found = std::find_if( 721 endpoints->begin(), endpoints->end(), 722 [sensorsAsyncResp](const std::string& entry) { 723 return entry.find( 724 sensorsAsyncResp->chassisId) != 725 std::string::npos; 726 }); 727 728 if (found == endpoints->end()) 729 { 730 return; 731 } 732 crow::connections::systemBus->async_method_call( 733 [path, sensorsAsyncResp]( 734 const boost::system::error_code ec, 735 const boost::container::flat_map< 736 std::string, 737 std::variant<uint8_t, 738 std::vector<std::string>, 739 std::string>>& ret) { 740 if (ec) 741 { 742 return; // don't have to have this 743 // interface 744 } 745 auto findFailures = ret.find("AllowedFailures"); 746 auto findCollection = ret.find("Collection"); 747 auto findStatus = ret.find("Status"); 748 749 if (findFailures == ret.end() || 750 findCollection == ret.end() || 751 findStatus == ret.end()) 752 { 753 BMCWEB_LOG_ERROR 754 << "Invalid redundancy interface"; 755 messages::internalError( 756 sensorsAsyncResp->res); 757 return; 758 } 759 760 auto allowedFailures = std::get_if<uint8_t>( 761 &(findFailures->second)); 762 auto collection = 763 std::get_if<std::vector<std::string>>( 764 &(findCollection->second)); 765 auto status = std::get_if<std::string>( 766 &(findStatus->second)); 767 768 if (allowedFailures == nullptr || 769 collection == nullptr || status == nullptr) 770 { 771 772 BMCWEB_LOG_ERROR 773 << "Invalid redundancy interface " 774 "types"; 775 messages::internalError( 776 sensorsAsyncResp->res); 777 return; 778 } 779 size_t lastSlash = path.rfind("/"); 780 if (lastSlash == std::string::npos) 781 { 782 // this should be impossible 783 messages::internalError( 784 sensorsAsyncResp->res); 785 return; 786 } 787 std::string name = path.substr(lastSlash + 1); 788 std::replace(name.begin(), name.end(), '_', 789 ' '); 790 791 std::string health; 792 793 if (boost::ends_with(*status, "Full")) 794 { 795 health = "OK"; 796 } 797 else if (boost::ends_with(*status, "Degraded")) 798 { 799 health = "Warning"; 800 } 801 else 802 { 803 health = "Critical"; 804 } 805 std::vector<nlohmann::json> redfishCollection; 806 const auto& fanRedfish = 807 sensorsAsyncResp->res.jsonValue["Fans"]; 808 for (const std::string& item : *collection) 809 { 810 lastSlash = item.rfind("/"); 811 // make a copy as collection is const 812 std::string itemName = 813 item.substr(lastSlash + 1); 814 /* 815 todo(ed): merge patch that fixes the names 816 std::replace(itemName.begin(), 817 itemName.end(), '_', ' ');*/ 818 auto schemaItem = std::find_if( 819 fanRedfish.begin(), fanRedfish.end(), 820 [itemName](const nlohmann::json& fan) { 821 return fan["MemberId"] == itemName; 822 }); 823 if (schemaItem != fanRedfish.end()) 824 { 825 redfishCollection.push_back( 826 {{"@odata.id", 827 (*schemaItem)["@odata.id"]}}); 828 } 829 else 830 { 831 BMCWEB_LOG_ERROR 832 << "failed to find fan in schema"; 833 messages::internalError( 834 sensorsAsyncResp->res); 835 return; 836 } 837 } 838 839 auto& resp = sensorsAsyncResp->res 840 .jsonValue["Redundancy"]; 841 resp.push_back( 842 {{"@odata.id", 843 "/refish/v1/Chassis/" + 844 sensorsAsyncResp->chassisId + "/" + 845 sensorsAsyncResp->chassisSubNode + 846 "#/Redundancy/" + 847 std::to_string(resp.size())}, 848 {"@odata.type", 849 "#Redundancy.v1_3_2.Redundancy"}, 850 {"MinNumNeeded", 851 collection->size() - *allowedFailures}, 852 {"MemberId", name}, 853 {"Mode", "N+m"}, 854 {"Name", name}, 855 {"RedundancySet", redfishCollection}, 856 {"Status", 857 {{"Health", health}, 858 {"State", "Enabled"}}}}); 859 }, 860 owner, path, "org.freedesktop.DBus.Properties", 861 "GetAll", 862 "xyz.openbmc_project.Control.FanRedundancy"); 863 }, 864 "xyz.openbmc_project.ObjectMapper", path + "/chassis", 865 "org.freedesktop.DBus.Properties", "Get", 866 "xyz.openbmc_project.Association", "endpoints"); 867 } 868 }, 869 "xyz.openbmc_project.ObjectMapper", 870 "/xyz/openbmc_project/object_mapper", 871 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 872 "/xyz/openbmc_project/control", 2, 873 std::array<const char*, 1>{ 874 "xyz.openbmc_project.Control.FanRedundancy"}); 875 } 876 877 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 878 { 879 nlohmann::json& response = SensorsAsyncResp->res.jsonValue; 880 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 881 if (SensorsAsyncResp->chassisSubNode == "Power") 882 { 883 sensorHeaders = {"Voltages", "PowerSupplies"}; 884 } 885 for (const std::string& sensorGroup : sensorHeaders) 886 { 887 nlohmann::json::iterator entry = response.find(sensorGroup); 888 if (entry != response.end()) 889 { 890 std::sort(entry->begin(), entry->end(), 891 [](nlohmann::json& c1, nlohmann::json& c2) { 892 return c1["Name"] < c2["Name"]; 893 }); 894 895 // add the index counts to the end of each entry 896 size_t count = 0; 897 for (nlohmann::json& sensorJson : *entry) 898 { 899 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 900 if (odata == sensorJson.end()) 901 { 902 continue; 903 } 904 std::string* value = odata->get_ptr<std::string*>(); 905 if (value != nullptr) 906 { 907 *value += std::to_string(count); 908 count++; 909 } 910 } 911 } 912 } 913 } 914 915 /** 916 * @brief Finds the JSON object for the specified sensor. 917 * 918 * Searches the JSON response in sensorsAsyncResp for an object corresponding to 919 * the specified sensor. 920 * 921 * @param sensorsAsyncResp Pointer to object holding response data. 922 * @param sensorName DBus object path to the sensor. 923 * @return Pointer to JSON object, or nullptr if object not found. 924 */ 925 static nlohmann::json* 926 findSensorJson(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 927 const std::string& sensorName) 928 { 929 // Get base name of sensor 930 std::size_t lastSlash = sensorName.rfind('/'); 931 if (lastSlash != std::string::npos) 932 { 933 std::string baseSensorName = sensorName.substr(lastSlash + 1); 934 935 // Loop through JSON sensor groups that could contain sensor 936 nlohmann::json& response = sensorsAsyncResp->res.jsonValue; 937 std::array<std::string, 4> sensorGroups{"Temperatures", "Fans", 938 "Voltages", "PowerSupplies"}; 939 for (const std::string& sensorGroup : sensorGroups) 940 { 941 nlohmann::json::iterator groupIt = response.find(sensorGroup); 942 if (groupIt != response.end()) 943 { 944 // Loop through sensors in current group 945 for (nlohmann::json& sensorJson : *groupIt) 946 { 947 // Check if this is the sensor we are looking for 948 nlohmann::json::iterator memberIdIt = 949 sensorJson.find("MemberId"); 950 if (memberIdIt != sensorJson.end()) 951 { 952 std::string* memberId = 953 memberIdIt->get_ptr<std::string*>(); 954 if ((memberId != nullptr) && 955 (*memberId == baseSensorName)) 956 { 957 return &sensorJson; 958 } 959 } 960 } 961 } 962 } 963 } 964 965 // Unable to find JSON object for specified sensor 966 return nullptr; 967 } 968 969 /** 970 * @brief Updates sensor status in JSON response based on inventory item status. 971 * 972 * Updates the status of the specified sensor based on the status of a related 973 * inventory item. 974 * 975 * Modifies the Redfish Status property in the JSON response if the inventory 976 * item indicates the hardware is not present or not functional. 977 * 978 * The D-Bus Present and Functional properties are typically on the inventory 979 * item rather than the sensor. 980 * 981 * @param sensorsAsyncResp Pointer to object holding response data. 982 * @param sensorName DBus object path to the sensor. 983 * @param interfacesDict Map containing the interfaces and properties of the 984 * inventory item associated with this sensor. 985 */ 986 static void updateSensorStatus( 987 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 988 const std::string& sensorName, 989 const boost::container::flat_map< 990 std::string, boost::container::flat_map<std::string, SensorVariant>>& 991 interfacesDict) 992 { 993 // Find the JSON object in the response for this sensor 994 nlohmann::json* sensorJson = findSensorJson(sensorsAsyncResp, sensorName); 995 if (sensorJson != nullptr) 996 { 997 // Get Inventory.Item.Present property of inventory item 998 auto itemIt = interfacesDict.find("xyz.openbmc_project.Inventory.Item"); 999 if (itemIt != interfacesDict.end()) 1000 { 1001 auto presentIt = itemIt->second.find("Present"); 1002 if (presentIt != itemIt->second.end()) 1003 { 1004 const bool* present = std::get_if<bool>(&presentIt->second); 1005 if ((present != nullptr) && (*present == false)) 1006 { 1007 // Inventory item is not present; update sensor State 1008 (*sensorJson)["Status"]["State"] = "Absent"; 1009 } 1010 } 1011 } 1012 1013 // Get OperationalStatus.Functional property of inventory item 1014 auto opStatusIt = interfacesDict.find( 1015 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 1016 if (opStatusIt != interfacesDict.end()) 1017 { 1018 auto functionalIt = opStatusIt->second.find("Functional"); 1019 if (functionalIt != opStatusIt->second.end()) 1020 { 1021 const bool* functional = 1022 std::get_if<bool>(&functionalIt->second); 1023 if ((functional != nullptr) && (*functional == false)) 1024 { 1025 // Inventory item is not functional; update sensor Health 1026 (*sensorJson)["Status"]["Health"] = "Critical"; 1027 } 1028 } 1029 } 1030 } 1031 } 1032 1033 /** 1034 * @brief Gets status of inventory items associated with sensors. 1035 * 1036 * Gets the D-Bus status properties for the inventory items associated with 1037 * sensors. 1038 * 1039 * Updates the Redfish sensors status in the JSON response, if needed, based on 1040 * the inventory items status. 1041 * 1042 * @param sensorsAsyncResp Pointer to object holding response data. 1043 * @param sensorToInvMap Mappings from sensor object path to the associated 1044 * inventory object path. 1045 * @param invConnections Connections that provide the status 1046 * interfaces/properties for the inventory items. 1047 * @param objectMgrPaths Mappings from connection name to DBus object path that 1048 * implements ObjectManager. 1049 */ 1050 static void getInventoryItemsStatus( 1051 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1052 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1053 sensorToInvMap, 1054 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1055 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1056 objectMgrPaths) 1057 { 1058 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus enter"; 1059 1060 // Loop through all connections providing inventory item status 1061 for (const std::string& invConnection : *invConnections) 1062 { 1063 // Response handler for GetManagedObjects 1064 auto respHandler = [sensorsAsyncResp, 1065 sensorToInvMap](const boost::system::error_code ec, 1066 ManagedObjectsVectorType& resp) { 1067 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler enter"; 1068 if (ec) 1069 { 1070 BMCWEB_LOG_ERROR 1071 << "getInventoryItemsStatus respHandler DBus error " << ec; 1072 messages::internalError(sensorsAsyncResp->res); 1073 return; 1074 } 1075 1076 // Loop through returned object paths 1077 for (const auto& objDictEntry : resp) 1078 { 1079 const std::string& objPath = 1080 static_cast<const std::string&>(objDictEntry.first); 1081 1082 // Find all sensors associated with this inventory item 1083 for (const std::pair<std::string, std::string>& pair : 1084 *sensorToInvMap) 1085 { 1086 if (pair.second == objPath) 1087 { 1088 // Update sensor status based on inventory item status 1089 updateSensorStatus(sensorsAsyncResp, pair.first, 1090 objDictEntry.second); 1091 } 1092 } 1093 } 1094 1095 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler exit"; 1096 }; 1097 1098 // Find DBus object path that implements ObjectManager for the current 1099 // connection. If no mapping found, default to "/". 1100 auto iter = objectMgrPaths->find(invConnection); 1101 const std::string& objectMgrPath = 1102 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1103 BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is " 1104 << objectMgrPath; 1105 1106 // Get all object paths and their interfaces for current connection 1107 crow::connections::systemBus->async_method_call( 1108 std::move(respHandler), invConnection, objectMgrPath, 1109 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1110 } 1111 1112 BMCWEB_LOG_DEBUG << "getInventoryItemsStatus exit"; 1113 } 1114 1115 /** 1116 * @brief Gets connections that provide status information on inventory items. 1117 * 1118 * Gets the D-Bus connections (services) that provide the interfaces and 1119 * properties containing status information for the inventory items. 1120 * 1121 * Finds the connections asynchronously. Invokes callback when information has 1122 * been obtained. 1123 * 1124 * The callback must have the following signature: 1125 * @code 1126 * callback(std::shared_ptr<boost::container::flat_set<std::string>> 1127 * invConnections) 1128 * @endcode 1129 * 1130 * @param sensorsAsyncResp Pointer to object holding response data. 1131 * @param sensorToInvMap Mappings from sensor object path to the associated 1132 * inventory object path. 1133 * @param callback Callback to invoke when connections have been obtained. 1134 */ 1135 template <typename Callback> 1136 static void getInventoryItemsConnections( 1137 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1138 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1139 sensorToInvMap, 1140 Callback&& callback) 1141 { 1142 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1143 1144 const std::string path = "/xyz/openbmc_project/inventory"; 1145 const std::array<std::string, 2> interfaces = { 1146 "xyz.openbmc_project.Inventory.Item", 1147 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1148 1149 // Response handler for parsing output from GetSubTree 1150 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp, 1151 sensorToInvMap](const boost::system::error_code ec, 1152 const GetSubTreeType& subtree) { 1153 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1154 if (ec) 1155 { 1156 messages::internalError(sensorsAsyncResp->res); 1157 BMCWEB_LOG_ERROR 1158 << "getInventoryItemsConnections respHandler DBus error " << ec; 1159 return; 1160 } 1161 1162 // Make unique list of connections for desired inventory items 1163 std::shared_ptr<boost::container::flat_set<std::string>> 1164 invConnections = 1165 std::make_shared<boost::container::flat_set<std::string>>(); 1166 invConnections->reserve(8); 1167 1168 // Loop through objects from GetSubTree 1169 for (const std::pair< 1170 std::string, 1171 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1172 object : subtree) 1173 { 1174 // Look for inventory item object path in the sensor->inventory map 1175 const std::string& objPath = object.first; 1176 for (const std::pair<std::string, std::string>& pair : 1177 *sensorToInvMap) 1178 { 1179 if (pair.second == objPath) 1180 { 1181 // Store all connections to inventory item 1182 for (const std::pair<std::string, std::vector<std::string>>& 1183 objData : object.second) 1184 { 1185 const std::string& invConnection = objData.first; 1186 invConnections->insert(invConnection); 1187 } 1188 break; 1189 } 1190 } 1191 } 1192 callback(invConnections); 1193 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1194 }; 1195 1196 // Make call to ObjectMapper to find all inventory items 1197 crow::connections::systemBus->async_method_call( 1198 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 1199 "/xyz/openbmc_project/object_mapper", 1200 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 1201 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1202 } 1203 1204 /** 1205 * @brief Gets inventory items associated with the specified sensors. 1206 * 1207 * Looks for ObjectMapper associations from the specified sensors to related 1208 * inventory items. Builds map where key is sensor object path and value is 1209 * inventory item object path. 1210 * 1211 * Finds the inventory items asynchronously. Invokes callback when information 1212 * has been obtained. 1213 * 1214 * The callback must have the following signature: 1215 * @code 1216 * callback(std::shared_ptr<boost::container::flat_map< 1217 std::string, std::string>> sensorToInvMap) 1218 * @endcode 1219 * 1220 * @param sensorsAsyncResp Pointer to object holding response data. 1221 * @param sensorNames All sensors within the current chassis. 1222 * @param objectMgrPaths Mappings from connection name to DBus object path that 1223 * implements ObjectManager. 1224 * @param callback Callback to invoke when inventory items have been obtained. 1225 */ 1226 template <typename Callback> 1227 static void getInventoryItems( 1228 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1229 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1230 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1231 objectMgrPaths, 1232 Callback&& callback) 1233 { 1234 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 1235 1236 // Response handler for GetManagedObjects 1237 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp, 1238 sensorNames](const boost::system::error_code ec, 1239 dbus::utility::ManagedObjectType& resp) { 1240 BMCWEB_LOG_DEBUG << "getInventoryItems respHandler enter"; 1241 if (ec) 1242 { 1243 BMCWEB_LOG_ERROR << "getInventoryItems respHandler DBus error " 1244 << ec; 1245 messages::internalError(sensorsAsyncResp->res); 1246 return; 1247 } 1248 1249 // Loop through returned object paths 1250 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1251 sensorToInvMap = std::make_shared< 1252 boost::container::flat_map<std::string, std::string>>(); 1253 std::string sensorAssocPath; 1254 sensorAssocPath.reserve(128); // avoid memory allocations 1255 for (const auto& objDictEntry : resp) 1256 { 1257 const std::string& objPath = 1258 static_cast<const std::string&>(objDictEntry.first); 1259 const boost::container::flat_map< 1260 std::string, boost::container::flat_map< 1261 std::string, dbus::utility::DbusVariantType>>& 1262 interfacesDict = objDictEntry.second; 1263 1264 // If path is inventory association for one of the specified sensors 1265 for (const std::string& sensorName : *sensorNames) 1266 { 1267 sensorAssocPath = sensorName; 1268 sensorAssocPath += "/inventory"; 1269 if (objPath == sensorAssocPath) 1270 { 1271 // Get Association interface for object path 1272 auto assocIt = 1273 interfacesDict.find("xyz.openbmc_project.Association"); 1274 if (assocIt != interfacesDict.end()) 1275 { 1276 // Get inventory item from end point 1277 auto endpointsIt = assocIt->second.find("endpoints"); 1278 if (endpointsIt != assocIt->second.end()) 1279 { 1280 const std::vector<std::string>* endpoints = 1281 std::get_if<std::vector<std::string>>( 1282 &endpointsIt->second); 1283 if ((endpoints != nullptr) && !endpoints->empty()) 1284 { 1285 // Store sensor -> inventory item mapping 1286 const std::string& invItem = endpoints->front(); 1287 (*sensorToInvMap)[sensorName] = invItem; 1288 } 1289 } 1290 } 1291 break; 1292 } 1293 } 1294 } 1295 1296 // Call callback if at least one inventory item was found 1297 if (!sensorToInvMap->empty()) 1298 { 1299 callback(sensorToInvMap); 1300 } 1301 BMCWEB_LOG_DEBUG << "getInventoryItems respHandler exit"; 1302 }; 1303 1304 // Find DBus object path that implements ObjectManager for ObjectMapper 1305 std::string connection = "xyz.openbmc_project.ObjectMapper"; 1306 auto iter = objectMgrPaths->find(connection); 1307 const std::string& objectMgrPath = 1308 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1309 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1310 << objectMgrPath; 1311 1312 // Call GetManagedObjects on the ObjectMapper to get all associations 1313 crow::connections::systemBus->async_method_call( 1314 std::move(respHandler), connection, objectMgrPath, 1315 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1316 1317 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 1318 } 1319 1320 /** 1321 * @brief Checks the status of inventory items associated with sensors. 1322 * 1323 * Finds the inventory items that are associated with the specified sensors. 1324 * Gets the status of those inventory items. 1325 * 1326 * If the inventory items are not present or functional, the sensor status is 1327 * updated in the JSON response. 1328 * 1329 * In D-Bus, the hardware present and functional properties are typically on the 1330 * inventory item rather than the sensor. 1331 * 1332 * @param sensorsAsyncResp Pointer to object holding response data. 1333 * @param sensorNames All sensors within the current chassis. 1334 * @param objectMgrPaths Mappings from connection name to DBus object path that 1335 * implements ObjectManager. 1336 */ 1337 static void checkInventoryItemsStatus( 1338 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1339 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1340 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1341 objectMgrPaths) 1342 { 1343 BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus enter"; 1344 auto getInventoryItemsCb = 1345 [sensorsAsyncResp, 1346 objectMgrPaths](std::shared_ptr< 1347 boost::container::flat_map<std::string, std::string>> 1348 sensorToInvMap) { 1349 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 1350 auto getInventoryItemsConnectionsCb = 1351 [sensorsAsyncResp, sensorToInvMap, objectMgrPaths]( 1352 std::shared_ptr<boost::container::flat_set<std::string>> 1353 invConnections) { 1354 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 1355 1356 // Get status of inventory items and update sensors 1357 getInventoryItemsStatus(sensorsAsyncResp, sensorToInvMap, 1358 invConnections, objectMgrPaths); 1359 1360 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 1361 }; 1362 1363 // Get connections that provide status of inventory items 1364 getInventoryItemsConnections( 1365 sensorsAsyncResp, sensorToInvMap, 1366 std::move(getInventoryItemsConnectionsCb)); 1367 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 1368 }; 1369 1370 // Get inventory items that are associated with specified sensors 1371 getInventoryItems(sensorsAsyncResp, sensorNames, objectMgrPaths, 1372 std::move(getInventoryItemsCb)); 1373 BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus exit"; 1374 } 1375 1376 /** 1377 * @brief Gets the values of the specified sensors. 1378 * 1379 * Stores the results as JSON in the SensorsAsyncResp. 1380 * 1381 * Gets the sensor values asynchronously. Stores the results later when the 1382 * information has been obtained. 1383 * 1384 * The sensorNames set contains all sensors for the current chassis. 1385 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 1386 * requested type are included in the JSON output. 1387 * 1388 * To minimize the number of DBus calls, the DBus method 1389 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 1390 * values of all sensors provided by a connection (service). 1391 * 1392 * The connections set contains all the connections that provide sensor values. 1393 * 1394 * The objectMgrPaths map contains mappings from a connection name to the 1395 * corresponding DBus object path that implements ObjectManager. 1396 * 1397 * @param SensorsAsyncResp Pointer to object holding response data. 1398 * @param sensorNames All sensors within the current chassis. 1399 * @param connections Connections that provide sensor values. 1400 * @param objectMgrPaths Mappings from connection name to DBus object path that 1401 * implements ObjectManager. 1402 */ 1403 void getSensorData( 1404 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 1405 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 1406 const boost::container::flat_set<std::string>& connections, 1407 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1408 objectMgrPaths) 1409 { 1410 BMCWEB_LOG_DEBUG << "getSensorData enter"; 1411 // Get managed objects from all services exposing sensors 1412 for (const std::string& connection : connections) 1413 { 1414 // Response handler to process managed objects 1415 auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames, 1416 objectMgrPaths]( 1417 const boost::system::error_code ec, 1418 ManagedObjectsVectorType& resp) { 1419 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 1420 if (ec) 1421 { 1422 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 1423 messages::internalError(SensorsAsyncResp->res); 1424 return; 1425 } 1426 // Go through all objects and update response with sensor data 1427 for (const auto& objDictEntry : resp) 1428 { 1429 const std::string& objPath = 1430 static_cast<const std::string&>(objDictEntry.first); 1431 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 1432 << objPath; 1433 1434 std::vector<std::string> split; 1435 // Reserve space for 1436 // /xyz/openbmc_project/sensors/<name>/<subname> 1437 split.reserve(6); 1438 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 1439 if (split.size() < 6) 1440 { 1441 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 1442 << objPath; 1443 continue; 1444 } 1445 // These indexes aren't intuitive, as boost::split puts an empty 1446 // string at the beginning 1447 const std::string& sensorType = split[4]; 1448 const std::string& sensorName = split[5]; 1449 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 1450 << " sensorType " << sensorType; 1451 if (sensorNames->find(objPath) == sensorNames->end()) 1452 { 1453 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 1454 continue; 1455 } 1456 1457 const char* fieldName = nullptr; 1458 if (sensorType == "temperature") 1459 { 1460 fieldName = "Temperatures"; 1461 } 1462 else if (sensorType == "fan" || sensorType == "fan_tach" || 1463 sensorType == "fan_pwm") 1464 { 1465 fieldName = "Fans"; 1466 } 1467 else if (sensorType == "voltage") 1468 { 1469 fieldName = "Voltages"; 1470 } 1471 else if (sensorType == "current") 1472 { 1473 fieldName = "PowerSupplies"; 1474 } 1475 else if (sensorType == "power") 1476 { 1477 if (!sensorName.compare("total_power")) 1478 { 1479 fieldName = "PowerControl"; 1480 } 1481 else 1482 { 1483 fieldName = "PowerSupplies"; 1484 } 1485 } 1486 else 1487 { 1488 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 1489 << sensorType; 1490 continue; 1491 } 1492 1493 nlohmann::json& tempArray = 1494 SensorsAsyncResp->res.jsonValue[fieldName]; 1495 1496 if (fieldName == "PowerSupplies" && !tempArray.empty()) 1497 { 1498 // Power supplies put multiple "sensors" into a single power 1499 // supply entry, so only create the first one 1500 } 1501 else 1502 { 1503 tempArray.push_back( 1504 {{"@odata.id", "/redfish/v1/Chassis/" + 1505 SensorsAsyncResp->chassisId + "/" + 1506 SensorsAsyncResp->chassisSubNode + 1507 "#/" + fieldName + "/"}}); 1508 } 1509 nlohmann::json& sensorJson = tempArray.back(); 1510 1511 objectInterfacesToJson(sensorName, sensorType, 1512 objDictEntry.second, sensorJson); 1513 } 1514 if (SensorsAsyncResp.use_count() == 1) 1515 { 1516 sortJSONResponse(SensorsAsyncResp); 1517 checkInventoryItemsStatus(SensorsAsyncResp, sensorNames, 1518 objectMgrPaths); 1519 if (SensorsAsyncResp->chassisSubNode == "Thermal") 1520 { 1521 populateFanRedundancy(SensorsAsyncResp); 1522 } 1523 } 1524 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 1525 }; 1526 1527 // Find DBus object path that implements ObjectManager for the current 1528 // connection. If no mapping found, default to "/". 1529 auto iter = objectMgrPaths->find(connection); 1530 const std::string& objectMgrPath = 1531 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1532 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1533 << objectMgrPath; 1534 1535 crow::connections::systemBus->async_method_call( 1536 getManagedObjectsCb, connection, objectMgrPath, 1537 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1538 }; 1539 BMCWEB_LOG_DEBUG << "getSensorData exit"; 1540 } 1541 1542 /** 1543 * @brief Entry point for retrieving sensors data related to requested 1544 * chassis. 1545 * @param SensorsAsyncResp Pointer to object holding response data 1546 */ 1547 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 1548 { 1549 BMCWEB_LOG_DEBUG << "getChassisData enter"; 1550 auto getChassisCb = 1551 [SensorsAsyncResp]( 1552 std::shared_ptr<boost::container::flat_set<std::string>> 1553 sensorNames) { 1554 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 1555 auto getConnectionCb = [SensorsAsyncResp, sensorNames]( 1556 const boost::container::flat_set< 1557 std::string>& connections) { 1558 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 1559 auto getObjectManagerPathsCb = 1560 [SensorsAsyncResp, sensorNames, connections]( 1561 std::shared_ptr<boost::container::flat_map<std::string, 1562 std::string>> 1563 objectMgrPaths) { 1564 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 1565 // Get sensor data and store results in JSON 1566 // response 1567 getSensorData(SensorsAsyncResp, sensorNames, 1568 connections, objectMgrPaths); 1569 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 1570 }; 1571 1572 // Get mapping from connection names to the DBus object 1573 // paths that implement the ObjectManager interface 1574 getObjectManagerPaths(SensorsAsyncResp, 1575 std::move(getObjectManagerPathsCb)); 1576 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 1577 }; 1578 1579 // Get set of connections that provide sensor values 1580 getConnections(SensorsAsyncResp, sensorNames, 1581 std::move(getConnectionCb)); 1582 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 1583 }; 1584 SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array(); 1585 1586 // Get set of sensors in chassis 1587 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 1588 BMCWEB_LOG_DEBUG << "getChassisData exit"; 1589 }; 1590 1591 /** 1592 * @brief Find the requested sensorName in the list of all sensors supplied by 1593 * the chassis node 1594 * 1595 * @param sensorName The sensor name supplied in the PATCH request 1596 * @param sensorsList The list of sensors managed by the chassis node 1597 * @param sensorsModified The list of sensors that were found as a result of 1598 * repeated calls to this function 1599 */ 1600 bool findSensorNameUsingSensorPath( 1601 std::string_view sensorName, 1602 boost::container::flat_set<std::string>& sensorsList, 1603 boost::container::flat_set<std::string>& sensorsModified) 1604 { 1605 for (std::string_view chassisSensor : sensorsList) 1606 { 1607 std::size_t pos = chassisSensor.rfind("/"); 1608 if (pos >= (chassisSensor.size() - 1)) 1609 { 1610 continue; 1611 } 1612 std::string_view thisSensorName = chassisSensor.substr(pos + 1); 1613 if (thisSensorName == sensorName) 1614 { 1615 sensorsModified.emplace(chassisSensor); 1616 return true; 1617 } 1618 } 1619 return false; 1620 } 1621 1622 /** 1623 * @brief Entry point for overriding sensor values of given sensor 1624 * 1625 * @param res response object 1626 * @param req request object 1627 * @param params parameter passed for CRUD 1628 * @param typeList TypeList of sensors for the resource queried 1629 * @param chassisSubNode Chassis Node for which the query has to happen 1630 */ 1631 void setSensorOverride(crow::Response& res, const crow::Request& req, 1632 const std::vector<std::string>& params, 1633 const std::vector<const char*> typeList, 1634 const std::string& chassisSubNode) 1635 { 1636 1637 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 1638 // override) based on another d-bus announcement to be more generic. 1639 if (params.size() != 1) 1640 { 1641 messages::internalError(res); 1642 res.end(); 1643 return; 1644 } 1645 1646 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1647 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1648 std::optional<std::vector<nlohmann::json>> fanCollections; 1649 std::vector<nlohmann::json> voltageCollections; 1650 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1651 << "\n"; 1652 1653 if (chassisSubNode == "Thermal") 1654 { 1655 if (!json_util::readJson(req, res, "Temperatures", 1656 temperatureCollections, "Fans", 1657 fanCollections)) 1658 { 1659 return; 1660 } 1661 if (!temperatureCollections && !fanCollections) 1662 { 1663 messages::resourceNotFound(res, "Thermal", 1664 "Temperatures / Voltages"); 1665 res.end(); 1666 return; 1667 } 1668 if (temperatureCollections) 1669 { 1670 allCollections.emplace("Temperatures", 1671 *std::move(temperatureCollections)); 1672 } 1673 if (fanCollections) 1674 { 1675 allCollections.emplace("Fans", *std::move(fanCollections)); 1676 } 1677 } 1678 else if (chassisSubNode == "Power") 1679 { 1680 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1681 { 1682 return; 1683 } 1684 allCollections.emplace("Voltages", std::move(voltageCollections)); 1685 } 1686 else 1687 { 1688 res.result(boost::beast::http::status::not_found); 1689 res.end(); 1690 return; 1691 } 1692 1693 const char* propertyValueName; 1694 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1695 std::string memberId; 1696 double value; 1697 for (auto& collectionItems : allCollections) 1698 { 1699 if (collectionItems.first == "Temperatures") 1700 { 1701 propertyValueName = "ReadingCelsius"; 1702 } 1703 else if (collectionItems.first == "Fans") 1704 { 1705 propertyValueName = "Reading"; 1706 } 1707 else 1708 { 1709 propertyValueName = "ReadingVolts"; 1710 } 1711 for (auto& item : collectionItems.second) 1712 { 1713 if (!json_util::readJson(item, res, "MemberId", memberId, 1714 propertyValueName, value)) 1715 { 1716 return; 1717 } 1718 overrideMap.emplace(memberId, 1719 std::make_pair(value, collectionItems.first)); 1720 } 1721 } 1722 const std::string& chassisName = params[0]; 1723 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1724 res, chassisName, typeList, chassisSubNode); 1725 auto getChassisSensorListCb = [sensorAsyncResp, 1726 overrideMap](const std::shared_ptr< 1727 boost::container::flat_set< 1728 std::string>> 1729 sensorsList) { 1730 // Match sensor names in the PATCH request to those managed by the 1731 // chassis node 1732 const std::shared_ptr<boost::container::flat_set<std::string>> 1733 sensorNames = 1734 std::make_shared<boost::container::flat_set<std::string>>(); 1735 for (const auto& item : overrideMap) 1736 { 1737 const auto& sensor = item.first; 1738 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 1739 *sensorNames)) 1740 { 1741 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1742 messages::resourceNotFound(sensorAsyncResp->res, 1743 item.second.second, item.first); 1744 return; 1745 } 1746 } 1747 // Get the connection to which the memberId belongs 1748 auto getObjectsWithConnectionCb = 1749 [sensorAsyncResp, overrideMap]( 1750 const boost::container::flat_set<std::string>& connections, 1751 const std::set<std::pair<std::string, std::string>>& 1752 objectsWithConnection) { 1753 if (objectsWithConnection.size() != overrideMap.size()) 1754 { 1755 BMCWEB_LOG_INFO 1756 << "Unable to find all objects with proper connection " 1757 << objectsWithConnection.size() << " requested " 1758 << overrideMap.size() << "\n"; 1759 messages::resourceNotFound( 1760 sensorAsyncResp->res, 1761 sensorAsyncResp->chassisSubNode == "Thermal" 1762 ? "Temperatures" 1763 : "Voltages", 1764 "Count"); 1765 return; 1766 } 1767 for (const auto& item : objectsWithConnection) 1768 { 1769 1770 auto lastPos = item.first.rfind('/'); 1771 if (lastPos == std::string::npos) 1772 { 1773 messages::internalError(sensorAsyncResp->res); 1774 return; 1775 } 1776 std::string sensorName = item.first.substr(lastPos + 1); 1777 1778 const auto& iterator = overrideMap.find(sensorName); 1779 if (iterator == overrideMap.end()) 1780 { 1781 BMCWEB_LOG_INFO << "Unable to find sensor object" 1782 << item.first << "\n"; 1783 messages::internalError(sensorAsyncResp->res); 1784 return; 1785 } 1786 crow::connections::systemBus->async_method_call( 1787 [sensorAsyncResp](const boost::system::error_code ec) { 1788 if (ec) 1789 { 1790 BMCWEB_LOG_DEBUG 1791 << "setOverrideValueStatus DBUS error: " 1792 << ec; 1793 messages::internalError(sensorAsyncResp->res); 1794 return; 1795 } 1796 }, 1797 item.second, item.first, 1798 "org.freedesktop.DBus.Properties", "Set", 1799 "xyz.openbmc_project.Sensor.Value", "Value", 1800 sdbusplus::message::variant<double>( 1801 iterator->second.first)); 1802 } 1803 }; 1804 // Get object with connection for the given sensor name 1805 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1806 std::move(getObjectsWithConnectionCb)); 1807 }; 1808 // get full sensor list for the given chassisId and cross verify the sensor. 1809 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1810 } 1811 1812 } // namespace redfish 1813