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 #include <dbus_singleton.hpp> 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 <boost/variant.hpp> 25 #include <boost/variant/get.hpp> 26 27 namespace redfish { 28 29 constexpr const char* DBUS_SENSOR_PREFIX = "/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 ManagedObjectsVectorType = std::vector<std::pair< 36 dbus::object_path, 37 boost::container::flat_map< 38 std::string, 39 boost::container::flat_map<dbus::string, dbus::dbus_variant>>>>; 40 41 /** 42 * AsyncResp 43 * Gathers data needed for response processing after async calls are done 44 */ 45 class AsyncResp { 46 public: 47 AsyncResp(crow::response& response, const std::string& chassisId, 48 const std::initializer_list<const char*> types) 49 : chassisId(chassisId), res(response), types(types) { 50 res.json_value["@odata.id"] = 51 "/redfish/v1/Chassis/" + chassisId + "/Thermal"; 52 } 53 54 ~AsyncResp() { 55 if (res.code != static_cast<int>(HttpRespCode::OK)) { 56 // Reset the json object to clear out any data that made it in before the 57 // error happened 58 // todo(ed) handle error condition with proper code 59 res.json_value = nlohmann::json::object(); 60 } 61 res.end(); 62 } 63 void setErrorStatus() { 64 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); 65 } 66 67 std::string chassisId{}; 68 crow::response& res; 69 const std::vector<const char*> types; 70 }; 71 72 /** 73 * @brief Creates connections necessary for chassis sensors 74 * @param asyncResp Pointer to object holding response data 75 * @param sensorNames Sensors retrieved from chassis 76 * @param callback Callback for processing gathered connections 77 */ 78 template <typename Callback> 79 void getConnections(const std::shared_ptr<AsyncResp>& asyncResp, 80 const boost::container::flat_set<std::string>& sensorNames, 81 Callback&& callback) { 82 CROW_LOG_DEBUG << "getConnections"; 83 const std::string path = "/xyz/openbmc_project/Sensors"; 84 const std::array<std::string, 1> interfaces = { 85 "xyz.openbmc_project.Sensor.Value"}; 86 const dbus::endpoint object_mapper( 87 "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", 88 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 89 90 // Response handler for parsing objects subtree 91 auto resp_handler = [ callback{std::move(callback)}, asyncResp, sensorNames ]( 92 const boost::system::error_code ec, const GetSubTreeType& subtree) { 93 if (ec != 0) { 94 asyncResp->setErrorStatus(); 95 CROW_LOG_ERROR << "Dbus error " << ec; 96 return; 97 } 98 99 CROW_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 100 101 // Make unique list of connections only for requested sensor types and 102 // found in the chassis 103 boost::container::flat_set<std::string> connections; 104 // Intrinsic to avoid malloc. Most systems will have < 8 sensor producers 105 connections.reserve(8); 106 107 CROW_LOG_DEBUG << "sensorNames list cout: " << sensorNames.size(); 108 for (const std::string& tsensor : sensorNames) { 109 CROW_LOG_DEBUG << "Sensor to find: " << tsensor; 110 } 111 112 for (const std::pair< 113 std::string, 114 std::vector<std::pair<std::string, std::vector<std::string>>>>& 115 object : subtree) { 116 for (const char* type : asyncResp->types) { 117 if (boost::starts_with(object.first, type)) { 118 auto lastPos = object.first.rfind('/'); 119 if (lastPos != std::string::npos) { 120 std::string sensorName = object.first.substr(lastPos + 1); 121 122 if (sensorNames.find(sensorName) != sensorNames.end()) { 123 // For each connection name 124 for (const std::pair<std::string, std::vector<std::string>>& 125 objData : object.second) { 126 connections.insert(objData.first); 127 } 128 } 129 } 130 break; 131 } 132 } 133 } 134 CROW_LOG_DEBUG << "Found " << connections.size() << " connections"; 135 callback(std::move(connections)); 136 }; 137 138 // Make call to ObjectMapper to find all sensors objects 139 crow::connections::system_bus->async_method_call(resp_handler, object_mapper, 140 path, 2, interfaces); 141 } 142 143 /** 144 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 145 * @param asyncResp Pointer to object holding response data 146 * @param callback Callback for next step in gathered sensor processing 147 */ 148 template <typename Callback> 149 void getChassis(const std::shared_ptr<AsyncResp>& asyncResp, 150 Callback&& callback) { 151 CROW_LOG_DEBUG << "getChassis Done"; 152 const dbus::endpoint entityManager = { 153 "xyz.openbmc_project.EntityManager", 154 "/xyz/openbmc_project/Inventory/Item/Chassis", 155 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}; 156 157 // Process response from EntityManager and extract chassis data 158 auto resp_handler = [ callback{std::move(callback)}, asyncResp ]( 159 const boost::system::error_code ec, ManagedObjectsVectorType& resp) { 160 CROW_LOG_DEBUG << "getChassis resp_handler called back Done"; 161 if (ec) { 162 CROW_LOG_ERROR << "getChassis resp_handler got error " << ec; 163 asyncResp->setErrorStatus(); 164 return; 165 } 166 boost::container::flat_set<std::string> sensorNames; 167 const std::string chassis_prefix = 168 "/xyz/openbmc_project/Inventory/Item/Chassis/" + asyncResp->chassisId + 169 '/'; 170 CROW_LOG_DEBUG << "Chassis Prefix " << chassis_prefix; 171 bool foundChassis = false; 172 for (const auto& objDictEntry : resp) { 173 if (boost::starts_with(objDictEntry.first.value, chassis_prefix)) { 174 foundChassis = true; 175 const std::string sensorName = 176 objDictEntry.first.value.substr(chassis_prefix.size()); 177 // Make sure this isn't a subobject (like a threshold) 178 const std::size_t sensorPos = sensorName.find('/'); 179 if (sensorPos == std::string::npos) { 180 CROW_LOG_DEBUG << "Adding sensor " << sensorName; 181 182 sensorNames.emplace(sensorName); 183 } 184 } 185 }; 186 CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names"; 187 188 if (!foundChassis) { 189 CROW_LOG_INFO << "Unable to find chassis named " << asyncResp->chassisId; 190 asyncResp->res.code = static_cast<int>(HttpRespCode::NOT_FOUND); 191 } else { 192 callback(sensorNames); 193 } 194 }; 195 196 // Make call to EntityManager to find all chassis objects 197 crow::connections::system_bus->async_method_call(resp_handler, entityManager); 198 } 199 200 /** 201 * @brief Builds a json sensor representation of a sensor. 202 * @param sensorName The name of the sensor to be built 203 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 204 * build 205 * @param interfacesDict A dictionary of the interfaces and properties of said 206 * interfaces to be built from 207 * @param sensor_json The json object to fill 208 */ 209 void objectInterfacesToJson( 210 const std::string& sensorName, const std::string& sensorType, 211 const boost::container::flat_map< 212 std::string, 213 boost::container::flat_map<dbus::string, dbus::dbus_variant>>& 214 interfacesDict, 215 nlohmann::json& sensor_json) { 216 // We need a value interface before we can do anything with it 217 auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 218 if (value_it == interfacesDict.end()) { 219 CROW_LOG_ERROR << "Sensor doesn't have a value interface"; 220 return; 221 } 222 223 // Assume values exist as is (10^0 == 1) if no scale exists 224 int64_t scaleMultiplier = 0; 225 226 auto scale_it = value_it->second.find("Scale"); 227 // If a scale exists, pull value as int64, and use the scaling. 228 if (scale_it != value_it->second.end()) { 229 const int64_t* int64Value = boost::get<int64_t>(&scale_it->second); 230 if (int64Value != nullptr) { 231 scaleMultiplier = *int64Value; 232 } 233 } 234 235 sensor_json["MemberId"] = sensorName; 236 sensor_json["Name"] = sensorName; 237 sensor_json["Status"]["State"] = "Enabled"; 238 sensor_json["Status"]["Health"] = "OK"; 239 240 // Parameter to set to override the type we get from dbus, and force it to 241 // int, regardless of what is available. This is used for schemas like fan, 242 // that require integers, not floats. 243 bool forceToInt = false; 244 245 const char* unit = "Reading"; 246 if (sensorType == "temperature") { 247 unit = "ReadingCelsius"; 248 // TODO(ed) Documentation says that path should be type fan_tach, 249 // implementation seems to implement fan 250 } else if (sensorType == "fan" || sensorType == "fan_tach") { 251 unit = "Reading"; 252 sensor_json["ReadingUnits"] = "RPM"; 253 forceToInt = true; 254 } else if (sensorType == "voltage") { 255 unit = "ReadingVolts"; 256 } else { 257 CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 258 return; 259 } 260 // Map of dbus interface name, dbus property name and redfish property_name 261 std::vector<std::tuple<const char*, const char*, const char*>> properties; 262 properties.reserve(7); 263 264 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 265 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 266 "WarningHigh", "UpperThresholdNonCritical"); 267 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 268 "WarningLow", "LowerThresholdNonCritical"); 269 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 270 "CriticalHigh", "UpperThresholdCritical"); 271 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 272 "CriticalLow", "LowerThresholdCritical"); 273 274 if (sensorType == "temperature") { 275 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 276 "MinReadingRangeTemp"); 277 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 278 "MaxReadingRangeTemp"); 279 } else { 280 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 281 "MinReadingRange"); 282 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 283 "MaxReadingRange"); 284 } 285 286 for (const std::tuple<const char*, const char*, const char*>& p : 287 properties) { 288 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 289 if (interfaceProperties != interfacesDict.end()) { 290 auto value_it = interfaceProperties->second.find(std::get<1>(p)); 291 if (value_it != interfaceProperties->second.end()) { 292 const dbus::dbus_variant& valueVariant = value_it->second; 293 nlohmann::json& value_it = sensor_json[std::get<2>(p)]; 294 // Attempt to pull the int64 directly 295 const int64_t* int64Value = boost::get<int64_t>(&valueVariant); 296 297 if (int64Value != nullptr) { 298 if (forceToInt || scaleMultiplier >= 0) { 299 value_it = *int64Value * std::pow(10, scaleMultiplier); 300 } else { 301 value_it = *int64Value * 302 std::pow(10, static_cast<double>(scaleMultiplier)); 303 } 304 } 305 // Attempt to pull the float directly 306 const double* doubleValue = boost::get<double>(&valueVariant); 307 308 if (doubleValue != nullptr) { 309 if (!forceToInt) { 310 value_it = *doubleValue * 311 std::pow(10, static_cast<double>(scaleMultiplier)); 312 } else { 313 value_it = static_cast<int64_t>(*doubleValue * 314 std::pow(10, scaleMultiplier)); 315 } 316 } 317 } 318 } 319 } 320 } 321 322 /** 323 * @brief Entry point for retrieving sensors data related to requested 324 * chassis. 325 * @param asyncResp Pointer to object holding response data 326 */ 327 void getChassisData(const std::shared_ptr<AsyncResp>& asyncResp) { 328 CROW_LOG_DEBUG << "getChassisData"; 329 auto getChassisCb = [&, asyncResp](boost::container::flat_set<std::string>& 330 sensorNames) { 331 CROW_LOG_DEBUG << "getChassisCb Done"; 332 auto getConnectionCb = 333 [&, asyncResp, sensorNames]( 334 const boost::container::flat_set<std::string>& connections) { 335 CROW_LOG_DEBUG << "getConnectionCb Done"; 336 // Get managed objects from all services exposing sensors 337 for (const std::string& connection : connections) { 338 // Response handler to process managed objects 339 auto getManagedObjectsCb = [&, asyncResp, sensorNames]( 340 const boost::system::error_code ec, 341 ManagedObjectsVectorType& resp) { 342 // Go through all objects and update response with 343 // sensor data 344 for (const auto& objDictEntry : resp) { 345 const std::string& objPath = objDictEntry.first.value; 346 CROW_LOG_DEBUG << "getManagedObjectsCb parsing object " 347 << objPath; 348 if (!boost::starts_with(objPath, DBUS_SENSOR_PREFIX)) { 349 CROW_LOG_ERROR << "Got path that isn't in sensor namespace: " 350 << objPath; 351 continue; 352 } 353 std::vector<std::string> split; 354 // Reserve space for 355 // /xyz/openbmc_project/Sensors/<name>/<subname> 356 split.reserve(6); 357 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 358 if (split.size() < 6) { 359 CROW_LOG_ERROR << "Got path that isn't long enough " 360 << objPath; 361 continue; 362 } 363 // These indexes aren't intuitive, as boost::split puts an empty 364 // string at the beginning 365 const std::string& sensorType = split[4]; 366 const std::string& sensorName = split[5]; 367 CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType " 368 << sensorType; 369 if (sensorNames.find(sensorName) == sensorNames.end()) { 370 CROW_LOG_ERROR << sensorName << " not in sensor list "; 371 continue; 372 } 373 374 const char* fieldName = nullptr; 375 if (sensorType == "temperature") { 376 fieldName = "Temperatures"; 377 } else if (sensorType == "fan" || sensorType == "fan_tach") { 378 fieldName = "Fans"; 379 } else if (sensorType == "voltage") { 380 fieldName = "Voltages"; 381 } else if (sensorType == "current") { 382 fieldName = "PowerSupply"; 383 } else if (sensorType == "power") { 384 fieldName = "PowerSupply"; 385 } else { 386 CROW_LOG_ERROR << "Unsure how to handle sensorType " 387 << sensorType; 388 continue; 389 } 390 391 nlohmann::json& temp_array = 392 asyncResp->res.json_value[fieldName]; 393 394 // Create the array if it doesn't yet exist 395 if (temp_array.is_array() == false) { 396 temp_array = nlohmann::json::array(); 397 } 398 399 temp_array.push_back(nlohmann::json::object()); 400 nlohmann::json& sensor_json = temp_array.back(); 401 sensor_json["@odata.id"] = "/redfish/v1/Chassis/" + 402 asyncResp->chassisId + "/Thermal#/" + 403 sensorName; 404 objectInterfacesToJson(sensorName, sensorType, 405 objDictEntry.second, sensor_json); 406 } 407 }; 408 409 dbus::endpoint ep(connection, "/xyz/openbmc_project/Sensors", 410 "org.freedesktop.DBus.ObjectManager", 411 "GetManagedObjects"); 412 crow::connections::system_bus->async_method_call( 413 getManagedObjectsCb, ep); 414 }; 415 }; 416 // Get connections and then pass it to get sensors 417 getConnections(asyncResp, sensorNames, std::move(getConnectionCb)); 418 }; 419 420 // Get chassis information related to sensors 421 getChassis(asyncResp, std::move(getChassisCb)); 422 }; 423 424 } // namespace redfish 425