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>; 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::initializer_list<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 sensors for this Chassis element 290 std::string sensorPath = *chassisPath + "/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(const 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 boost::container::flat_map<std::string, std::string> objectMgrPaths; 376 for (const std::pair< 377 std::string, 378 std::vector<std::pair<std::string, std::vector<std::string>>>>& 379 object : subtree) 380 { 381 // Loop over connections for current object path 382 const std::string& objectPath = object.first; 383 for (const std::pair<std::string, std::vector<std::string>>& 384 objData : object.second) 385 { 386 // Add mapping from connection to object path 387 const std::string& connection = objData.first; 388 objectMgrPaths[connection] = objectPath; 389 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 390 << objectPath; 391 } 392 } 393 callback(std::move(objectMgrPaths)); 394 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 395 }; 396 397 // Query mapper for all DBus object paths that implement ObjectManager 398 crow::connections::systemBus->async_method_call( 399 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 400 "/xyz/openbmc_project/object_mapper", 401 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0), 402 interfaces); 403 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 404 } 405 406 /** 407 * @brief Builds a json sensor representation of a sensor. 408 * @param sensorName The name of the sensor to be built 409 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 410 * build 411 * @param interfacesDict A dictionary of the interfaces and properties of said 412 * interfaces to be built from 413 * @param sensor_json The json object to fill 414 */ 415 void objectInterfacesToJson( 416 const std::string& sensorName, const std::string& sensorType, 417 const boost::container::flat_map< 418 std::string, boost::container::flat_map<std::string, SensorVariant>>& 419 interfacesDict, 420 nlohmann::json& sensor_json) 421 { 422 // We need a value interface before we can do anything with it 423 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 424 if (valueIt == interfacesDict.end()) 425 { 426 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 427 return; 428 } 429 430 // Assume values exist as is (10^0 == 1) if no scale exists 431 int64_t scaleMultiplier = 0; 432 433 auto scaleIt = valueIt->second.find("Scale"); 434 // If a scale exists, pull value as int64, and use the scaling. 435 if (scaleIt != valueIt->second.end()) 436 { 437 const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second); 438 if (int64Value != nullptr) 439 { 440 scaleMultiplier = *int64Value; 441 } 442 } 443 444 sensor_json["MemberId"] = sensorName; 445 sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " "); 446 447 sensor_json["Status"]["State"] = "Enabled"; 448 sensor_json["Status"]["Health"] = "OK"; 449 450 // Parameter to set to override the type we get from dbus, and force it to 451 // int, regardless of what is available. This is used for schemas like fan, 452 // that require integers, not floats. 453 bool forceToInt = false; 454 455 const char* unit = "Reading"; 456 if (sensorType == "temperature") 457 { 458 unit = "ReadingCelsius"; 459 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 460 // TODO(ed) Documentation says that path should be type fan_tach, 461 // implementation seems to implement fan 462 } 463 else if (sensorType == "fan" || sensorType == "fan_tach") 464 { 465 unit = "Reading"; 466 sensor_json["ReadingUnits"] = "RPM"; 467 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 468 forceToInt = true; 469 } 470 else if (sensorType == "fan_pwm") 471 { 472 unit = "Reading"; 473 sensor_json["ReadingUnits"] = "Percent"; 474 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 475 forceToInt = true; 476 } 477 else if (sensorType == "voltage") 478 { 479 unit = "ReadingVolts"; 480 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 481 } 482 else if (sensorType == "power") 483 { 484 std::string sensorNameLower = 485 boost::algorithm::to_lower_copy(sensorName); 486 487 if (sensorNameLower.find("input") != std::string::npos) 488 { 489 unit = "PowerInputWatts"; 490 } 491 else 492 { 493 unit = "PowerOutputWatts"; 494 } 495 } 496 else 497 { 498 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 499 return; 500 } 501 // Map of dbus interface name, dbus property name and redfish property_name 502 std::vector<std::tuple<const char*, const char*, const char*>> properties; 503 properties.reserve(7); 504 505 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 506 507 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 508 if ((sensorType != "current") && (sensorType != "power")) 509 { 510 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 511 "WarningHigh", "UpperThresholdNonCritical"); 512 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 513 "WarningLow", "LowerThresholdNonCritical"); 514 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 515 "CriticalHigh", "UpperThresholdCritical"); 516 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 517 "CriticalLow", "LowerThresholdCritical"); 518 } 519 520 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 521 522 if (sensorType == "temperature") 523 { 524 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 525 "MinReadingRangeTemp"); 526 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 527 "MaxReadingRangeTemp"); 528 } 529 else if ((sensorType != "current") && (sensorType != "power")) 530 { 531 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 532 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 533 "MinReadingRange"); 534 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 535 "MaxReadingRange"); 536 } 537 538 for (const std::tuple<const char*, const char*, const char*>& p : 539 properties) 540 { 541 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 542 if (interfaceProperties != interfacesDict.end()) 543 { 544 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 545 if (valueIt != interfaceProperties->second.end()) 546 { 547 const SensorVariant& valueVariant = valueIt->second; 548 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 549 // Attempt to pull the int64 directly 550 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 551 552 const double* doubleValue = std::get_if<double>(&valueVariant); 553 double temp = 0.0; 554 if (int64Value != nullptr) 555 { 556 temp = *int64Value; 557 } 558 else if (doubleValue != nullptr) 559 { 560 temp = *doubleValue; 561 } 562 else 563 { 564 BMCWEB_LOG_ERROR 565 << "Got value interface that wasn't int or double"; 566 continue; 567 } 568 temp = temp * std::pow(10, scaleMultiplier); 569 if (forceToInt) 570 { 571 valueIt = static_cast<int64_t>(temp); 572 } 573 else 574 { 575 valueIt = temp; 576 } 577 } 578 } 579 } 580 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 581 } 582 583 static void 584 populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp) 585 { 586 crow::connections::systemBus->async_method_call( 587 [sensorsAsyncResp](const boost::system::error_code ec, 588 const GetSubTreeType& resp) { 589 if (ec) 590 { 591 return; // don't have to have this interface 592 } 593 for (const std::pair<std::string, 594 std::vector<std::pair< 595 std::string, std::vector<std::string>>>>& 596 pathPair : resp) 597 { 598 const std::string& path = pathPair.first; 599 const std::vector< 600 std::pair<std::string, std::vector<std::string>>>& objDict = 601 pathPair.second; 602 if (objDict.empty()) 603 { 604 continue; // this should be impossible 605 } 606 607 const std::string& owner = objDict.begin()->first; 608 crow::connections::systemBus->async_method_call( 609 [path, owner, 610 sensorsAsyncResp](const boost::system::error_code ec, 611 std::variant<std::vector<std::string>> 612 variantEndpoints) { 613 if (ec) 614 { 615 return; // if they don't have an association we 616 // can't tell what chassis is 617 } 618 // verify part of the right chassis 619 auto endpoints = std::get_if<std::vector<std::string>>( 620 &variantEndpoints); 621 622 if (endpoints == nullptr) 623 { 624 BMCWEB_LOG_ERROR << "Invalid association interface"; 625 messages::internalError(sensorsAsyncResp->res); 626 return; 627 } 628 629 auto found = std::find_if( 630 endpoints->begin(), endpoints->end(), 631 [sensorsAsyncResp](const std::string& entry) { 632 return entry.find( 633 sensorsAsyncResp->chassisId) != 634 std::string::npos; 635 }); 636 637 if (found == endpoints->end()) 638 { 639 return; 640 } 641 crow::connections::systemBus->async_method_call( 642 [path, sensorsAsyncResp]( 643 const boost::system::error_code ec, 644 const boost::container::flat_map< 645 std::string, 646 std::variant<uint8_t, 647 std::vector<std::string>, 648 std::string>>& ret) { 649 if (ec) 650 { 651 return; // don't have to have this 652 // interface 653 } 654 auto findFailures = ret.find("AllowedFailures"); 655 auto findCollection = ret.find("Collection"); 656 auto findStatus = ret.find("Status"); 657 658 if (findFailures == ret.end() || 659 findCollection == ret.end() || 660 findStatus == ret.end()) 661 { 662 BMCWEB_LOG_ERROR 663 << "Invalid redundancy interface"; 664 messages::internalError( 665 sensorsAsyncResp->res); 666 return; 667 } 668 669 auto allowedFailures = std::get_if<uint8_t>( 670 &(findFailures->second)); 671 auto collection = 672 std::get_if<std::vector<std::string>>( 673 &(findCollection->second)); 674 auto status = std::get_if<std::string>( 675 &(findStatus->second)); 676 677 if (allowedFailures == nullptr || 678 collection == nullptr || status == nullptr) 679 { 680 681 BMCWEB_LOG_ERROR 682 << "Invalid redundancy interface " 683 "types"; 684 messages::internalError( 685 sensorsAsyncResp->res); 686 return; 687 } 688 size_t lastSlash = path.rfind("/"); 689 if (lastSlash == std::string::npos) 690 { 691 // this should be impossible 692 messages::internalError( 693 sensorsAsyncResp->res); 694 return; 695 } 696 std::string name = path.substr(lastSlash + 1); 697 std::replace(name.begin(), name.end(), '_', 698 ' '); 699 700 std::string health; 701 702 if (boost::ends_with(*status, "Full")) 703 { 704 health = "OK"; 705 } 706 else if (boost::ends_with(*status, "Degraded")) 707 { 708 health = "Warning"; 709 } 710 else 711 { 712 health = "Critical"; 713 } 714 std::vector<nlohmann::json> redfishCollection; 715 const auto& fanRedfish = 716 sensorsAsyncResp->res.jsonValue["Fans"]; 717 for (const std::string& item : *collection) 718 { 719 lastSlash = item.rfind("/"); 720 // make a copy as collection is const 721 std::string itemName = 722 item.substr(lastSlash + 1); 723 /* 724 todo(ed): merge patch that fixes the names 725 std::replace(itemName.begin(), 726 itemName.end(), '_', ' ');*/ 727 auto schemaItem = std::find_if( 728 fanRedfish.begin(), fanRedfish.end(), 729 [itemName](const nlohmann::json& fan) { 730 return fan["MemberId"] == itemName; 731 }); 732 if (schemaItem != fanRedfish.end()) 733 { 734 redfishCollection.push_back( 735 {{"@odata.id", 736 (*schemaItem)["@odata.id"]}}); 737 } 738 else 739 { 740 BMCWEB_LOG_ERROR 741 << "failed to find fan in schema"; 742 messages::internalError( 743 sensorsAsyncResp->res); 744 return; 745 } 746 } 747 748 auto& resp = sensorsAsyncResp->res 749 .jsonValue["Redundancy"]; 750 resp.push_back( 751 {{"@odata.id", 752 "/refish/v1/Chassis/" + 753 sensorsAsyncResp->chassisId + "/" + 754 sensorsAsyncResp->chassisSubNode + 755 "#/Redundancy/" + 756 std::to_string(resp.size())}, 757 {"@odata.type", 758 "#Redundancy.v1_3_2.Redundancy"}, 759 {"MinNumNeeded", 760 collection->size() - *allowedFailures}, 761 {"MemberId", name}, 762 {"Mode", "N+m"}, 763 {"Name", name}, 764 {"RedundancySet", redfishCollection}, 765 {"Status", 766 {{"Health", health}, 767 {"State", "Enabled"}}}}); 768 }, 769 owner, path, "org.freedesktop.DBus.Properties", 770 "GetAll", 771 "xyz.openbmc_project.Control.FanRedundancy"); 772 }, 773 "xyz.openbmc_project.ObjectMapper", path + "/inventory", 774 "org.freedesktop.DBus.Properties", "Get", 775 "xyz.openbmc_project.Association", "endpoints"); 776 } 777 }, 778 "xyz.openbmc_project.ObjectMapper", 779 "/xyz/openbmc_project/object_mapper", 780 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 781 "/xyz/openbmc_project/control", 2, 782 std::array<const char*, 1>{ 783 "xyz.openbmc_project.Control.FanRedundancy"}); 784 } 785 786 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 787 { 788 nlohmann::json& response = SensorsAsyncResp->res.jsonValue; 789 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 790 if (SensorsAsyncResp->chassisSubNode == "Power") 791 { 792 sensorHeaders = {"Voltages", "PowerSupplies"}; 793 } 794 for (const std::string& sensorGroup : sensorHeaders) 795 { 796 nlohmann::json::iterator entry = response.find(sensorGroup); 797 if (entry != response.end()) 798 { 799 std::sort(entry->begin(), entry->end(), 800 [](nlohmann::json& c1, nlohmann::json& c2) { 801 return c1["Name"] < c2["Name"]; 802 }); 803 804 // add the index counts to the end of each entry 805 size_t count = 0; 806 for (nlohmann::json& sensorJson : *entry) 807 { 808 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 809 if (odata == sensorJson.end()) 810 { 811 continue; 812 } 813 std::string* value = odata->get_ptr<std::string*>(); 814 if (value != nullptr) 815 { 816 *value += std::to_string(count); 817 count++; 818 } 819 } 820 } 821 } 822 } 823 824 /** 825 * @brief Gets the values of the specified sensors. 826 * 827 * Stores the results as JSON in the SensorsAsyncResp. 828 * 829 * Gets the sensor values asynchronously. Stores the results later when the 830 * information has been obtained. 831 * 832 * The sensorNames set contains all sensors for the current chassis. 833 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 834 * requested type are included in the JSON output. 835 * 836 * To minimize the number of DBus calls, the DBus method 837 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 838 * values of all sensors provided by a connection (service). 839 * 840 * The connections set contains all the connections that provide sensor values. 841 * 842 * The objectMgrPaths map contains mappings from a connection name to the 843 * corresponding DBus object path that implements ObjectManager. 844 * 845 * @param SensorsAsyncResp Pointer to object holding response data. 846 * @param sensorNames All sensors within the current chassis. 847 * @param connections Connections that provide sensor values. 848 * @param objectMgrPaths Mappings from connection name to DBus object path that 849 * implements ObjectManager. 850 */ 851 void getSensorData( 852 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 853 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 854 const boost::container::flat_set<std::string>& connections, 855 const boost::container::flat_map<std::string, std::string>& objectMgrPaths) 856 { 857 BMCWEB_LOG_DEBUG << "getSensorData enter"; 858 // Get managed objects from all services exposing sensors 859 for (const std::string& connection : connections) 860 { 861 // Response handler to process managed objects 862 auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames]( 863 const boost::system::error_code ec, 864 ManagedObjectsVectorType& resp) { 865 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 866 if (ec) 867 { 868 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 869 messages::internalError(SensorsAsyncResp->res); 870 return; 871 } 872 // Go through all objects and update response with sensor data 873 for (const auto& objDictEntry : resp) 874 { 875 const std::string& objPath = 876 static_cast<const std::string&>(objDictEntry.first); 877 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 878 << objPath; 879 880 std::vector<std::string> split; 881 // Reserve space for 882 // /xyz/openbmc_project/sensors/<name>/<subname> 883 split.reserve(6); 884 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 885 if (split.size() < 6) 886 { 887 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 888 << objPath; 889 continue; 890 } 891 // These indexes aren't intuitive, as boost::split puts an empty 892 // string at the beginning 893 const std::string& sensorType = split[4]; 894 const std::string& sensorName = split[5]; 895 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 896 << " sensorType " << sensorType; 897 if (sensorNames->find(objPath) == sensorNames->end()) 898 { 899 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 900 continue; 901 } 902 903 const char* fieldName = nullptr; 904 if (sensorType == "temperature") 905 { 906 fieldName = "Temperatures"; 907 } 908 else if (sensorType == "fan" || sensorType == "fan_tach" || 909 sensorType == "fan_pwm") 910 { 911 fieldName = "Fans"; 912 } 913 else if (sensorType == "voltage") 914 { 915 fieldName = "Voltages"; 916 } 917 else if (sensorType == "current") 918 { 919 fieldName = "PowerSupplies"; 920 } 921 else if (sensorType == "power") 922 { 923 fieldName = "PowerSupplies"; 924 } 925 else 926 { 927 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 928 << sensorType; 929 continue; 930 } 931 932 nlohmann::json& tempArray = 933 SensorsAsyncResp->res.jsonValue[fieldName]; 934 935 if (fieldName == "PowerSupplies" && !tempArray.empty()) 936 { 937 // Power supplies put multiple "sensors" into a single power 938 // supply entry, so only create the first one 939 } 940 else 941 { 942 tempArray.push_back( 943 {{"@odata.id", "/redfish/v1/Chassis/" + 944 SensorsAsyncResp->chassisId + "/" + 945 SensorsAsyncResp->chassisSubNode + 946 "#/" + fieldName + "/"}}); 947 } 948 nlohmann::json& sensorJson = tempArray.back(); 949 950 objectInterfacesToJson(sensorName, sensorType, 951 objDictEntry.second, sensorJson); 952 } 953 if (SensorsAsyncResp.use_count() == 1) 954 { 955 sortJSONResponse(SensorsAsyncResp); 956 if (SensorsAsyncResp->chassisSubNode == "Thermal") 957 { 958 populateFanRedundancy(SensorsAsyncResp); 959 } 960 } 961 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 962 }; 963 964 // Find DBus object path that implements ObjectManager for the current 965 // connection. If no mapping found, default to "/". 966 auto iter = objectMgrPaths.find(connection); 967 const std::string& objectMgrPath = 968 (iter != objectMgrPaths.end()) ? iter->second : "/"; 969 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 970 << objectMgrPath; 971 972 crow::connections::systemBus->async_method_call( 973 getManagedObjectsCb, connection, objectMgrPath, 974 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 975 }; 976 BMCWEB_LOG_DEBUG << "getSensorData exit"; 977 } 978 979 /** 980 * @brief Entry point for retrieving sensors data related to requested 981 * chassis. 982 * @param SensorsAsyncResp Pointer to object holding response data 983 */ 984 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 985 { 986 BMCWEB_LOG_DEBUG << "getChassisData enter"; 987 auto getChassisCb = 988 [SensorsAsyncResp]( 989 std::shared_ptr<boost::container::flat_set<std::string>> 990 sensorNames) { 991 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 992 auto getConnectionCb = 993 [SensorsAsyncResp, 994 sensorNames](const boost::container::flat_set<std::string>& 995 connections) { 996 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 997 auto getObjectManagerPathsCb = 998 [SensorsAsyncResp, sensorNames, connections]( 999 const boost::container::flat_map< 1000 std::string, std::string>& objectMgrPaths) { 1001 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 1002 // Get sensor data and store results in JSON 1003 // response 1004 getSensorData(SensorsAsyncResp, sensorNames, 1005 connections, objectMgrPaths); 1006 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 1007 }; 1008 1009 // Get mapping from connection names to the DBus object 1010 // paths that implement the ObjectManager interface 1011 getObjectManagerPaths(SensorsAsyncResp, 1012 std::move(getObjectManagerPathsCb)); 1013 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 1014 }; 1015 1016 // Get set of connections that provide sensor values 1017 getConnections(SensorsAsyncResp, sensorNames, 1018 std::move(getConnectionCb)); 1019 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 1020 }; 1021 SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array(); 1022 1023 // Get set of sensors in chassis 1024 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 1025 BMCWEB_LOG_DEBUG << "getChassisData exit"; 1026 }; 1027 1028 /** 1029 * @brief Find the requested sensorName in the list of all sensors supplied by 1030 * the chassis node 1031 * 1032 * @param sensorName The sensor name supplied in the PATCH request 1033 * @param sensorsList The list of sensors managed by the chassis node 1034 * @param sensorsModified The list of sensors that were found as a result of 1035 * repeated calls to this function 1036 */ 1037 bool findSensorNameUsingSensorPath( 1038 const std::string& sensorName, 1039 boost::container::flat_set<std::string>& sensorsList, 1040 boost::container::flat_set<std::string>& sensorsModified) 1041 { 1042 for (const std::string& chassisSensor : sensorsList) 1043 { 1044 std::string thisSensorName; 1045 if (!dbus::utility::getNthStringFromPath(chassisSensor, 5, 1046 thisSensorName)) 1047 { 1048 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 1049 << chassisSensor; 1050 continue; 1051 } 1052 if (thisSensorName == sensorName) 1053 { 1054 sensorsModified.emplace(chassisSensor); 1055 return true; 1056 } 1057 } 1058 return false; 1059 } 1060 1061 /** 1062 * @brief Entry point for overriding sensor values of given sensor 1063 * 1064 * @param res response object 1065 * @param req request object 1066 * @param params parameter passed for CRUD 1067 * @param typeList TypeList of sensors for the resource queried 1068 * @param chassisSubNode Chassis Node for which the query has to happen 1069 */ 1070 void setSensorOverride(crow::Response& res, const crow::Request& req, 1071 const std::vector<std::string>& params, 1072 const std::initializer_list<const char*> typeList, 1073 const std::string& chassisSubNode) 1074 { 1075 1076 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 1077 // override) based on another d-bus announcement to be more generic. 1078 if (params.size() != 1) 1079 { 1080 messages::internalError(res); 1081 res.end(); 1082 return; 1083 } 1084 1085 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1086 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1087 std::optional<std::vector<nlohmann::json>> fanCollections; 1088 std::vector<nlohmann::json> voltageCollections; 1089 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1090 << "\n"; 1091 1092 if (chassisSubNode == "Thermal") 1093 { 1094 if (!json_util::readJson(req, res, "Temperatures", 1095 temperatureCollections, "Fans", 1096 fanCollections)) 1097 { 1098 return; 1099 } 1100 if (!temperatureCollections && !fanCollections) 1101 { 1102 messages::resourceNotFound(res, "Thermal", 1103 "Temperatures / Voltages"); 1104 res.end(); 1105 return; 1106 } 1107 if (temperatureCollections) 1108 { 1109 allCollections.emplace("Temperatures", 1110 *std::move(temperatureCollections)); 1111 } 1112 if (fanCollections) 1113 { 1114 allCollections.emplace("Fans", *std::move(fanCollections)); 1115 } 1116 } 1117 else if (chassisSubNode == "Power") 1118 { 1119 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1120 { 1121 return; 1122 } 1123 allCollections.emplace("Voltages", std::move(voltageCollections)); 1124 } 1125 else 1126 { 1127 res.result(boost::beast::http::status::not_found); 1128 res.end(); 1129 return; 1130 } 1131 1132 const char* propertyValueName; 1133 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1134 std::string memberId; 1135 double value; 1136 for (auto& collectionItems : allCollections) 1137 { 1138 if (collectionItems.first == "Temperatures") 1139 { 1140 propertyValueName = "ReadingCelsius"; 1141 } 1142 else if (collectionItems.first == "Fans") 1143 { 1144 propertyValueName = "Reading"; 1145 } 1146 else 1147 { 1148 propertyValueName = "ReadingVolts"; 1149 } 1150 for (auto& item : collectionItems.second) 1151 { 1152 if (!json_util::readJson(item, res, "MemberId", memberId, 1153 propertyValueName, value)) 1154 { 1155 return; 1156 } 1157 overrideMap.emplace(memberId, 1158 std::make_pair(value, collectionItems.first)); 1159 } 1160 } 1161 const std::string& chassisName = params[0]; 1162 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1163 res, chassisName, typeList, chassisSubNode); 1164 auto getChassisSensorListCb = [sensorAsyncResp, 1165 overrideMap](const std::shared_ptr< 1166 boost::container::flat_set< 1167 std::string>> 1168 sensorsList) { 1169 // Match sensor names in the PATCH request to those managed by the 1170 // chassis node 1171 const std::shared_ptr<boost::container::flat_set<std::string>> 1172 sensorNames = 1173 std::make_shared<boost::container::flat_set<std::string>>(); 1174 for (const auto& item : overrideMap) 1175 { 1176 const auto& sensor = item.first; 1177 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 1178 *sensorNames)) 1179 { 1180 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1181 messages::resourceNotFound(sensorAsyncResp->res, 1182 item.second.second, item.first); 1183 return; 1184 } 1185 } 1186 // Get the connection to which the memberId belongs 1187 auto getObjectsWithConnectionCb = 1188 [sensorAsyncResp, overrideMap]( 1189 const boost::container::flat_set<std::string>& connections, 1190 const std::set<std::pair<std::string, std::string>>& 1191 objectsWithConnection) { 1192 if (objectsWithConnection.size() != overrideMap.size()) 1193 { 1194 BMCWEB_LOG_INFO 1195 << "Unable to find all objects with proper connection " 1196 << objectsWithConnection.size() << " requested " 1197 << overrideMap.size() << "\n"; 1198 messages::resourceNotFound( 1199 sensorAsyncResp->res, 1200 sensorAsyncResp->chassisSubNode == "Thermal" 1201 ? "Temperatures" 1202 : "Voltages", 1203 "Count"); 1204 return; 1205 } 1206 for (const auto& item : objectsWithConnection) 1207 { 1208 1209 auto lastPos = item.first.rfind('/'); 1210 if (lastPos == std::string::npos) 1211 { 1212 messages::internalError(sensorAsyncResp->res); 1213 return; 1214 } 1215 std::string sensorName = item.first.substr(lastPos + 1); 1216 1217 const auto& iterator = overrideMap.find(sensorName); 1218 if (iterator == overrideMap.end()) 1219 { 1220 BMCWEB_LOG_INFO << "Unable to find sensor object" 1221 << item.first << "\n"; 1222 messages::internalError(sensorAsyncResp->res); 1223 return; 1224 } 1225 crow::connections::systemBus->async_method_call( 1226 [sensorAsyncResp](const boost::system::error_code ec) { 1227 if (ec) 1228 { 1229 BMCWEB_LOG_DEBUG 1230 << "setOverrideValueStatus DBUS error: " 1231 << ec; 1232 messages::internalError(sensorAsyncResp->res); 1233 return; 1234 } 1235 }, 1236 item.second, item.first, 1237 "org.freedesktop.DBus.Properties", "Set", 1238 "xyz.openbmc_project.Sensor.Value", "Value", 1239 sdbusplus::message::variant<double>( 1240 iterator->second.first)); 1241 } 1242 }; 1243 // Get object with connection for the given sensor name 1244 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1245 std::move(getObjectsWithConnectionCb)); 1246 }; 1247 // get full sensor list for the given chassisId and cross verify the sensor. 1248 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1249 } 1250 1251 } // namespace redfish 1252