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