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 messages::resourceNotFound(SensorsAsyncResp->res, "Chassis", 231 SensorsAsyncResp->chassisId); 232 } 233 else 234 { 235 callback(sensorNames); 236 } 237 BMCWEB_LOG_DEBUG << "getChassis respHandler exit"; 238 }; 239 240 // Make call to EntityManager to find all chassis objects 241 crow::connections::systemBus->async_method_call( 242 respHandler, "xyz.openbmc_project.EntityManager", "/", 243 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 244 BMCWEB_LOG_DEBUG << "getChassis exit"; 245 } 246 247 /** 248 * @brief Builds a json sensor representation of a sensor. 249 * @param sensorName The name of the sensor to be built 250 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 251 * build 252 * @param interfacesDict A dictionary of the interfaces and properties of said 253 * interfaces to be built from 254 * @param sensor_json The json object to fill 255 */ 256 void objectInterfacesToJson( 257 const std::string& sensorName, const std::string& sensorType, 258 const boost::container::flat_map< 259 std::string, boost::container::flat_map<std::string, SensorVariant>>& 260 interfacesDict, 261 nlohmann::json& sensor_json) 262 { 263 // We need a value interface before we can do anything with it 264 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 265 if (valueIt == interfacesDict.end()) 266 { 267 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 268 return; 269 } 270 271 // Assume values exist as is (10^0 == 1) if no scale exists 272 int64_t scaleMultiplier = 0; 273 274 auto scaleIt = valueIt->second.find("Scale"); 275 // If a scale exists, pull value as int64, and use the scaling. 276 if (scaleIt != valueIt->second.end()) 277 { 278 const int64_t* int64Value = 279 mapbox::getPtr<const int64_t>(scaleIt->second); 280 if (int64Value != nullptr) 281 { 282 scaleMultiplier = *int64Value; 283 } 284 } 285 286 sensor_json["MemberId"] = sensorName; 287 sensor_json["Name"] = sensorName; 288 sensor_json["Status"]["State"] = "Enabled"; 289 sensor_json["Status"]["Health"] = "OK"; 290 291 // Parameter to set to override the type we get from dbus, and force it to 292 // int, regardless of what is available. This is used for schemas like fan, 293 // that require integers, not floats. 294 bool forceToInt = false; 295 296 const char* unit = "Reading"; 297 if (sensorType == "temperature") 298 { 299 unit = "ReadingCelsius"; 300 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 301 // TODO(ed) Documentation says that path should be type fan_tach, 302 // implementation seems to implement fan 303 } 304 else if (sensorType == "fan" || sensorType == "fan_tach") 305 { 306 unit = "Reading"; 307 sensor_json["ReadingUnits"] = "RPM"; 308 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 309 forceToInt = true; 310 } 311 else if (sensorType == "fan_pwm") 312 { 313 unit = "Reading"; 314 sensor_json["ReadingUnits"] = "Percent"; 315 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 316 forceToInt = true; 317 } 318 else if (sensorType == "voltage") 319 { 320 unit = "ReadingVolts"; 321 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 322 } 323 else 324 { 325 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 326 return; 327 } 328 // Map of dbus interface name, dbus property name and redfish property_name 329 std::vector<std::tuple<const char*, const char*, const char*>> properties; 330 properties.reserve(7); 331 332 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 333 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 334 "WarningHigh", "UpperThresholdNonCritical"); 335 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 336 "WarningLow", "LowerThresholdNonCritical"); 337 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 338 "CriticalHigh", "UpperThresholdCritical"); 339 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 340 "CriticalLow", "LowerThresholdCritical"); 341 342 if (sensorType == "temperature") 343 { 344 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 345 "MinReadingRangeTemp"); 346 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 347 "MaxReadingRangeTemp"); 348 } 349 else 350 { 351 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 352 "MinReadingRange"); 353 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 354 "MaxReadingRange"); 355 } 356 357 for (const std::tuple<const char*, const char*, const char*>& p : 358 properties) 359 { 360 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 361 if (interfaceProperties != interfacesDict.end()) 362 { 363 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 364 if (valueIt != interfaceProperties->second.end()) 365 { 366 const SensorVariant& valueVariant = valueIt->second; 367 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 368 // Attempt to pull the int64 directly 369 const int64_t* int64Value = 370 mapbox::getPtr<const int64_t>(valueVariant); 371 372 const double* doubleValue = 373 mapbox::getPtr<const double>(valueVariant); 374 double temp = 0.0; 375 if (int64Value != nullptr) 376 { 377 temp = *int64Value; 378 } 379 else if (doubleValue != nullptr) 380 { 381 temp = *doubleValue; 382 } 383 else 384 { 385 BMCWEB_LOG_ERROR 386 << "Got value interface that wasn't int or double"; 387 continue; 388 } 389 temp = temp * std::pow(10, scaleMultiplier); 390 if (forceToInt) 391 { 392 valueIt = static_cast<int64_t>(temp); 393 } 394 else 395 { 396 valueIt = temp; 397 } 398 } 399 } 400 } 401 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 402 } 403 404 /** 405 * @brief Entry point for retrieving sensors data related to requested 406 * chassis. 407 * @param SensorsAsyncResp Pointer to object holding response data 408 */ 409 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 410 { 411 BMCWEB_LOG_DEBUG << "getChassisData enter"; 412 auto getChassisCb = [&, SensorsAsyncResp]( 413 boost::container::flat_set<std::string>& 414 sensorNames) { 415 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 416 auto getConnectionCb = 417 [&, SensorsAsyncResp, sensorNames]( 418 const boost::container::flat_set<std::string>& connections) { 419 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 420 // Get managed objects from all services exposing sensors 421 for (const std::string& connection : connections) 422 { 423 // Response handler to process managed objects 424 auto getManagedObjectsCb = 425 [&, SensorsAsyncResp, 426 sensorNames](const boost::system::error_code ec, 427 ManagedObjectsVectorType& resp) { 428 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 429 if (ec) 430 { 431 BMCWEB_LOG_ERROR 432 << "getManagedObjectsCb DBUS error: " << ec; 433 SensorsAsyncResp->setErrorStatus(); 434 return; 435 } 436 // Go through all objects and update response with 437 // sensor data 438 for (const auto& objDictEntry : resp) 439 { 440 const std::string& objPath = 441 static_cast<const std::string&>( 442 objDictEntry.first); 443 BMCWEB_LOG_DEBUG 444 << "getManagedObjectsCb parsing object " 445 << objPath; 446 447 std::vector<std::string> split; 448 // Reserve space for 449 // /xyz/openbmc_project/sensors/<name>/<subname> 450 split.reserve(6); 451 boost::algorithm::split(split, objPath, 452 boost::is_any_of("/")); 453 if (split.size() < 6) 454 { 455 BMCWEB_LOG_ERROR 456 << "Got path that isn't long enough " 457 << objPath; 458 continue; 459 } 460 // These indexes aren't intuitive, as 461 // boost::split puts an empty string at the 462 // beggining 463 const std::string& sensorType = split[4]; 464 const std::string& sensorName = split[5]; 465 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 466 << " sensorType " 467 << sensorType; 468 if (sensorNames.find(sensorName) == 469 sensorNames.end()) 470 { 471 BMCWEB_LOG_ERROR << sensorName 472 << " not in sensor list "; 473 continue; 474 } 475 476 const char* fieldName = nullptr; 477 if (sensorType == "temperature") 478 { 479 fieldName = "Temperatures"; 480 } 481 else if (sensorType == "fan" || 482 sensorType == "fan_tach" || 483 sensorType == "fan_pwm") 484 { 485 fieldName = "Fans"; 486 } 487 else if (sensorType == "voltage") 488 { 489 fieldName = "Voltages"; 490 } 491 else if (sensorType == "current") 492 { 493 fieldName = "PowerSupply"; 494 } 495 else if (sensorType == "power") 496 { 497 fieldName = "PowerSupply"; 498 } 499 else 500 { 501 BMCWEB_LOG_ERROR 502 << "Unsure how to handle sensorType " 503 << sensorType; 504 continue; 505 } 506 507 nlohmann::json& tempArray = 508 SensorsAsyncResp->res.jsonValue[fieldName]; 509 510 tempArray.push_back( 511 {{"@odata.id", 512 "/redfish/v1/Chassis/" + 513 SensorsAsyncResp->chassisId + 514 "/Thermal#/" + fieldName + "/" + 515 std::to_string(tempArray.size())}}); 516 nlohmann::json& sensorJson = tempArray.back(); 517 518 objectInterfacesToJson(sensorName, sensorType, 519 objDictEntry.second, 520 sensorJson); 521 } 522 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 523 }; 524 crow::connections::systemBus->async_method_call( 525 getManagedObjectsCb, connection, "/", 526 "org.freedesktop.DBus.ObjectManager", 527 "GetManagedObjects"); 528 }; 529 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 530 }; 531 // get connections and then pass it to get sensors 532 getConnections(SensorsAsyncResp, sensorNames, 533 std::move(getConnectionCb)); 534 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 535 }; 536 537 // get chassis information related to sensors 538 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 539 BMCWEB_LOG_DEBUG << "getChassisData exit"; 540 }; 541 542 } // namespace redfish 543