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 26 namespace redfish 27 { 28 29 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/"; 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 = sdbusplus::message::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 res(response), 52 chassisId(chassisId), types(types) 53 { 54 res.jsonValue["@odata.id"] = 55 "/redfish/v1/Chassis/" + chassisId + "/Thermal"; 56 } 57 58 ~SensorsAsyncResp() 59 { 60 if (res.result() == boost::beast::http::status::internal_server_error) 61 { 62 // Reset the json object to clear out any data that made it in 63 // before the error happened todo(ed) handle error condition with 64 // proper code 65 res.jsonValue = nlohmann::json::object(); 66 } 67 res.end(); 68 } 69 70 void setErrorStatus() 71 { 72 res.result(boost::beast::http::status::internal_server_error); 73 } 74 75 crow::Response& res; 76 std::string chassisId{}; 77 const std::vector<const char*> types; 78 }; 79 80 /** 81 * @brief Creates connections necessary for chassis sensors 82 * @param SensorsAsyncResp Pointer to object holding response data 83 * @param sensorNames Sensors retrieved from chassis 84 * @param callback Callback for processing gathered connections 85 */ 86 template <typename Callback> 87 void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 88 const boost::container::flat_set<std::string>& sensorNames, 89 Callback&& callback) 90 { 91 BMCWEB_LOG_DEBUG << "getConnections enter"; 92 const std::string path = "/xyz/openbmc_project/sensors"; 93 const std::array<std::string, 1> interfaces = { 94 "xyz.openbmc_project.Sensor.Value"}; 95 96 // Response handler for parsing objects subtree 97 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp, 98 sensorNames](const boost::system::error_code ec, 99 const GetSubTreeType& subtree) { 100 BMCWEB_LOG_DEBUG << "getConnections resp_handler enter"; 101 if (ec) 102 { 103 SensorsAsyncResp->setErrorStatus(); 104 BMCWEB_LOG_ERROR << "getConnections resp_handler: Dbus error " 105 << ec; 106 return; 107 } 108 109 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 110 111 // Make unique list of connections only for requested sensor types and 112 // found in the chassis 113 boost::container::flat_set<std::string> connections; 114 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 115 // producers 116 connections.reserve(8); 117 118 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames.size(); 119 for (const std::string& tsensor : sensorNames) 120 { 121 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 122 } 123 124 for (const std::pair< 125 std::string, 126 std::vector<std::pair<std::string, std::vector<std::string>>>>& 127 object : subtree) 128 { 129 for (const char* type : SensorsAsyncResp->types) 130 { 131 if (boost::starts_with(object.first, type)) 132 { 133 auto lastPos = object.first.rfind('/'); 134 if (lastPos != std::string::npos) 135 { 136 std::string sensorName = 137 object.first.substr(lastPos + 1); 138 139 if (sensorNames.find(sensorName) != sensorNames.end()) 140 { 141 // For each Connection name 142 for (const std::pair<std::string, 143 std::vector<std::string>>& 144 objData : object.second) 145 { 146 BMCWEB_LOG_DEBUG << "Adding connection: " 147 << objData.first; 148 connections.insert(objData.first); 149 } 150 } 151 } 152 break; 153 } 154 } 155 } 156 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 157 callback(std::move(connections)); 158 BMCWEB_LOG_DEBUG << "getConnections resp_handler exit"; 159 }; 160 161 // Make call to ObjectMapper to find all sensors objects 162 crow::connections::systemBus->async_method_call( 163 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 164 "/xyz/openbmc_project/object_mapper", 165 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 166 BMCWEB_LOG_DEBUG << "getConnections exit"; 167 } 168 169 /** 170 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 171 * @param SensorsAsyncResp Pointer to object holding response data 172 * @param callback Callback for next step in gathered sensor processing 173 */ 174 template <typename Callback> 175 void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 176 Callback&& callback) 177 { 178 BMCWEB_LOG_DEBUG << "getChassis enter"; 179 // Process response from EntityManager and extract chassis data 180 auto respHandler = [callback{std::move(callback)}, 181 SensorsAsyncResp](const boost::system::error_code ec, 182 ManagedObjectsVectorType& resp) { 183 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 184 if (ec) 185 { 186 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 187 SensorsAsyncResp->setErrorStatus(); 188 return; 189 } 190 boost::container::flat_set<std::string> sensorNames; 191 192 // SensorsAsyncResp->chassisId 193 bool foundChassis = false; 194 std::vector<std::string> split; 195 // Reserve space for 196 // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames 197 split.reserve(8); 198 199 for (const auto& objDictEntry : resp) 200 { 201 const std::string& objectPath = 202 static_cast<const std::string&>(objDictEntry.first); 203 boost::algorithm::split(split, objectPath, boost::is_any_of("/")); 204 if (split.size() < 2) 205 { 206 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 207 << objectPath; 208 split.clear(); 209 continue; 210 } 211 const std::string& sensorName = split.end()[-1]; 212 const std::string& chassisName = split.end()[-2]; 213 214 if (chassisName != SensorsAsyncResp->chassisId) 215 { 216 split.clear(); 217 continue; 218 } 219 BMCWEB_LOG_DEBUG << "New sensor: " << sensorName; 220 foundChassis = true; 221 sensorNames.emplace(sensorName); 222 split.clear(); 223 }; 224 BMCWEB_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names"; 225 226 if (!foundChassis) 227 { 228 BMCWEB_LOG_INFO << "Unable to find chassis named " 229 << SensorsAsyncResp->chassisId; 230 SensorsAsyncResp->res.result(boost::beast::http::status::not_found); 231 } 232 else 233 { 234 callback(sensorNames); 235 } 236 BMCWEB_LOG_DEBUG << "getChassis respHandler exit"; 237 }; 238 239 // Make call to EntityManager to find all chassis objects 240 crow::connections::systemBus->async_method_call( 241 respHandler, "xyz.openbmc_project.EntityManager", "/", 242 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 243 BMCWEB_LOG_DEBUG << "getChassis exit"; 244 } 245 246 /** 247 * @brief Builds a json sensor representation of a sensor. 248 * @param sensorName The name of the sensor to be built 249 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 250 * build 251 * @param interfacesDict A dictionary of the interfaces and properties of said 252 * interfaces to be built from 253 * @param sensor_json The json object to fill 254 */ 255 void objectInterfacesToJson( 256 const std::string& sensorName, const std::string& sensorType, 257 const boost::container::flat_map< 258 std::string, boost::container::flat_map<std::string, SensorVariant>>& 259 interfacesDict, 260 nlohmann::json& sensor_json) 261 { 262 // We need a value interface before we can do anything with it 263 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 264 if (valueIt == interfacesDict.end()) 265 { 266 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 267 return; 268 } 269 270 // Assume values exist as is (10^0 == 1) if no scale exists 271 int64_t scaleMultiplier = 0; 272 273 auto scaleIt = valueIt->second.find("Scale"); 274 // If a scale exists, pull value as int64, and use the scaling. 275 if (scaleIt != valueIt->second.end()) 276 { 277 const int64_t* int64Value = 278 mapbox::getPtr<const int64_t>(scaleIt->second); 279 if (int64Value != nullptr) 280 { 281 scaleMultiplier = *int64Value; 282 } 283 } 284 285 sensor_json["MemberId"] = sensorName; 286 sensor_json["Name"] = sensorName; 287 sensor_json["Status"]["State"] = "Enabled"; 288 sensor_json["Status"]["Health"] = "OK"; 289 290 // Parameter to set to override the type we get from dbus, and force it to 291 // int, regardless of what is available. This is used for schemas like fan, 292 // that require integers, not floats. 293 bool forceToInt = false; 294 295 const char* unit = "Reading"; 296 if (sensorType == "temperature") 297 { 298 unit = "ReadingCelsius"; 299 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 300 // TODO(ed) Documentation says that path should be type fan_tach, 301 // implementation seems to implement fan 302 } 303 else if (sensorType == "fan" || sensorType == "fan_tach") 304 { 305 unit = "Reading"; 306 sensor_json["ReadingUnits"] = "RPM"; 307 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 308 forceToInt = true; 309 } 310 else if (sensorType == "voltage") 311 { 312 unit = "ReadingVolts"; 313 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 314 } 315 else 316 { 317 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 318 return; 319 } 320 // Map of dbus interface name, dbus property name and redfish property_name 321 std::vector<std::tuple<const char*, const char*, const char*>> properties; 322 properties.reserve(7); 323 324 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 325 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 326 "WarningHigh", "UpperThresholdNonCritical"); 327 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 328 "WarningLow", "LowerThresholdNonCritical"); 329 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 330 "CriticalHigh", "UpperThresholdCritical"); 331 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 332 "CriticalLow", "LowerThresholdCritical"); 333 334 if (sensorType == "temperature") 335 { 336 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 337 "MinReadingRangeTemp"); 338 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 339 "MaxReadingRangeTemp"); 340 } 341 else 342 { 343 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 344 "MinReadingRange"); 345 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 346 "MaxReadingRange"); 347 } 348 349 for (const std::tuple<const char*, const char*, const char*>& p : 350 properties) 351 { 352 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 353 if (interfaceProperties != interfacesDict.end()) 354 { 355 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 356 if (valueIt != interfaceProperties->second.end()) 357 { 358 const SensorVariant& valueVariant = valueIt->second; 359 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 360 361 // Attempt to pull the int64 directly 362 const int64_t* int64Value = 363 mapbox::getPtr<const int64_t>(valueVariant); 364 365 if (int64Value != nullptr) 366 { 367 if (forceToInt || scaleMultiplier >= 0) 368 { 369 valueIt = *int64Value * std::pow(10, scaleMultiplier); 370 } 371 else 372 { 373 valueIt = 374 *int64Value * 375 std::pow(10, static_cast<double>(scaleMultiplier)); 376 } 377 } 378 // Attempt to pull the float directly 379 const double* doubleValue = 380 mapbox::getPtr<const double>(valueVariant); 381 382 if (doubleValue != nullptr) 383 { 384 if (!forceToInt) 385 { 386 valueIt = 387 *doubleValue * 388 std::pow(10, static_cast<double>(scaleMultiplier)); 389 } 390 else 391 { 392 valueIt = static_cast<int64_t>( 393 *doubleValue * std::pow(10, scaleMultiplier)); 394 } 395 } 396 } 397 } 398 } 399 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 400 } 401 402 /** 403 * @brief Entry point for retrieving sensors data related to requested 404 * chassis. 405 * @param SensorsAsyncResp Pointer to object holding response data 406 */ 407 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 408 { 409 BMCWEB_LOG_DEBUG << "getChassisData enter"; 410 auto getChassisCb = [&, SensorsAsyncResp]( 411 boost::container::flat_set<std::string>& 412 sensorNames) { 413 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 414 auto getConnectionCb = 415 [&, SensorsAsyncResp, sensorNames]( 416 const boost::container::flat_set<std::string>& connections) { 417 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 418 // Get managed objects from all services exposing sensors 419 for (const std::string& connection : connections) 420 { 421 // Response handler to process managed objects 422 auto getManagedObjectsCb = 423 [&, SensorsAsyncResp, 424 sensorNames](const boost::system::error_code ec, 425 ManagedObjectsVectorType& resp) { 426 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 427 if (ec) 428 { 429 BMCWEB_LOG_ERROR 430 << "getManagedObjectsCb DBUS error: " << ec; 431 SensorsAsyncResp->setErrorStatus(); 432 return; 433 } 434 // Go through all objects and update response with 435 // sensor data 436 for (const auto& objDictEntry : resp) 437 { 438 const std::string& objPath = 439 static_cast<const std::string&>( 440 objDictEntry.first); 441 BMCWEB_LOG_DEBUG 442 << "getManagedObjectsCb parsing object " 443 << objPath; 444 445 std::vector<std::string> split; 446 // Reserve space for 447 // /xyz/openbmc_project/sensors/<name>/<subname> 448 split.reserve(6); 449 boost::algorithm::split(split, objPath, 450 boost::is_any_of("/")); 451 if (split.size() < 6) 452 { 453 BMCWEB_LOG_ERROR 454 << "Got path that isn't long enough " 455 << objPath; 456 continue; 457 } 458 // These indexes aren't intuitive, as 459 // boost::split puts an empty string at the 460 // beggining 461 const std::string& sensorType = split[4]; 462 const std::string& sensorName = split[5]; 463 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 464 << " sensorType " 465 << sensorType; 466 if (sensorNames.find(sensorName) == 467 sensorNames.end()) 468 { 469 BMCWEB_LOG_ERROR << sensorName 470 << " not in sensor list "; 471 continue; 472 } 473 474 const char* fieldName = nullptr; 475 if (sensorType == "temperature") 476 { 477 fieldName = "Temperatures"; 478 } 479 else if (sensorType == "fan" || 480 sensorType == "fan_tach") 481 { 482 fieldName = "Fans"; 483 } 484 else if (sensorType == "voltage") 485 { 486 fieldName = "Voltages"; 487 } 488 else if (sensorType == "current") 489 { 490 fieldName = "PowerSupply"; 491 } 492 else if (sensorType == "power") 493 { 494 fieldName = "PowerSupply"; 495 } 496 else 497 { 498 BMCWEB_LOG_ERROR 499 << "Unsure how to handle sensorType " 500 << sensorType; 501 continue; 502 } 503 504 nlohmann::json& tempArray = 505 SensorsAsyncResp->res.jsonValue[fieldName]; 506 507 // Create the array if it doesn't yet exist 508 if (tempArray.is_array() == false) 509 { 510 tempArray = nlohmann::json::array(); 511 } 512 513 tempArray.push_back( 514 {{"@odata.id", 515 "/redfish/v1/Chassis/" + 516 SensorsAsyncResp->chassisId + 517 "/Thermal#/" + sensorName}}); 518 nlohmann::json& sensorJson = tempArray.back(); 519 objectInterfacesToJson(sensorName, sensorType, 520 objDictEntry.second, 521 sensorJson); 522 } 523 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 524 }; 525 crow::connections::systemBus->async_method_call( 526 getManagedObjectsCb, connection, "/", 527 "org.freedesktop.DBus.ObjectManager", 528 "GetManagedObjects"); 529 }; 530 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 531 }; 532 // get connections and then pass it to get sensors 533 getConnections(SensorsAsyncResp, sensorNames, 534 std::move(getConnectionCb)); 535 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 536 }; 537 538 // get chassis information related to sensors 539 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 540 BMCWEB_LOG_DEBUG << "getChassisData exit"; 541 }; 542 543 } // namespace redfish 544