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 == "fan_pwm") 311 { 312 unit = "Reading"; 313 sensor_json["ReadingUnits"] = "Percent"; 314 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 315 forceToInt = true; 316 } 317 else if (sensorType == "voltage") 318 { 319 unit = "ReadingVolts"; 320 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 321 } 322 else 323 { 324 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 325 return; 326 } 327 // Map of dbus interface name, dbus property name and redfish property_name 328 std::vector<std::tuple<const char*, const char*, const char*>> properties; 329 properties.reserve(7); 330 331 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 332 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 333 "WarningHigh", "UpperThresholdNonCritical"); 334 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 335 "WarningLow", "LowerThresholdNonCritical"); 336 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 337 "CriticalHigh", "UpperThresholdCritical"); 338 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 339 "CriticalLow", "LowerThresholdCritical"); 340 341 if (sensorType == "temperature") 342 { 343 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 344 "MinReadingRangeTemp"); 345 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 346 "MaxReadingRangeTemp"); 347 } 348 else 349 { 350 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 351 "MinReadingRange"); 352 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 353 "MaxReadingRange"); 354 } 355 356 for (const std::tuple<const char*, const char*, const char*>& p : 357 properties) 358 { 359 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 360 if (interfaceProperties != interfacesDict.end()) 361 { 362 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 363 if (valueIt != interfaceProperties->second.end()) 364 { 365 const SensorVariant& valueVariant = valueIt->second; 366 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 367 // Attempt to pull the int64 directly 368 const int64_t* int64Value = 369 mapbox::getPtr<const int64_t>(valueVariant); 370 371 const double* doubleValue = 372 mapbox::getPtr<const double>(valueVariant); 373 double temp = 0.0; 374 if (int64Value != nullptr) 375 { 376 temp = *int64Value; 377 } 378 else if (doubleValue != nullptr) 379 { 380 temp = *doubleValue; 381 } 382 else 383 { 384 BMCWEB_LOG_ERROR 385 << "Got value interface that wasn't int or double"; 386 continue; 387 } 388 temp = temp * std::pow(10, scaleMultiplier); 389 if (forceToInt) 390 { 391 valueIt = static_cast<int64_t>(temp); 392 } 393 else 394 { 395 valueIt = temp; 396 } 397 } 398 } 399 } 400 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 401 } 402 403 /** 404 * @brief Entry point for retrieving sensors data related to requested 405 * chassis. 406 * @param SensorsAsyncResp Pointer to object holding response data 407 */ 408 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 409 { 410 BMCWEB_LOG_DEBUG << "getChassisData enter"; 411 auto getChassisCb = [&, SensorsAsyncResp]( 412 boost::container::flat_set<std::string>& 413 sensorNames) { 414 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 415 auto getConnectionCb = 416 [&, SensorsAsyncResp, sensorNames]( 417 const boost::container::flat_set<std::string>& connections) { 418 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 419 // Get managed objects from all services exposing sensors 420 for (const std::string& connection : connections) 421 { 422 // Response handler to process managed objects 423 auto getManagedObjectsCb = 424 [&, SensorsAsyncResp, 425 sensorNames](const boost::system::error_code ec, 426 ManagedObjectsVectorType& resp) { 427 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 428 if (ec) 429 { 430 BMCWEB_LOG_ERROR 431 << "getManagedObjectsCb DBUS error: " << ec; 432 SensorsAsyncResp->setErrorStatus(); 433 return; 434 } 435 // Go through all objects and update response with 436 // sensor data 437 for (const auto& objDictEntry : resp) 438 { 439 const std::string& objPath = 440 static_cast<const std::string&>( 441 objDictEntry.first); 442 BMCWEB_LOG_DEBUG 443 << "getManagedObjectsCb parsing object " 444 << objPath; 445 446 std::vector<std::string> split; 447 // Reserve space for 448 // /xyz/openbmc_project/sensors/<name>/<subname> 449 split.reserve(6); 450 boost::algorithm::split(split, objPath, 451 boost::is_any_of("/")); 452 if (split.size() < 6) 453 { 454 BMCWEB_LOG_ERROR 455 << "Got path that isn't long enough " 456 << objPath; 457 continue; 458 } 459 // These indexes aren't intuitive, as 460 // boost::split puts an empty string at the 461 // beggining 462 const std::string& sensorType = split[4]; 463 const std::string& sensorName = split[5]; 464 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 465 << " sensorType " 466 << sensorType; 467 if (sensorNames.find(sensorName) == 468 sensorNames.end()) 469 { 470 BMCWEB_LOG_ERROR << sensorName 471 << " not in sensor list "; 472 continue; 473 } 474 475 const char* fieldName = nullptr; 476 if (sensorType == "temperature") 477 { 478 fieldName = "Temperatures"; 479 } 480 else if (sensorType == "fan" || 481 sensorType == "fan_tach" || 482 sensorType == "fan_pwm") 483 { 484 fieldName = "Fans"; 485 } 486 else if (sensorType == "voltage") 487 { 488 fieldName = "Voltages"; 489 } 490 else if (sensorType == "current") 491 { 492 fieldName = "PowerSupply"; 493 } 494 else if (sensorType == "power") 495 { 496 fieldName = "PowerSupply"; 497 } 498 else 499 { 500 BMCWEB_LOG_ERROR 501 << "Unsure how to handle sensorType " 502 << sensorType; 503 continue; 504 } 505 506 nlohmann::json& tempArray = 507 SensorsAsyncResp->res.jsonValue[fieldName]; 508 509 tempArray.push_back( 510 {{"@odata.id", 511 "/redfish/v1/Chassis/" + 512 SensorsAsyncResp->chassisId + 513 "/Thermal#/" + fieldName + "/" + 514 std::to_string(tempArray.size())}}); 515 nlohmann::json& sensorJson = tempArray.back(); 516 517 objectInterfacesToJson(sensorName, sensorType, 518 objDictEntry.second, 519 sensorJson); 520 } 521 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 522 }; 523 crow::connections::systemBus->async_method_call( 524 getManagedObjectsCb, connection, "/", 525 "org.freedesktop.DBus.ObjectManager", 526 "GetManagedObjects"); 527 }; 528 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 529 }; 530 // get connections and then pass it to get sensors 531 getConnections(SensorsAsyncResp, sensorNames, 532 std::move(getConnectionCb)); 533 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 534 }; 535 536 // get chassis information related to sensors 537 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 538 BMCWEB_LOG_DEBUG << "getChassisData exit"; 539 }; 540 541 } // namespace redfish 542