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