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