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 auto& [path, objDict] : resp) 592 { 593 if (objDict.empty()) 594 { 595 continue; // this should be impossible 596 } 597 598 const std::string& owner = objDict.begin()->first; 599 crow::connections::systemBus->async_method_call( 600 [path, owner, 601 sensorsAsyncResp](const boost::system::error_code ec, 602 std::variant<std::vector<std::string>> 603 variantEndpoints) { 604 if (ec) 605 { 606 return; // if they don't have an association we 607 // can't tell what chassis is 608 } 609 // verify part of the right chassis 610 auto endpoints = std::get_if<std::vector<std::string>>( 611 &variantEndpoints); 612 613 if (endpoints == nullptr) 614 { 615 BMCWEB_LOG_ERROR << "Invalid association interface"; 616 messages::internalError(sensorsAsyncResp->res); 617 return; 618 } 619 620 auto found = std::find_if( 621 endpoints->begin(), endpoints->end(), 622 [sensorsAsyncResp](const std::string& entry) { 623 return entry.find( 624 sensorsAsyncResp->chassisId) != 625 std::string::npos; 626 }); 627 628 if (found == endpoints->end()) 629 { 630 return; 631 } 632 crow::connections::systemBus->async_method_call( 633 [path, sensorsAsyncResp]( 634 const boost::system::error_code ec, 635 const boost::container::flat_map< 636 std::string, 637 std::variant<uint8_t, 638 std::vector<std::string>, 639 std::string>>& ret) { 640 if (ec) 641 { 642 return; // don't have to have this 643 // interface 644 } 645 auto findFailures = ret.find("AllowedFailures"); 646 auto findCollection = ret.find("Collection"); 647 auto findStatus = ret.find("Status"); 648 649 if (findFailures == ret.end() || 650 findCollection == ret.end() || 651 findStatus == ret.end()) 652 { 653 BMCWEB_LOG_ERROR 654 << "Invalid redundancy interface"; 655 messages::internalError( 656 sensorsAsyncResp->res); 657 return; 658 } 659 660 auto allowedFailures = std::get_if<uint8_t>( 661 &(findFailures->second)); 662 auto collection = 663 std::get_if<std::vector<std::string>>( 664 &(findCollection->second)); 665 auto status = std::get_if<std::string>( 666 &(findStatus->second)); 667 668 if (allowedFailures == nullptr || 669 collection == nullptr || status == nullptr) 670 { 671 672 BMCWEB_LOG_ERROR 673 << "Invalid redundancy interface " 674 "types"; 675 messages::internalError( 676 sensorsAsyncResp->res); 677 return; 678 } 679 size_t lastSlash = path.rfind("/"); 680 if (lastSlash == std::string::npos) 681 { 682 // this should be impossible 683 messages::internalError( 684 sensorsAsyncResp->res); 685 return; 686 } 687 std::string name = path.substr(lastSlash + 1); 688 std::replace(name.begin(), name.end(), '_', 689 ' '); 690 691 std::string health; 692 693 if (boost::ends_with(*status, "Full")) 694 { 695 health = "OK"; 696 } 697 else if (boost::ends_with(*status, "Degraded")) 698 { 699 health = "Warning"; 700 } 701 else 702 { 703 health = "Critical"; 704 } 705 std::vector<nlohmann::json> redfishCollection; 706 const auto& fanRedfish = 707 sensorsAsyncResp->res.jsonValue["Fans"]; 708 for (const std::string& item : *collection) 709 { 710 lastSlash = item.rfind("/"); 711 // make a copy as collection is const 712 std::string itemName = 713 item.substr(lastSlash + 1); 714 /* 715 todo(ed): merge patch that fixes the names 716 std::replace(itemName.begin(), 717 itemName.end(), '_', ' ');*/ 718 auto schemaItem = std::find_if( 719 fanRedfish.begin(), fanRedfish.end(), 720 [itemName](const nlohmann::json& fan) { 721 return fan["MemberId"] == itemName; 722 }); 723 if (schemaItem != fanRedfish.end()) 724 { 725 redfishCollection.push_back( 726 {{"@odata.id", 727 (*schemaItem)["@odata.id"]}}); 728 } 729 else 730 { 731 BMCWEB_LOG_ERROR 732 << "failed to find fan in schema"; 733 messages::internalError( 734 sensorsAsyncResp->res); 735 return; 736 } 737 } 738 739 auto& resp = sensorsAsyncResp->res 740 .jsonValue["Redundancy"]; 741 resp.push_back( 742 {{"@odata.id", 743 "/refish/v1/Chassis/" + 744 sensorsAsyncResp->chassisId + "/" + 745 sensorsAsyncResp->chassisSubNode + 746 "#/Redundancy/" + 747 std::to_string(resp.size())}, 748 {"@odata.type", 749 "#Redundancy.v1_3_2.Redundancy"}, 750 {"MinNumNeeded", 751 collection->size() - *allowedFailures}, 752 {"MemberId", name}, 753 {"Mode", "N+m"}, 754 {"Name", name}, 755 {"RedundancySet", redfishCollection}, 756 {"Status", 757 {{"Health", health}, 758 {"State", "Enabled"}}}}); 759 }, 760 owner, path, "org.freedesktop.DBus.Properties", 761 "GetAll", 762 "xyz.openbmc_project.Control.FanRedundancy"); 763 }, 764 "xyz.openbmc_project.ObjectMapper", path + "/inventory", 765 "org.freedesktop.DBus.Properties", "Get", 766 "xyz.openbmc_project.Association", "endpoints"); 767 } 768 }, 769 "xyz.openbmc_project.ObjectMapper", 770 "/xyz/openbmc_project/object_mapper", 771 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 772 "/xyz/openbmc_project/control", 2, 773 std::array<const char*, 1>{ 774 "xyz.openbmc_project.Control.FanRedundancy"}); 775 } 776 777 /** 778 * @brief Gets the values of the specified sensors. 779 * 780 * Stores the results as JSON in the SensorsAsyncResp. 781 * 782 * Gets the sensor values asynchronously. Stores the results later when the 783 * information has been obtained. 784 * 785 * The sensorNames set contains all sensors for the current chassis. 786 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 787 * requested type are included in the JSON output. 788 * 789 * To minimize the number of DBus calls, the DBus method 790 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 791 * values of all sensors provided by a connection (service). 792 * 793 * The connections set contains all the connections that provide sensor values. 794 * 795 * The objectMgrPaths map contains mappings from a connection name to the 796 * corresponding DBus object path that implements ObjectManager. 797 * 798 * @param SensorsAsyncResp Pointer to object holding response data. 799 * @param sensorNames All sensors within the current chassis. 800 * @param connections Connections that provide sensor values. 801 * @param objectMgrPaths Mappings from connection name to DBus object path that 802 * implements ObjectManager. 803 */ 804 void getSensorData( 805 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 806 const boost::container::flat_set<std::string>& sensorNames, 807 const boost::container::flat_set<std::string>& connections, 808 const boost::container::flat_map<std::string, std::string>& objectMgrPaths) 809 { 810 BMCWEB_LOG_DEBUG << "getSensorData enter"; 811 // Get managed objects from all services exposing sensors 812 for (const std::string& connection : connections) 813 { 814 // Response handler to process managed objects 815 auto getManagedObjectsCb = [&, SensorsAsyncResp, sensorNames]( 816 const boost::system::error_code ec, 817 ManagedObjectsVectorType& resp) { 818 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 819 if (ec) 820 { 821 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 822 messages::internalError(SensorsAsyncResp->res); 823 return; 824 } 825 // Go through all objects and update response with sensor data 826 for (const auto& objDictEntry : resp) 827 { 828 const std::string& objPath = 829 static_cast<const std::string&>(objDictEntry.first); 830 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 831 << objPath; 832 833 // Skip sensor if it is not one of the requested types 834 if (!isRequestedSensorType(objPath, SensorsAsyncResp)) 835 { 836 continue; 837 } 838 839 std::vector<std::string> split; 840 // Reserve space for 841 // /xyz/openbmc_project/sensors/<name>/<subname> 842 split.reserve(6); 843 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 844 if (split.size() < 6) 845 { 846 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 847 << objPath; 848 continue; 849 } 850 // These indexes aren't intuitive, as boost::split puts an empty 851 // string at the beginning 852 const std::string& sensorType = split[4]; 853 const std::string& sensorName = split[5]; 854 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 855 << " sensorType " << sensorType; 856 if (sensorNames.find(sensorName) == sensorNames.end()) 857 { 858 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 859 continue; 860 } 861 862 const char* fieldName = nullptr; 863 if (sensorType == "temperature") 864 { 865 fieldName = "Temperatures"; 866 } 867 else if (sensorType == "fan" || sensorType == "fan_tach" || 868 sensorType == "fan_pwm") 869 { 870 fieldName = "Fans"; 871 } 872 else if (sensorType == "voltage") 873 { 874 fieldName = "Voltages"; 875 } 876 else if (sensorType == "current") 877 { 878 fieldName = "PowerSupplies"; 879 } 880 else if (sensorType == "power") 881 { 882 fieldName = "PowerSupplies"; 883 } 884 else 885 { 886 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 887 << sensorType; 888 continue; 889 } 890 891 nlohmann::json& tempArray = 892 SensorsAsyncResp->res.jsonValue[fieldName]; 893 894 tempArray.push_back( 895 {{"@odata.id", 896 "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId + 897 "/" + SensorsAsyncResp->chassisSubNode + "#/" + 898 fieldName + "/" + std::to_string(tempArray.size())}}); 899 nlohmann::json& sensorJson = tempArray.back(); 900 901 objectInterfacesToJson(sensorName, sensorType, 902 objDictEntry.second, sensorJson); 903 } 904 if (SensorsAsyncResp.use_count() == 1 && 905 SensorsAsyncResp->chassisSubNode == "Thermal") 906 { 907 populateFanRedundancy(SensorsAsyncResp); 908 } 909 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 910 }; 911 912 // Find DBus object path that implements ObjectManager for the current 913 // connection. If no mapping found, default to "/". 914 auto iter = objectMgrPaths.find(connection); 915 const std::string& objectMgrPath = 916 (iter != objectMgrPaths.end()) ? iter->second : "/"; 917 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 918 << objectMgrPath; 919 920 crow::connections::systemBus->async_method_call( 921 getManagedObjectsCb, connection, objectMgrPath, 922 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 923 }; 924 BMCWEB_LOG_DEBUG << "getSensorData exit"; 925 } 926 927 /** 928 * @brief Entry point for retrieving sensors data related to requested 929 * chassis. 930 * @param SensorsAsyncResp Pointer to object holding response data 931 */ 932 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 933 { 934 BMCWEB_LOG_DEBUG << "getChassisData enter"; 935 auto getChassisCb = [&, SensorsAsyncResp]( 936 boost::container::flat_set<std::string>& 937 sensorNames) { 938 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 939 auto getConnectionCb = 940 [&, SensorsAsyncResp, sensorNames]( 941 const boost::container::flat_set<std::string>& connections) { 942 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 943 auto getObjectManagerPathsCb = 944 [SensorsAsyncResp, sensorNames, 945 connections](const boost::container::flat_map< 946 std::string, std::string>& objectMgrPaths) { 947 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 948 // Get sensor data and store results in JSON response 949 getSensorData(SensorsAsyncResp, sensorNames, 950 connections, objectMgrPaths); 951 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 952 }; 953 954 // Get mapping from connection names to the DBus object paths 955 // that implement the ObjectManager interface 956 getObjectManagerPaths(SensorsAsyncResp, 957 std::move(getObjectManagerPathsCb)); 958 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 959 }; 960 961 // Get set of connections that provide sensor values 962 getConnections(SensorsAsyncResp, sensorNames, 963 std::move(getConnectionCb)); 964 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 965 }; 966 967 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS 968 // Get all sensor names 969 getAllSensors(SensorsAsyncResp, std::move(getChassisCb)); 970 #else 971 // Get sensor names in chassis 972 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 973 #endif 974 BMCWEB_LOG_DEBUG << "getChassisData exit"; 975 }; 976 977 /** 978 * @brief Entry point for overriding sensor values of given sensor 979 * 980 * @param res response object 981 * @param req request object 982 * @param params parameter passed for CRUD 983 * @param typeList TypeList of sensors for the resource queried 984 * @param chassisSubNode Chassis Node for which the query has to happen 985 */ 986 void setSensorOverride(crow::Response& res, const crow::Request& req, 987 const std::vector<std::string>& params, 988 const std::initializer_list<const char*> typeList, 989 const std::string& chassisSubNode) 990 { 991 992 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 993 // override) based on another d-bus announcement to be more generic. 994 if (params.size() != 1) 995 { 996 messages::internalError(res); 997 res.end(); 998 return; 999 } 1000 1001 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1002 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1003 std::optional<std::vector<nlohmann::json>> fanCollections; 1004 std::vector<nlohmann::json> voltageCollections; 1005 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1006 << "\n"; 1007 1008 if (chassisSubNode == "Thermal") 1009 { 1010 if (!json_util::readJson(req, res, "Temperatures", 1011 temperatureCollections, "Fans", 1012 fanCollections)) 1013 { 1014 return; 1015 } 1016 if (!temperatureCollections && !fanCollections) 1017 { 1018 messages::resourceNotFound(res, "Thermal", 1019 "Temperatures / Voltages"); 1020 res.end(); 1021 return; 1022 } 1023 if (temperatureCollections) 1024 { 1025 allCollections.emplace("Temperatures", 1026 *std::move(temperatureCollections)); 1027 } 1028 if (fanCollections) 1029 { 1030 allCollections.emplace("Fans", *std::move(fanCollections)); 1031 } 1032 } 1033 else if (chassisSubNode == "Power") 1034 { 1035 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1036 { 1037 return; 1038 } 1039 allCollections.emplace("Voltages", std::move(voltageCollections)); 1040 } 1041 else 1042 { 1043 res.result(boost::beast::http::status::not_found); 1044 res.end(); 1045 return; 1046 } 1047 1048 const char* propertyValueName; 1049 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1050 std::string memberId; 1051 double value; 1052 for (auto& collectionItems : allCollections) 1053 { 1054 if (collectionItems.first == "Temperatures") 1055 { 1056 propertyValueName = "ReadingCelsius"; 1057 } 1058 else if (collectionItems.first == "Fans") 1059 { 1060 propertyValueName = "Reading"; 1061 } 1062 else 1063 { 1064 propertyValueName = "ReadingVolts"; 1065 } 1066 for (auto& item : collectionItems.second) 1067 { 1068 if (!json_util::readJson(item, res, "MemberId", memberId, 1069 propertyValueName, value)) 1070 { 1071 return; 1072 } 1073 overrideMap.emplace(memberId, 1074 std::make_pair(value, collectionItems.first)); 1075 } 1076 } 1077 const std::string& chassisName = params[0]; 1078 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1079 res, chassisName, typeList, chassisSubNode); 1080 // first check for valid chassis id & sensor in requested chassis. 1081 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap]( 1082 const boost::container::flat_set< 1083 std::string>& sensorLists) { 1084 boost::container::flat_set<std::string> sensorNames; 1085 for (const auto& item : overrideMap) 1086 { 1087 const auto& sensor = item.first; 1088 if (sensorLists.find(item.first) == sensorLists.end()) 1089 { 1090 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1091 messages::resourceNotFound(sensorAsyncResp->res, 1092 item.second.second, item.first); 1093 return; 1094 } 1095 sensorNames.emplace(sensor); 1096 } 1097 // Get the connection to which the memberId belongs 1098 auto getObjectsWithConnectionCb = 1099 [sensorAsyncResp, overrideMap]( 1100 const boost::container::flat_set<std::string>& connections, 1101 const std::set<std::pair<std::string, std::string>>& 1102 objectsWithConnection) { 1103 if (objectsWithConnection.size() != overrideMap.size()) 1104 { 1105 BMCWEB_LOG_INFO 1106 << "Unable to find all objects with proper connection " 1107 << objectsWithConnection.size() << " requested " 1108 << overrideMap.size() << "\n"; 1109 messages::resourceNotFound( 1110 sensorAsyncResp->res, 1111 sensorAsyncResp->chassisSubNode == "Thermal" 1112 ? "Temperatures" 1113 : "Voltages", 1114 "Count"); 1115 return; 1116 } 1117 for (const auto& item : objectsWithConnection) 1118 { 1119 1120 auto lastPos = item.first.rfind('/'); 1121 if (lastPos == std::string::npos) 1122 { 1123 messages::internalError(sensorAsyncResp->res); 1124 return; 1125 } 1126 std::string sensorName = item.first.substr(lastPos + 1); 1127 1128 const auto& iterator = overrideMap.find(sensorName); 1129 if (iterator == overrideMap.end()) 1130 { 1131 BMCWEB_LOG_INFO << "Unable to find sensor object" 1132 << item.first << "\n"; 1133 messages::internalError(sensorAsyncResp->res); 1134 return; 1135 } 1136 crow::connections::systemBus->async_method_call( 1137 [sensorAsyncResp](const boost::system::error_code ec) { 1138 if (ec) 1139 { 1140 BMCWEB_LOG_DEBUG 1141 << "setOverrideValueStatus DBUS error: " 1142 << ec; 1143 messages::internalError(sensorAsyncResp->res); 1144 return; 1145 } 1146 }, 1147 item.second, item.first, 1148 "org.freedesktop.DBus.Properties", "Set", 1149 "xyz.openbmc_project.Sensor.Value", "Value", 1150 sdbusplus::message::variant<double>( 1151 iterator->second.first)); 1152 } 1153 }; 1154 // Get object with connection for the given sensor name 1155 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1156 std::move(getObjectsWithConnectionCb)); 1157 }; 1158 // get full sensor list for the given chassisId and cross verify the sensor. 1159 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1160 } 1161 1162 } // namespace redfish 1163