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