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