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 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/"; 32 33 using GetSubTreeType = std::vector< 34 std::pair<std::string, 35 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 36 37 using SensorVariant = std::variant<int64_t, double>; 38 39 using ManagedObjectsVectorType = std::vector<std::pair< 40 sdbusplus::message::object_path, 41 boost::container::flat_map< 42 std::string, boost::container::flat_map<std::string, SensorVariant>>>>; 43 44 /** 45 * SensorsAsyncResp 46 * Gathers data needed for response processing after async calls are done 47 */ 48 class SensorsAsyncResp 49 { 50 public: 51 SensorsAsyncResp(crow::Response& response, const std::string& chassisId, 52 const std::initializer_list<const char*> types, 53 const std::string& subNode) : 54 res(response), 55 chassisId(chassisId), types(types), chassisSubNode(subNode) 56 { 57 } 58 59 ~SensorsAsyncResp() 60 { 61 if (res.result() == boost::beast::http::status::internal_server_error) 62 { 63 // Reset the json object to clear out any data that made it in 64 // before the error happened todo(ed) handle error condition with 65 // proper code 66 res.jsonValue = nlohmann::json::object(); 67 } 68 res.end(); 69 } 70 71 crow::Response& res; 72 std::string chassisId{}; 73 const std::vector<const char*> types; 74 std::string chassisSubNode{}; 75 }; 76 77 /** 78 * @brief Get objects with connection necessary for sensors 79 * @param SensorsAsyncResp Pointer to object holding response data 80 * @param sensorNames Sensors retrieved from chassis 81 * @param callback Callback for processing gathered connections 82 */ 83 template <typename Callback> 84 void getObjectsWithConnection( 85 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 86 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 87 Callback&& callback) 88 { 89 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 90 const std::string path = "/xyz/openbmc_project/sensors"; 91 const std::array<std::string, 1> interfaces = { 92 "xyz.openbmc_project.Sensor.Value"}; 93 94 // Response handler for parsing objects subtree 95 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp, 96 sensorNames](const boost::system::error_code ec, 97 const GetSubTreeType& subtree) { 98 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 99 if (ec) 100 { 101 messages::internalError(SensorsAsyncResp->res); 102 BMCWEB_LOG_ERROR 103 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 104 return; 105 } 106 107 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 108 109 // Make unique list of connections only for requested sensor types and 110 // found in the chassis 111 boost::container::flat_set<std::string> connections; 112 std::set<std::pair<std::string, std::string>> objectsWithConnection; 113 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 114 // producers 115 connections.reserve(8); 116 117 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size(); 118 for (const std::string& tsensor : *sensorNames) 119 { 120 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 121 } 122 123 for (const std::pair< 124 std::string, 125 std::vector<std::pair<std::string, std::vector<std::string>>>>& 126 object : subtree) 127 { 128 if (sensorNames->find(object.first) != sensorNames->end()) 129 { 130 for (const std::pair<std::string, std::vector<std::string>>& 131 objData : object.second) 132 { 133 BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first; 134 connections.insert(objData.first); 135 objectsWithConnection.insert( 136 std::make_pair(object.first, objData.first)); 137 } 138 } 139 } 140 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 141 callback(std::move(connections), std::move(objectsWithConnection)); 142 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 143 }; 144 // Make call to ObjectMapper to find all sensors objects 145 crow::connections::systemBus->async_method_call( 146 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 147 "/xyz/openbmc_project/object_mapper", 148 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 149 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 150 } 151 152 /** 153 * @brief Create connections necessary for sensors 154 * @param SensorsAsyncResp Pointer to object holding response data 155 * @param sensorNames Sensors retrieved from chassis 156 * @param callback Callback for processing gathered connections 157 */ 158 template <typename Callback> 159 void getConnections( 160 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 161 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 162 Callback&& callback) 163 { 164 auto objectsWithConnectionCb = 165 [callback](const boost::container::flat_set<std::string>& connections, 166 const std::set<std::pair<std::string, std::string>>& 167 objectsWithConnection) { 168 callback(std::move(connections)); 169 }; 170 getObjectsWithConnection(SensorsAsyncResp, sensorNames, 171 std::move(objectsWithConnectionCb)); 172 } 173 174 /** 175 * @brief Gets all DBus sensor names. 176 * 177 * Finds the sensor names asynchronously. Invokes callback when information has 178 * been obtained. 179 * 180 * The callback must have the following signature: 181 * @code 182 * callback(boost::container::flat_set<std::string>& sensorNames) 183 * @endcode 184 * 185 * @param SensorsAsyncResp Pointer to object holding response data. 186 * @param callback Callback to invoke when sensor names obtained. 187 */ 188 template <typename Callback> 189 void getAllSensors(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 190 Callback&& callback) 191 { 192 BMCWEB_LOG_DEBUG << "getAllSensors enter"; 193 const std::array<std::string, 1> interfaces = { 194 "xyz.openbmc_project.Sensor.Value"}; 195 196 // Response handler for GetSubTreePaths DBus method 197 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp]( 198 const boost::system::error_code ec, 199 const std::vector<std::string>& sensorList) { 200 BMCWEB_LOG_DEBUG << "getAllSensors respHandler enter"; 201 if (ec) 202 { 203 messages::internalError(SensorsAsyncResp->res); 204 BMCWEB_LOG_ERROR << "getAllSensors respHandler: DBus error " << ec; 205 return; 206 } 207 boost::container::flat_set<std::string> sensorNames; 208 for (const std::string& sensor : sensorList) 209 { 210 std::size_t lastPos = sensor.rfind("/"); 211 if (lastPos == std::string::npos) 212 { 213 BMCWEB_LOG_ERROR << "Failed to find '/' in " << sensor; 214 continue; 215 } 216 std::string sensorName = sensor.substr(lastPos + 1); 217 sensorNames.emplace(sensorName); 218 BMCWEB_LOG_DEBUG << "Added sensor name " << sensorName; 219 } 220 callback(sensorNames); 221 BMCWEB_LOG_DEBUG << "getAllSensors respHandler exit"; 222 }; 223 224 // Query mapper for all DBus sensor object paths 225 crow::connections::systemBus->async_method_call( 226 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 227 "/xyz/openbmc_project/object_mapper", 228 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 229 "/xyz/openbmc_project/sensors", int32_t(0), interfaces); 230 BMCWEB_LOG_DEBUG << "getAllSensors exit"; 231 } 232 233 /** 234 * @brief Shrinks the list of sensors for processing 235 * @param SensorsAysncResp The class holding the Redfish response 236 * @param allSensors A list of all the sensors associated to the 237 * chassis element (i.e. baseboard, front panel, etc...) 238 * @param activeSensors A list that is a reduction of the incoming 239 * allSensors list. Eliminate Thermal sensors when a Power request is 240 * made, and eliminate Power sensors when a Thermal request is made. 241 */ 242 void reduceSensorList( 243 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 244 const std::vector<std::string>* allSensors, 245 std::shared_ptr<boost::container::flat_set<std::string>> activeSensors) 246 { 247 if (SensorsAsyncResp == nullptr) 248 { 249 return; 250 } 251 if ((allSensors == nullptr) || (activeSensors == nullptr)) 252 { 253 messages::resourceNotFound( 254 SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode, 255 SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures" 256 : "Voltages"); 257 258 return; 259 } 260 if (allSensors->empty()) 261 { 262 // Nothing to do, the activeSensors object is also empty 263 return; 264 } 265 266 for (const char* type : SensorsAsyncResp->types) 267 { 268 for (const std::string& sensor : *allSensors) 269 { 270 if (boost::starts_with(sensor, type)) 271 { 272 activeSensors->emplace(sensor); 273 } 274 } 275 } 276 } 277 278 /** 279 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 280 * @param SensorsAsyncResp Pointer to object holding response data 281 * @param callback Callback for next step in gathered sensor processing 282 */ 283 template <typename Callback> 284 void getChassis(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 285 Callback&& callback) 286 { 287 BMCWEB_LOG_DEBUG << "getChassis enter"; 288 const std::array<const char*, 3> interfaces = { 289 "xyz.openbmc_project.Inventory.Item.Board", 290 "xyz.openbmc_project.Inventory.Item.Chassis", 291 "xyz.openbmc_project.Inventory.Item.PowerSupply"}; 292 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp]( 293 const boost::system::error_code ec, 294 const std::vector<std::string>& chassisPaths) { 295 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 296 if (ec) 297 { 298 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 299 messages::internalError(sensorsAsyncResp->res); 300 return; 301 } 302 303 const std::string* chassisPath = nullptr; 304 std::string chassisName; 305 for (const std::string& chassis : chassisPaths) 306 { 307 std::size_t lastPos = chassis.rfind("/"); 308 if (lastPos == std::string::npos) 309 { 310 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 311 continue; 312 } 313 chassisName = chassis.substr(lastPos + 1); 314 if (chassisName == sensorsAsyncResp->chassisId) 315 { 316 chassisPath = &chassis; 317 break; 318 } 319 } 320 if (chassisPath == nullptr) 321 { 322 messages::resourceNotFound(sensorsAsyncResp->res, "Chassis", 323 sensorsAsyncResp->chassisId); 324 return; 325 } 326 327 const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode; 328 if (chassisSubNode == "Power") 329 { 330 sensorsAsyncResp->res.jsonValue["@odata.type"] = 331 "#Power.v1_5_2.Power"; 332 } 333 else if (chassisSubNode == "Thermal") 334 { 335 sensorsAsyncResp->res.jsonValue["@odata.type"] = 336 "#Thermal.v1_4_0.Thermal"; 337 } 338 sensorsAsyncResp->res.jsonValue["@odata.id"] = 339 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + 340 chassisSubNode; 341 342 sensorsAsyncResp->res.jsonValue["@odata.context"] = 343 "/redfish/v1/$metadata#" + chassisSubNode + "." + chassisSubNode; 344 sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode; 345 sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode; 346 347 // Get the list of sensors for this Chassis element 348 std::string sensorPath = *chassisPath + "/sensors"; 349 crow::connections::systemBus->async_method_call( 350 [sensorsAsyncResp, callback{std::move(callback)}]( 351 const boost::system::error_code ec, 352 const std::variant<std::vector<std::string>>& 353 variantEndpoints) { 354 if (ec) 355 { 356 if (ec.value() != EBADR) 357 { 358 messages::internalError(sensorsAsyncResp->res); 359 return; 360 } 361 } 362 const std::vector<std::string>* nodeSensorList = 363 std::get_if<std::vector<std::string>>(&(variantEndpoints)); 364 if (nodeSensorList == nullptr) 365 { 366 messages::resourceNotFound( 367 sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode, 368 sensorsAsyncResp->chassisSubNode == "Thermal" 369 ? "Temperatures" 370 : "Voltages"); 371 return; 372 } 373 const std::shared_ptr<boost::container::flat_set<std::string>> 374 culledSensorList = std::make_shared< 375 boost::container::flat_set<std::string>>(); 376 reduceSensorList(sensorsAsyncResp, nodeSensorList, 377 culledSensorList); 378 callback(culledSensorList); 379 }, 380 "xyz.openbmc_project.ObjectMapper", sensorPath, 381 "org.freedesktop.DBus.Properties", "Get", 382 "xyz.openbmc_project.Association", "endpoints"); 383 }; 384 385 // Get the Chassis Collection 386 crow::connections::systemBus->async_method_call( 387 respHandler, "xyz.openbmc_project.ObjectMapper", 388 "/xyz/openbmc_project/object_mapper", 389 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 390 "/xyz/openbmc_project/inventory", int32_t(0), interfaces); 391 BMCWEB_LOG_DEBUG << "getChassis exit"; 392 } 393 394 /** 395 * @brief Finds all DBus object paths that implement ObjectManager. 396 * 397 * Creates a mapping from the associated connection name to the object path. 398 * 399 * Finds the object paths asynchronously. Invokes callback when information has 400 * been obtained. 401 * 402 * The callback must have the following signature: 403 * @code 404 * callback(const boost::container::flat_map<std::string, 405 * std::string>& objectMgrPaths) 406 * @endcode 407 * 408 * @param sensorsAsyncResp Pointer to object holding response data. 409 * @param callback Callback to invoke when object paths obtained. 410 */ 411 template <typename Callback> 412 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 413 Callback&& callback) 414 { 415 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 416 const std::array<std::string, 1> interfaces = { 417 "org.freedesktop.DBus.ObjectManager"}; 418 419 // Response handler for GetSubTree DBus method 420 auto respHandler = [callback{std::move(callback)}, 421 SensorsAsyncResp](const boost::system::error_code ec, 422 const GetSubTreeType& subtree) { 423 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 424 if (ec) 425 { 426 messages::internalError(SensorsAsyncResp->res); 427 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 428 << ec; 429 return; 430 } 431 432 // Loop over returned object paths 433 boost::container::flat_map<std::string, std::string> objectMgrPaths; 434 for (const std::pair< 435 std::string, 436 std::vector<std::pair<std::string, std::vector<std::string>>>>& 437 object : subtree) 438 { 439 // Loop over connections for current object path 440 const std::string& objectPath = object.first; 441 for (const std::pair<std::string, std::vector<std::string>>& 442 objData : object.second) 443 { 444 // Add mapping from connection to object path 445 const std::string& connection = objData.first; 446 objectMgrPaths[connection] = objectPath; 447 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 448 << objectPath; 449 } 450 } 451 callback(std::move(objectMgrPaths)); 452 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 453 }; 454 455 // Query mapper for all DBus object paths that implement ObjectManager 456 crow::connections::systemBus->async_method_call( 457 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 458 "/xyz/openbmc_project/object_mapper", 459 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0), 460 interfaces); 461 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 462 } 463 464 /** 465 * @brief Builds a json sensor representation of a sensor. 466 * @param sensorName The name of the sensor to be built 467 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 468 * build 469 * @param interfacesDict A dictionary of the interfaces and properties of said 470 * interfaces to be built from 471 * @param sensor_json The json object to fill 472 */ 473 void objectInterfacesToJson( 474 const std::string& sensorName, const std::string& sensorType, 475 const boost::container::flat_map< 476 std::string, boost::container::flat_map<std::string, SensorVariant>>& 477 interfacesDict, 478 nlohmann::json& sensor_json) 479 { 480 // We need a value interface before we can do anything with it 481 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 482 if (valueIt == interfacesDict.end()) 483 { 484 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 485 return; 486 } 487 488 // Assume values exist as is (10^0 == 1) if no scale exists 489 int64_t scaleMultiplier = 0; 490 491 auto scaleIt = valueIt->second.find("Scale"); 492 // If a scale exists, pull value as int64, and use the scaling. 493 if (scaleIt != valueIt->second.end()) 494 { 495 const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second); 496 if (int64Value != nullptr) 497 { 498 scaleMultiplier = *int64Value; 499 } 500 } 501 502 sensor_json["MemberId"] = sensorName; 503 sensor_json["Name"] = sensorName; 504 sensor_json["Status"]["State"] = "Enabled"; 505 sensor_json["Status"]["Health"] = "OK"; 506 507 // Parameter to set to override the type we get from dbus, and force it to 508 // int, regardless of what is available. This is used for schemas like fan, 509 // that require integers, not floats. 510 bool forceToInt = false; 511 512 const char* unit = "Reading"; 513 if (sensorType == "temperature") 514 { 515 unit = "ReadingCelsius"; 516 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 517 // TODO(ed) Documentation says that path should be type fan_tach, 518 // implementation seems to implement fan 519 } 520 else if (sensorType == "fan" || sensorType == "fan_tach") 521 { 522 unit = "Reading"; 523 sensor_json["ReadingUnits"] = "RPM"; 524 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 525 forceToInt = true; 526 } 527 else if (sensorType == "fan_pwm") 528 { 529 unit = "Reading"; 530 sensor_json["ReadingUnits"] = "Percent"; 531 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 532 forceToInt = true; 533 } 534 else if (sensorType == "voltage") 535 { 536 unit = "ReadingVolts"; 537 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 538 } 539 else if (sensorType == "power") 540 { 541 std::string sensorNameLower = 542 boost::algorithm::to_lower_copy(sensorName); 543 544 if (sensorNameLower.find("input") != std::string::npos) 545 { 546 unit = "PowerInputWatts"; 547 } 548 else 549 { 550 unit = "PowerOutputWatts"; 551 } 552 } 553 else 554 { 555 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 556 return; 557 } 558 // Map of dbus interface name, dbus property name and redfish property_name 559 std::vector<std::tuple<const char*, const char*, const char*>> properties; 560 properties.reserve(7); 561 562 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 563 564 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 565 if ((sensorType != "current") && (sensorType != "power")) 566 { 567 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 568 "WarningHigh", "UpperThresholdNonCritical"); 569 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 570 "WarningLow", "LowerThresholdNonCritical"); 571 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 572 "CriticalHigh", "UpperThresholdCritical"); 573 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 574 "CriticalLow", "LowerThresholdCritical"); 575 } 576 577 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 578 579 if (sensorType == "temperature") 580 { 581 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 582 "MinReadingRangeTemp"); 583 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 584 "MaxReadingRangeTemp"); 585 } 586 else if ((sensorType != "current") && (sensorType != "power")) 587 { 588 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 589 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 590 "MinReadingRange"); 591 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 592 "MaxReadingRange"); 593 } 594 595 for (const std::tuple<const char*, const char*, const char*>& p : 596 properties) 597 { 598 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 599 if (interfaceProperties != interfacesDict.end()) 600 { 601 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 602 if (valueIt != interfaceProperties->second.end()) 603 { 604 const SensorVariant& valueVariant = valueIt->second; 605 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 606 // Attempt to pull the int64 directly 607 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 608 609 const double* doubleValue = std::get_if<double>(&valueVariant); 610 double temp = 0.0; 611 if (int64Value != nullptr) 612 { 613 temp = *int64Value; 614 } 615 else if (doubleValue != nullptr) 616 { 617 temp = *doubleValue; 618 } 619 else 620 { 621 BMCWEB_LOG_ERROR 622 << "Got value interface that wasn't int or double"; 623 continue; 624 } 625 temp = temp * std::pow(10, scaleMultiplier); 626 if (forceToInt) 627 { 628 valueIt = static_cast<int64_t>(temp); 629 } 630 else 631 { 632 valueIt = temp; 633 } 634 } 635 } 636 } 637 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 638 } 639 640 static void 641 populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp) 642 { 643 crow::connections::systemBus->async_method_call( 644 [sensorsAsyncResp](const boost::system::error_code ec, 645 const GetSubTreeType& resp) { 646 if (ec) 647 { 648 return; // don't have to have this interface 649 } 650 for (const std::pair<std::string, 651 std::vector<std::pair< 652 std::string, std::vector<std::string>>>>& 653 pathPair : resp) 654 { 655 const std::string& path = pathPair.first; 656 const std::vector< 657 std::pair<std::string, std::vector<std::string>>>& objDict = 658 pathPair.second; 659 if (objDict.empty()) 660 { 661 continue; // this should be impossible 662 } 663 664 const std::string& owner = objDict.begin()->first; 665 crow::connections::systemBus->async_method_call( 666 [path, owner, 667 sensorsAsyncResp](const boost::system::error_code ec, 668 std::variant<std::vector<std::string>> 669 variantEndpoints) { 670 if (ec) 671 { 672 return; // if they don't have an association we 673 // can't tell what chassis is 674 } 675 // verify part of the right chassis 676 auto endpoints = std::get_if<std::vector<std::string>>( 677 &variantEndpoints); 678 679 if (endpoints == nullptr) 680 { 681 BMCWEB_LOG_ERROR << "Invalid association interface"; 682 messages::internalError(sensorsAsyncResp->res); 683 return; 684 } 685 686 auto found = std::find_if( 687 endpoints->begin(), endpoints->end(), 688 [sensorsAsyncResp](const std::string& entry) { 689 return entry.find( 690 sensorsAsyncResp->chassisId) != 691 std::string::npos; 692 }); 693 694 if (found == endpoints->end()) 695 { 696 return; 697 } 698 crow::connections::systemBus->async_method_call( 699 [path, sensorsAsyncResp]( 700 const boost::system::error_code ec, 701 const boost::container::flat_map< 702 std::string, 703 std::variant<uint8_t, 704 std::vector<std::string>, 705 std::string>>& ret) { 706 if (ec) 707 { 708 return; // don't have to have this 709 // interface 710 } 711 auto findFailures = ret.find("AllowedFailures"); 712 auto findCollection = ret.find("Collection"); 713 auto findStatus = ret.find("Status"); 714 715 if (findFailures == ret.end() || 716 findCollection == ret.end() || 717 findStatus == ret.end()) 718 { 719 BMCWEB_LOG_ERROR 720 << "Invalid redundancy interface"; 721 messages::internalError( 722 sensorsAsyncResp->res); 723 return; 724 } 725 726 auto allowedFailures = std::get_if<uint8_t>( 727 &(findFailures->second)); 728 auto collection = 729 std::get_if<std::vector<std::string>>( 730 &(findCollection->second)); 731 auto status = std::get_if<std::string>( 732 &(findStatus->second)); 733 734 if (allowedFailures == nullptr || 735 collection == nullptr || status == nullptr) 736 { 737 738 BMCWEB_LOG_ERROR 739 << "Invalid redundancy interface " 740 "types"; 741 messages::internalError( 742 sensorsAsyncResp->res); 743 return; 744 } 745 size_t lastSlash = path.rfind("/"); 746 if (lastSlash == std::string::npos) 747 { 748 // this should be impossible 749 messages::internalError( 750 sensorsAsyncResp->res); 751 return; 752 } 753 std::string name = path.substr(lastSlash + 1); 754 std::replace(name.begin(), name.end(), '_', 755 ' '); 756 757 std::string health; 758 759 if (boost::ends_with(*status, "Full")) 760 { 761 health = "OK"; 762 } 763 else if (boost::ends_with(*status, "Degraded")) 764 { 765 health = "Warning"; 766 } 767 else 768 { 769 health = "Critical"; 770 } 771 std::vector<nlohmann::json> redfishCollection; 772 const auto& fanRedfish = 773 sensorsAsyncResp->res.jsonValue["Fans"]; 774 for (const std::string& item : *collection) 775 { 776 lastSlash = item.rfind("/"); 777 // make a copy as collection is const 778 std::string itemName = 779 item.substr(lastSlash + 1); 780 /* 781 todo(ed): merge patch that fixes the names 782 std::replace(itemName.begin(), 783 itemName.end(), '_', ' ');*/ 784 auto schemaItem = std::find_if( 785 fanRedfish.begin(), fanRedfish.end(), 786 [itemName](const nlohmann::json& fan) { 787 return fan["MemberId"] == itemName; 788 }); 789 if (schemaItem != fanRedfish.end()) 790 { 791 redfishCollection.push_back( 792 {{"@odata.id", 793 (*schemaItem)["@odata.id"]}}); 794 } 795 else 796 { 797 BMCWEB_LOG_ERROR 798 << "failed to find fan in schema"; 799 messages::internalError( 800 sensorsAsyncResp->res); 801 return; 802 } 803 } 804 805 auto& resp = sensorsAsyncResp->res 806 .jsonValue["Redundancy"]; 807 resp.push_back( 808 {{"@odata.id", 809 "/refish/v1/Chassis/" + 810 sensorsAsyncResp->chassisId + "/" + 811 sensorsAsyncResp->chassisSubNode + 812 "#/Redundancy/" + 813 std::to_string(resp.size())}, 814 {"@odata.type", 815 "#Redundancy.v1_3_2.Redundancy"}, 816 {"MinNumNeeded", 817 collection->size() - *allowedFailures}, 818 {"MemberId", name}, 819 {"Mode", "N+m"}, 820 {"Name", name}, 821 {"RedundancySet", redfishCollection}, 822 {"Status", 823 {{"Health", health}, 824 {"State", "Enabled"}}}}); 825 }, 826 owner, path, "org.freedesktop.DBus.Properties", 827 "GetAll", 828 "xyz.openbmc_project.Control.FanRedundancy"); 829 }, 830 "xyz.openbmc_project.ObjectMapper", path + "/inventory", 831 "org.freedesktop.DBus.Properties", "Get", 832 "xyz.openbmc_project.Association", "endpoints"); 833 } 834 }, 835 "xyz.openbmc_project.ObjectMapper", 836 "/xyz/openbmc_project/object_mapper", 837 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 838 "/xyz/openbmc_project/control", 2, 839 std::array<const char*, 1>{ 840 "xyz.openbmc_project.Control.FanRedundancy"}); 841 } 842 843 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 844 { 845 nlohmann::json& response = SensorsAsyncResp->res.jsonValue; 846 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 847 if (SensorsAsyncResp->chassisSubNode == "Power") 848 { 849 sensorHeaders = {"Voltages", "PowerSupplies"}; 850 } 851 for (const std::string& sensorGroup : sensorHeaders) 852 { 853 nlohmann::json::iterator entry = response.find(sensorGroup); 854 if (entry != response.end()) 855 { 856 std::sort(entry->begin(), entry->end(), 857 [](nlohmann::json& c1, nlohmann::json& c2) { 858 return c1["Name"] < c2["Name"]; 859 }); 860 861 // add the index counts to the end of each entry 862 size_t count = 0; 863 for (nlohmann::json& sensorJson : *entry) 864 { 865 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 866 if (odata == sensorJson.end()) 867 { 868 continue; 869 } 870 std::string* value = odata->get_ptr<std::string*>(); 871 if (value != nullptr) 872 { 873 *value += std::to_string(count); 874 count++; 875 } 876 } 877 } 878 } 879 } 880 881 /** 882 * @brief Gets the values of the specified sensors. 883 * 884 * Stores the results as JSON in the SensorsAsyncResp. 885 * 886 * Gets the sensor values asynchronously. Stores the results later when the 887 * information has been obtained. 888 * 889 * The sensorNames set contains all sensors for the current chassis. 890 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 891 * requested type are included in the JSON output. 892 * 893 * To minimize the number of DBus calls, the DBus method 894 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 895 * values of all sensors provided by a connection (service). 896 * 897 * The connections set contains all the connections that provide sensor values. 898 * 899 * The objectMgrPaths map contains mappings from a connection name to the 900 * corresponding DBus object path that implements ObjectManager. 901 * 902 * @param SensorsAsyncResp Pointer to object holding response data. 903 * @param sensorNames All sensors within the current chassis. 904 * @param connections Connections that provide sensor values. 905 * @param objectMgrPaths Mappings from connection name to DBus object path that 906 * implements ObjectManager. 907 */ 908 void getSensorData( 909 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 910 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 911 const boost::container::flat_set<std::string>& connections, 912 const boost::container::flat_map<std::string, std::string>& objectMgrPaths) 913 { 914 BMCWEB_LOG_DEBUG << "getSensorData enter"; 915 // Get managed objects from all services exposing sensors 916 for (const std::string& connection : connections) 917 { 918 // Response handler to process managed objects 919 auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames]( 920 const boost::system::error_code ec, 921 ManagedObjectsVectorType& resp) { 922 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 923 if (ec) 924 { 925 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 926 messages::internalError(SensorsAsyncResp->res); 927 return; 928 } 929 // Go through all objects and update response with sensor data 930 for (const auto& objDictEntry : resp) 931 { 932 const std::string& objPath = 933 static_cast<const std::string&>(objDictEntry.first); 934 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 935 << objPath; 936 937 std::vector<std::string> split; 938 // Reserve space for 939 // /xyz/openbmc_project/sensors/<name>/<subname> 940 split.reserve(6); 941 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 942 if (split.size() < 6) 943 { 944 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 945 << objPath; 946 continue; 947 } 948 // These indexes aren't intuitive, as boost::split puts an empty 949 // string at the beginning 950 const std::string& sensorType = split[4]; 951 const std::string& sensorName = split[5]; 952 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 953 << " sensorType " << sensorType; 954 if (sensorNames->find(objPath) == sensorNames->end()) 955 { 956 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 957 continue; 958 } 959 960 const char* fieldName = nullptr; 961 if (sensorType == "temperature") 962 { 963 fieldName = "Temperatures"; 964 } 965 else if (sensorType == "fan" || sensorType == "fan_tach" || 966 sensorType == "fan_pwm") 967 { 968 fieldName = "Fans"; 969 } 970 else if (sensorType == "voltage") 971 { 972 fieldName = "Voltages"; 973 } 974 else if (sensorType == "current") 975 { 976 fieldName = "PowerSupplies"; 977 } 978 else if (sensorType == "power") 979 { 980 fieldName = "PowerSupplies"; 981 } 982 else 983 { 984 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 985 << sensorType; 986 continue; 987 } 988 989 nlohmann::json& tempArray = 990 SensorsAsyncResp->res.jsonValue[fieldName]; 991 992 if (fieldName == "PowerSupplies" && !tempArray.empty()) 993 { 994 // Power supplies put multiple "sensors" into a single power 995 // supply entry, so only create the first one 996 } 997 else 998 { 999 tempArray.push_back( 1000 {{"@odata.id", "/redfish/v1/Chassis/" + 1001 SensorsAsyncResp->chassisId + "/" + 1002 SensorsAsyncResp->chassisSubNode + 1003 "#/" + fieldName + "/"}}); 1004 } 1005 nlohmann::json& sensorJson = tempArray.back(); 1006 1007 objectInterfacesToJson(sensorName, sensorType, 1008 objDictEntry.second, sensorJson); 1009 } 1010 if (SensorsAsyncResp.use_count() == 1) 1011 { 1012 sortJSONResponse(SensorsAsyncResp); 1013 if (SensorsAsyncResp->chassisSubNode == "Thermal") 1014 { 1015 populateFanRedundancy(SensorsAsyncResp); 1016 } 1017 } 1018 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 1019 }; 1020 1021 // Find DBus object path that implements ObjectManager for the current 1022 // connection. If no mapping found, default to "/". 1023 auto iter = objectMgrPaths.find(connection); 1024 const std::string& objectMgrPath = 1025 (iter != objectMgrPaths.end()) ? iter->second : "/"; 1026 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1027 << objectMgrPath; 1028 1029 crow::connections::systemBus->async_method_call( 1030 getManagedObjectsCb, connection, objectMgrPath, 1031 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1032 }; 1033 BMCWEB_LOG_DEBUG << "getSensorData exit"; 1034 } 1035 1036 /** 1037 * @brief Entry point for retrieving sensors data related to requested 1038 * chassis. 1039 * @param SensorsAsyncResp Pointer to object holding response data 1040 */ 1041 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 1042 { 1043 BMCWEB_LOG_DEBUG << "getChassisData enter"; 1044 auto getChassisCb = 1045 [SensorsAsyncResp]( 1046 std::shared_ptr<boost::container::flat_set<std::string>> 1047 sensorNames) { 1048 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 1049 auto getConnectionCb = 1050 [SensorsAsyncResp, 1051 sensorNames](const boost::container::flat_set<std::string>& 1052 connections) { 1053 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 1054 auto getObjectManagerPathsCb = 1055 [SensorsAsyncResp, sensorNames, connections]( 1056 const boost::container::flat_map< 1057 std::string, std::string>& objectMgrPaths) { 1058 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 1059 // Get sensor data and store results in JSON 1060 // response 1061 getSensorData(SensorsAsyncResp, sensorNames, 1062 connections, objectMgrPaths); 1063 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 1064 }; 1065 1066 // Get mapping from connection names to the DBus object 1067 // paths that implement the ObjectManager interface 1068 getObjectManagerPaths(SensorsAsyncResp, 1069 std::move(getObjectManagerPathsCb)); 1070 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 1071 }; 1072 1073 // Get set of connections that provide sensor values 1074 getConnections(SensorsAsyncResp, sensorNames, 1075 std::move(getConnectionCb)); 1076 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 1077 }; 1078 1079 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS 1080 // Get all sensor names 1081 getAllSensors(SensorsAsyncResp, std::move(getChassisCb)); 1082 #else 1083 // Get sensor names in chassis 1084 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 1085 #endif 1086 BMCWEB_LOG_DEBUG << "getChassisData exit"; 1087 }; 1088 1089 /** 1090 * @brief Find the requested sensorName in the list of all sensors supplied by 1091 * the chassis node 1092 * 1093 * @param sensorName The sensor name supplied in the PATCH request 1094 * @param sensorsList The list of sensors managed by the chassis node 1095 * @param sensorsModified The list of sensors that were found as a result of 1096 * repeated calls to this function 1097 */ 1098 bool findSensorNameUsingSensorPath( 1099 const std::string& sensorName, 1100 boost::container::flat_set<std::string>& sensorsList, 1101 boost::container::flat_set<std::string>& sensorsModified) 1102 { 1103 for (const std::string& chassisSensor : sensorsList) 1104 { 1105 std::string thisSensorName; 1106 if (!dbus::utility::getNthStringFromPath(chassisSensor, 5, 1107 thisSensorName)) 1108 { 1109 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 1110 << chassisSensor; 1111 continue; 1112 } 1113 if (thisSensorName == sensorName) 1114 { 1115 sensorsModified.emplace(chassisSensor); 1116 return true; 1117 } 1118 } 1119 return false; 1120 } 1121 1122 /** 1123 * @brief Entry point for overriding sensor values of given sensor 1124 * 1125 * @param res response object 1126 * @param req request object 1127 * @param params parameter passed for CRUD 1128 * @param typeList TypeList of sensors for the resource queried 1129 * @param chassisSubNode Chassis Node for which the query has to happen 1130 */ 1131 void setSensorOverride(crow::Response& res, const crow::Request& req, 1132 const std::vector<std::string>& params, 1133 const std::initializer_list<const char*> typeList, 1134 const std::string& chassisSubNode) 1135 { 1136 1137 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 1138 // override) based on another d-bus announcement to be more generic. 1139 if (params.size() != 1) 1140 { 1141 messages::internalError(res); 1142 res.end(); 1143 return; 1144 } 1145 1146 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1147 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1148 std::optional<std::vector<nlohmann::json>> fanCollections; 1149 std::vector<nlohmann::json> voltageCollections; 1150 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1151 << "\n"; 1152 1153 if (chassisSubNode == "Thermal") 1154 { 1155 if (!json_util::readJson(req, res, "Temperatures", 1156 temperatureCollections, "Fans", 1157 fanCollections)) 1158 { 1159 return; 1160 } 1161 if (!temperatureCollections && !fanCollections) 1162 { 1163 messages::resourceNotFound(res, "Thermal", 1164 "Temperatures / Voltages"); 1165 res.end(); 1166 return; 1167 } 1168 if (temperatureCollections) 1169 { 1170 allCollections.emplace("Temperatures", 1171 *std::move(temperatureCollections)); 1172 } 1173 if (fanCollections) 1174 { 1175 allCollections.emplace("Fans", *std::move(fanCollections)); 1176 } 1177 } 1178 else if (chassisSubNode == "Power") 1179 { 1180 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1181 { 1182 return; 1183 } 1184 allCollections.emplace("Voltages", std::move(voltageCollections)); 1185 } 1186 else 1187 { 1188 res.result(boost::beast::http::status::not_found); 1189 res.end(); 1190 return; 1191 } 1192 1193 const char* propertyValueName; 1194 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1195 std::string memberId; 1196 double value; 1197 for (auto& collectionItems : allCollections) 1198 { 1199 if (collectionItems.first == "Temperatures") 1200 { 1201 propertyValueName = "ReadingCelsius"; 1202 } 1203 else if (collectionItems.first == "Fans") 1204 { 1205 propertyValueName = "Reading"; 1206 } 1207 else 1208 { 1209 propertyValueName = "ReadingVolts"; 1210 } 1211 for (auto& item : collectionItems.second) 1212 { 1213 if (!json_util::readJson(item, res, "MemberId", memberId, 1214 propertyValueName, value)) 1215 { 1216 return; 1217 } 1218 overrideMap.emplace(memberId, 1219 std::make_pair(value, collectionItems.first)); 1220 } 1221 } 1222 const std::string& chassisName = params[0]; 1223 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1224 res, chassisName, typeList, chassisSubNode); 1225 auto getChassisSensorListCb = [sensorAsyncResp, 1226 overrideMap](const std::shared_ptr< 1227 boost::container::flat_set< 1228 std::string>> 1229 sensorsList) { 1230 // Match sensor names in the PATCH request to those managed by the 1231 // chassis node 1232 const std::shared_ptr<boost::container::flat_set<std::string>> 1233 sensorNames = 1234 std::make_shared<boost::container::flat_set<std::string>>(); 1235 for (const auto& item : overrideMap) 1236 { 1237 const auto& sensor = item.first; 1238 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 1239 *sensorNames)) 1240 { 1241 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1242 messages::resourceNotFound(sensorAsyncResp->res, 1243 item.second.second, item.first); 1244 return; 1245 } 1246 } 1247 // Get the connection to which the memberId belongs 1248 auto getObjectsWithConnectionCb = 1249 [sensorAsyncResp, overrideMap]( 1250 const boost::container::flat_set<std::string>& connections, 1251 const std::set<std::pair<std::string, std::string>>& 1252 objectsWithConnection) { 1253 if (objectsWithConnection.size() != overrideMap.size()) 1254 { 1255 BMCWEB_LOG_INFO 1256 << "Unable to find all objects with proper connection " 1257 << objectsWithConnection.size() << " requested " 1258 << overrideMap.size() << "\n"; 1259 messages::resourceNotFound( 1260 sensorAsyncResp->res, 1261 sensorAsyncResp->chassisSubNode == "Thermal" 1262 ? "Temperatures" 1263 : "Voltages", 1264 "Count"); 1265 return; 1266 } 1267 for (const auto& item : objectsWithConnection) 1268 { 1269 1270 auto lastPos = item.first.rfind('/'); 1271 if (lastPos == std::string::npos) 1272 { 1273 messages::internalError(sensorAsyncResp->res); 1274 return; 1275 } 1276 std::string sensorName = item.first.substr(lastPos + 1); 1277 1278 const auto& iterator = overrideMap.find(sensorName); 1279 if (iterator == overrideMap.end()) 1280 { 1281 BMCWEB_LOG_INFO << "Unable to find sensor object" 1282 << item.first << "\n"; 1283 messages::internalError(sensorAsyncResp->res); 1284 return; 1285 } 1286 crow::connections::systemBus->async_method_call( 1287 [sensorAsyncResp](const boost::system::error_code ec) { 1288 if (ec) 1289 { 1290 BMCWEB_LOG_DEBUG 1291 << "setOverrideValueStatus DBUS error: " 1292 << ec; 1293 messages::internalError(sensorAsyncResp->res); 1294 return; 1295 } 1296 }, 1297 item.second, item.first, 1298 "org.freedesktop.DBus.Properties", "Set", 1299 "xyz.openbmc_project.Sensor.Value", "Value", 1300 sdbusplus::message::variant<double>( 1301 iterator->second.first)); 1302 } 1303 }; 1304 // Get object with connection for the given sensor name 1305 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1306 std::move(getObjectsWithConnectionCb)); 1307 }; 1308 // get full sensor list for the given chassisId and cross verify the sensor. 1309 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1310 } 1311 1312 } // namespace redfish 1313