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 <utils/json_utils.hpp> 26 #include <variant> 27 28 namespace redfish 29 { 30 31 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/"; 32 33 using GetSubTreeType = std::vector< 34 std::pair<std::string, 35 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 36 37 using SensorVariant = std::variant<int64_t, double>; 38 39 using ManagedObjectsVectorType = std::vector<std::pair< 40 sdbusplus::message::object_path, 41 boost::container::flat_map< 42 std::string, boost::container::flat_map<std::string, SensorVariant>>>>; 43 44 /** 45 * SensorsAsyncResp 46 * Gathers data needed for response processing after async calls are done 47 */ 48 class SensorsAsyncResp 49 { 50 public: 51 SensorsAsyncResp(crow::Response& response, const std::string& chassisId, 52 const std::initializer_list<const char*> types, 53 const std::string& subNode) : 54 res(response), 55 chassisId(chassisId), types(types), chassisSubNode(subNode) 56 { 57 res.jsonValue["@odata.id"] = 58 "/redfish/v1/Chassis/" + chassisId + "/" + subNode; 59 } 60 61 ~SensorsAsyncResp() 62 { 63 if (res.result() == boost::beast::http::status::internal_server_error) 64 { 65 // Reset the json object to clear out any data that made it in 66 // before the error happened todo(ed) handle error condition with 67 // proper code 68 res.jsonValue = nlohmann::json::object(); 69 } 70 res.end(); 71 } 72 73 crow::Response& res; 74 std::string chassisId{}; 75 const std::vector<const char*> types; 76 std::string chassisSubNode{}; 77 }; 78 79 /** 80 * @brief Checks whether the specified sensor is one of the requested types. 81 * @param objectPath DBus object path of the sensor. 82 * @param SensorsAsyncResp Pointer to object holding response data and requested 83 * sensor types. 84 * @return true if sensor type is requested, false otherwise. 85 */ 86 inline bool 87 isRequestedSensorType(const std::string& objectPath, 88 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 89 { 90 for (const char* type : SensorsAsyncResp->types) 91 { 92 if (boost::starts_with(objectPath, type)) 93 { 94 return true; 95 } 96 } 97 return false; 98 } 99 100 /** 101 * @brief Get objects with connection necessary for sensors 102 * @param SensorsAsyncResp Pointer to object holding response data 103 * @param sensorNames Sensors retrieved from chassis 104 * @param callback Callback for processing gathered connections 105 */ 106 template <typename Callback> 107 void getObjectsWithConnection( 108 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 109 const boost::container::flat_set<std::string>& sensorNames, 110 Callback&& callback) 111 { 112 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 113 const std::string path = "/xyz/openbmc_project/sensors"; 114 const std::array<std::string, 1> interfaces = { 115 "xyz.openbmc_project.Sensor.Value"}; 116 117 // Response handler for parsing objects subtree 118 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp, 119 sensorNames](const boost::system::error_code ec, 120 const GetSubTreeType& subtree) { 121 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 122 if (ec) 123 { 124 messages::internalError(SensorsAsyncResp->res); 125 BMCWEB_LOG_ERROR 126 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 127 return; 128 } 129 130 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 131 132 // Make unique list of connections only for requested sensor types and 133 // found in the chassis 134 boost::container::flat_set<std::string> connections; 135 std::set<std::pair<std::string, std::string>> objectsWithConnection; 136 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 137 // producers 138 connections.reserve(8); 139 140 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames.size(); 141 for (const std::string& tsensor : sensorNames) 142 { 143 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 144 } 145 146 for (const std::pair< 147 std::string, 148 std::vector<std::pair<std::string, std::vector<std::string>>>>& 149 object : subtree) 150 { 151 if (isRequestedSensorType(object.first, SensorsAsyncResp)) 152 { 153 auto lastPos = object.first.rfind('/'); 154 if (lastPos != std::string::npos) 155 { 156 std::string sensorName = object.first.substr(lastPos + 1); 157 158 if (sensorNames.find(sensorName) != sensorNames.end()) 159 { 160 // For each Connection name 161 for (const std::pair<std::string, 162 std::vector<std::string>>& 163 objData : object.second) 164 { 165 BMCWEB_LOG_DEBUG << "Adding connection: " 166 << objData.first; 167 connections.insert(objData.first); 168 objectsWithConnection.insert( 169 std::make_pair(object.first, objData.first)); 170 } 171 } 172 } 173 } 174 } 175 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 176 callback(std::move(connections), std::move(objectsWithConnection)); 177 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 178 }; 179 // Make call to ObjectMapper to find all sensors objects 180 crow::connections::systemBus->async_method_call( 181 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 182 "/xyz/openbmc_project/object_mapper", 183 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 184 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 185 } 186 187 /** 188 * @brief Create connections necessary for sensors 189 * @param SensorsAsyncResp Pointer to object holding response data 190 * @param sensorNames Sensors retrieved from chassis 191 * @param callback Callback for processing gathered connections 192 */ 193 template <typename Callback> 194 void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 195 const boost::container::flat_set<std::string>& sensorNames, 196 Callback&& callback) 197 { 198 auto objectsWithConnectionCb = 199 [callback](const boost::container::flat_set<std::string>& connections, 200 const std::set<std::pair<std::string, std::string>>& 201 objectsWithConnection) { 202 callback(std::move(connections)); 203 }; 204 getObjectsWithConnection(SensorsAsyncResp, sensorNames, 205 std::move(objectsWithConnectionCb)); 206 } 207 208 /** 209 * @brief Gets all DBus sensor names. 210 * 211 * Finds the sensor names asynchronously. Invokes callback when information has 212 * been obtained. 213 * 214 * The callback must have the following signature: 215 * @code 216 * callback(boost::container::flat_set<std::string>& sensorNames) 217 * @endcode 218 * 219 * @param SensorsAsyncResp Pointer to object holding response data. 220 * @param callback Callback to invoke when sensor names obtained. 221 */ 222 template <typename Callback> 223 void getAllSensors(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 224 Callback&& callback) 225 { 226 BMCWEB_LOG_DEBUG << "getAllSensors enter"; 227 const std::array<std::string, 1> interfaces = { 228 "xyz.openbmc_project.Sensor.Value"}; 229 230 // Response handler for GetSubTreePaths DBus method 231 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp]( 232 const boost::system::error_code ec, 233 const std::vector<std::string>& sensorList) { 234 BMCWEB_LOG_DEBUG << "getAllSensors respHandler enter"; 235 if (ec) 236 { 237 messages::internalError(SensorsAsyncResp->res); 238 BMCWEB_LOG_ERROR << "getAllSensors respHandler: DBus error " << ec; 239 return; 240 } 241 boost::container::flat_set<std::string> sensorNames; 242 for (const std::string& sensor : sensorList) 243 { 244 std::size_t lastPos = sensor.rfind("/"); 245 if (lastPos == std::string::npos) 246 { 247 BMCWEB_LOG_ERROR << "Failed to find '/' in " << sensor; 248 continue; 249 } 250 std::string sensorName = sensor.substr(lastPos + 1); 251 sensorNames.emplace(sensorName); 252 BMCWEB_LOG_DEBUG << "Added sensor name " << sensorName; 253 } 254 callback(sensorNames); 255 BMCWEB_LOG_DEBUG << "getAllSensors respHandler exit"; 256 }; 257 258 // Query mapper for all DBus sensor object paths 259 crow::connections::systemBus->async_method_call( 260 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 261 "/xyz/openbmc_project/object_mapper", 262 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 263 "/xyz/openbmc_project/sensors", int32_t(0), interfaces); 264 BMCWEB_LOG_DEBUG << "getAllSensors exit"; 265 } 266 267 /** 268 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 269 * @param SensorsAsyncResp Pointer to object holding response data 270 * @param callback Callback for next step in gathered sensor processing 271 */ 272 template <typename Callback> 273 void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 274 Callback&& callback) 275 { 276 BMCWEB_LOG_DEBUG << "getChassis enter"; 277 // Process response from EntityManager and extract chassis data 278 auto respHandler = [callback{std::move(callback)}, 279 SensorsAsyncResp](const boost::system::error_code ec, 280 ManagedObjectsVectorType& resp) { 281 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 282 if (ec) 283 { 284 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 285 messages::internalError(SensorsAsyncResp->res); 286 return; 287 } 288 boost::container::flat_set<std::string> sensorNames; 289 290 // SensorsAsyncResp->chassisId 291 bool foundChassis = false; 292 std::vector<std::string> split; 293 // Reserve space for 294 // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames 295 split.reserve(8); 296 297 for (const auto& objDictEntry : resp) 298 { 299 const std::string& objectPath = 300 static_cast<const std::string&>(objDictEntry.first); 301 boost::algorithm::split(split, objectPath, boost::is_any_of("/")); 302 if (split.size() < 2) 303 { 304 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 305 << objectPath; 306 split.clear(); 307 continue; 308 } 309 const std::string& sensorName = split.end()[-1]; 310 const std::string& chassisName = split.end()[-2]; 311 312 if (chassisName != SensorsAsyncResp->chassisId) 313 { 314 split.clear(); 315 continue; 316 } 317 BMCWEB_LOG_DEBUG << "New sensor: " << sensorName; 318 foundChassis = true; 319 sensorNames.emplace(sensorName); 320 split.clear(); 321 }; 322 BMCWEB_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names"; 323 324 if (!foundChassis) 325 { 326 BMCWEB_LOG_INFO << "Unable to find chassis named " 327 << SensorsAsyncResp->chassisId; 328 messages::resourceNotFound(SensorsAsyncResp->res, "Chassis", 329 SensorsAsyncResp->chassisId); 330 } 331 else 332 { 333 callback(sensorNames); 334 } 335 BMCWEB_LOG_DEBUG << "getChassis respHandler exit"; 336 }; 337 338 // Make call to EntityManager to find all chassis objects 339 crow::connections::systemBus->async_method_call( 340 respHandler, "xyz.openbmc_project.EntityManager", "/", 341 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 342 BMCWEB_LOG_DEBUG << "getChassis exit"; 343 } 344 345 /** 346 * @brief Finds all DBus object paths that implement ObjectManager. 347 * 348 * Creates a mapping from the associated connection name to the object path. 349 * 350 * Finds the object paths asynchronously. Invokes callback when information has 351 * been obtained. 352 * 353 * The callback must have the following signature: 354 * @code 355 * callback(const boost::container::flat_map<std::string, 356 * std::string>& objectMgrPaths) 357 * @endcode 358 * 359 * @param SensorsAsyncResp Pointer to object holding response data. 360 * @param callback Callback to invoke when object paths obtained. 361 */ 362 template <typename Callback> 363 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 364 Callback&& callback) 365 { 366 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 367 const std::array<std::string, 1> interfaces = { 368 "org.freedesktop.DBus.ObjectManager"}; 369 370 // Response handler for GetSubTree DBus method 371 auto respHandler = [callback{std::move(callback)}, 372 SensorsAsyncResp](const boost::system::error_code ec, 373 const GetSubTreeType& subtree) { 374 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 375 if (ec) 376 { 377 messages::internalError(SensorsAsyncResp->res); 378 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 379 << ec; 380 return; 381 } 382 383 // Loop over returned object paths 384 boost::container::flat_map<std::string, std::string> objectMgrPaths; 385 for (const std::pair< 386 std::string, 387 std::vector<std::pair<std::string, std::vector<std::string>>>>& 388 object : subtree) 389 { 390 // Loop over connections for current object path 391 const std::string& objectPath = object.first; 392 for (const std::pair<std::string, std::vector<std::string>>& 393 objData : object.second) 394 { 395 // Add mapping from connection to object path 396 const std::string& connection = objData.first; 397 objectMgrPaths[connection] = objectPath; 398 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 399 << objectPath; 400 } 401 } 402 callback(std::move(objectMgrPaths)); 403 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 404 }; 405 406 // Query mapper for all DBus object paths that implement ObjectManager 407 crow::connections::systemBus->async_method_call( 408 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 409 "/xyz/openbmc_project/object_mapper", 410 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0), 411 interfaces); 412 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 413 } 414 415 /** 416 * @brief Builds a json sensor representation of a sensor. 417 * @param sensorName The name of the sensor to be built 418 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 419 * build 420 * @param interfacesDict A dictionary of the interfaces and properties of said 421 * interfaces to be built from 422 * @param sensor_json The json object to fill 423 */ 424 void objectInterfacesToJson( 425 const std::string& sensorName, const std::string& sensorType, 426 const boost::container::flat_map< 427 std::string, boost::container::flat_map<std::string, SensorVariant>>& 428 interfacesDict, 429 nlohmann::json& sensor_json) 430 { 431 // We need a value interface before we can do anything with it 432 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 433 if (valueIt == interfacesDict.end()) 434 { 435 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 436 return; 437 } 438 439 // Assume values exist as is (10^0 == 1) if no scale exists 440 int64_t scaleMultiplier = 0; 441 442 auto scaleIt = valueIt->second.find("Scale"); 443 // If a scale exists, pull value as int64, and use the scaling. 444 if (scaleIt != valueIt->second.end()) 445 { 446 const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second); 447 if (int64Value != nullptr) 448 { 449 scaleMultiplier = *int64Value; 450 } 451 } 452 453 sensor_json["MemberId"] = sensorName; 454 sensor_json["Name"] = sensorName; 455 sensor_json["Status"]["State"] = "Enabled"; 456 sensor_json["Status"]["Health"] = "OK"; 457 458 // Parameter to set to override the type we get from dbus, and force it to 459 // int, regardless of what is available. This is used for schemas like fan, 460 // that require integers, not floats. 461 bool forceToInt = false; 462 463 const char* unit = "Reading"; 464 if (sensorType == "temperature") 465 { 466 unit = "ReadingCelsius"; 467 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 468 // TODO(ed) Documentation says that path should be type fan_tach, 469 // implementation seems to implement fan 470 } 471 else if (sensorType == "fan" || sensorType == "fan_tach") 472 { 473 unit = "Reading"; 474 sensor_json["ReadingUnits"] = "RPM"; 475 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 476 forceToInt = true; 477 } 478 else if (sensorType == "fan_pwm") 479 { 480 unit = "Reading"; 481 sensor_json["ReadingUnits"] = "Percent"; 482 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 483 forceToInt = true; 484 } 485 else if (sensorType == "voltage") 486 { 487 unit = "ReadingVolts"; 488 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 489 } 490 else if (sensorType == "power") 491 { 492 unit = "LastPowerOutputWatts"; 493 } 494 else 495 { 496 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 497 return; 498 } 499 // Map of dbus interface name, dbus property name and redfish property_name 500 std::vector<std::tuple<const char*, const char*, const char*>> properties; 501 properties.reserve(7); 502 503 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 504 505 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 506 if ((sensorType != "current") && (sensorType != "power")) 507 { 508 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 509 "WarningHigh", "UpperThresholdNonCritical"); 510 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 511 "WarningLow", "LowerThresholdNonCritical"); 512 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 513 "CriticalHigh", "UpperThresholdCritical"); 514 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 515 "CriticalLow", "LowerThresholdCritical"); 516 } 517 518 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 519 520 if (sensorType == "temperature") 521 { 522 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 523 "MinReadingRangeTemp"); 524 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 525 "MaxReadingRangeTemp"); 526 } 527 else if ((sensorType != "current") && (sensorType != "power")) 528 { 529 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 530 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 531 "MinReadingRange"); 532 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 533 "MaxReadingRange"); 534 } 535 536 for (const std::tuple<const char*, const char*, const char*>& p : 537 properties) 538 { 539 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 540 if (interfaceProperties != interfacesDict.end()) 541 { 542 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 543 if (valueIt != interfaceProperties->second.end()) 544 { 545 const SensorVariant& valueVariant = valueIt->second; 546 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 547 // Attempt to pull the int64 directly 548 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 549 550 const double* doubleValue = std::get_if<double>(&valueVariant); 551 double temp = 0.0; 552 if (int64Value != nullptr) 553 { 554 temp = *int64Value; 555 } 556 else if (doubleValue != nullptr) 557 { 558 temp = *doubleValue; 559 } 560 else 561 { 562 BMCWEB_LOG_ERROR 563 << "Got value interface that wasn't int or double"; 564 continue; 565 } 566 temp = temp * std::pow(10, scaleMultiplier); 567 if (forceToInt) 568 { 569 valueIt = static_cast<int64_t>(temp); 570 } 571 else 572 { 573 valueIt = temp; 574 } 575 } 576 } 577 } 578 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 579 } 580 581 /** 582 * @brief Gets the values of the specified sensors. 583 * 584 * Stores the results as JSON in the SensorsAsyncResp. 585 * 586 * Gets the sensor values asynchronously. Stores the results later when the 587 * information has been obtained. 588 * 589 * The sensorNames set contains all sensors for the current chassis. 590 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 591 * requested type are included in the JSON output. 592 * 593 * To minimize the number of DBus calls, the DBus method 594 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 595 * values of all sensors provided by a connection (service). 596 * 597 * The connections set contains all the connections that provide sensor values. 598 * 599 * The objectMgrPaths map contains mappings from a connection name to the 600 * corresponding DBus object path that implements ObjectManager. 601 * 602 * @param SensorsAsyncResp Pointer to object holding response data. 603 * @param sensorNames All sensors within the current chassis. 604 * @param connections Connections that provide sensor values. 605 * @param objectMgrPaths Mappings from connection name to DBus object path that 606 * implements ObjectManager. 607 */ 608 void getSensorData( 609 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 610 const boost::container::flat_set<std::string>& sensorNames, 611 const boost::container::flat_set<std::string>& connections, 612 const boost::container::flat_map<std::string, std::string>& objectMgrPaths) 613 { 614 BMCWEB_LOG_DEBUG << "getSensorData enter"; 615 // Get managed objects from all services exposing sensors 616 for (const std::string& connection : connections) 617 { 618 // Response handler to process managed objects 619 auto getManagedObjectsCb = [&, SensorsAsyncResp, sensorNames]( 620 const boost::system::error_code ec, 621 ManagedObjectsVectorType& resp) { 622 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 623 if (ec) 624 { 625 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 626 messages::internalError(SensorsAsyncResp->res); 627 return; 628 } 629 // Go through all objects and update response with sensor data 630 for (const auto& objDictEntry : resp) 631 { 632 const std::string& objPath = 633 static_cast<const std::string&>(objDictEntry.first); 634 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 635 << objPath; 636 637 // Skip sensor if it is not one of the requested types 638 if (!isRequestedSensorType(objPath, SensorsAsyncResp)) 639 { 640 continue; 641 } 642 643 std::vector<std::string> split; 644 // Reserve space for 645 // /xyz/openbmc_project/sensors/<name>/<subname> 646 split.reserve(6); 647 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 648 if (split.size() < 6) 649 { 650 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 651 << objPath; 652 continue; 653 } 654 // These indexes aren't intuitive, as boost::split puts an empty 655 // string at the beginning 656 const std::string& sensorType = split[4]; 657 const std::string& sensorName = split[5]; 658 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 659 << " sensorType " << sensorType; 660 if (sensorNames.find(sensorName) == sensorNames.end()) 661 { 662 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 663 continue; 664 } 665 666 const char* fieldName = nullptr; 667 if (sensorType == "temperature") 668 { 669 fieldName = "Temperatures"; 670 } 671 else if (sensorType == "fan" || sensorType == "fan_tach" || 672 sensorType == "fan_pwm") 673 { 674 fieldName = "Fans"; 675 } 676 else if (sensorType == "voltage") 677 { 678 fieldName = "Voltages"; 679 } 680 else if (sensorType == "current") 681 { 682 fieldName = "PowerSupplies"; 683 } 684 else if (sensorType == "power") 685 { 686 fieldName = "PowerSupplies"; 687 } 688 else 689 { 690 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 691 << sensorType; 692 continue; 693 } 694 695 nlohmann::json& tempArray = 696 SensorsAsyncResp->res.jsonValue[fieldName]; 697 698 tempArray.push_back( 699 {{"@odata.id", 700 "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId + 701 "/" + SensorsAsyncResp->chassisSubNode + "#/" + 702 fieldName + "/" + std::to_string(tempArray.size())}}); 703 nlohmann::json& sensorJson = tempArray.back(); 704 705 objectInterfacesToJson(sensorName, sensorType, 706 objDictEntry.second, sensorJson); 707 } 708 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 709 }; 710 711 // Find DBus object path that implements ObjectManager for the current 712 // connection. If no mapping found, default to "/". 713 auto iter = objectMgrPaths.find(connection); 714 const std::string& objectMgrPath = 715 (iter != objectMgrPaths.end()) ? iter->second : "/"; 716 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 717 << objectMgrPath; 718 719 crow::connections::systemBus->async_method_call( 720 getManagedObjectsCb, connection, objectMgrPath, 721 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 722 }; 723 BMCWEB_LOG_DEBUG << "getSensorData exit"; 724 } 725 726 /** 727 * @brief Entry point for retrieving sensors data related to requested 728 * chassis. 729 * @param SensorsAsyncResp Pointer to object holding response data 730 */ 731 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 732 { 733 BMCWEB_LOG_DEBUG << "getChassisData enter"; 734 auto getChassisCb = [&, SensorsAsyncResp]( 735 boost::container::flat_set<std::string>& 736 sensorNames) { 737 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 738 auto getConnectionCb = 739 [&, SensorsAsyncResp, sensorNames]( 740 const boost::container::flat_set<std::string>& connections) { 741 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 742 auto getObjectManagerPathsCb = 743 [SensorsAsyncResp, sensorNames, 744 connections](const boost::container::flat_map< 745 std::string, std::string>& objectMgrPaths) { 746 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 747 // Get sensor data and store results in JSON response 748 getSensorData(SensorsAsyncResp, sensorNames, 749 connections, objectMgrPaths); 750 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 751 }; 752 753 // Get mapping from connection names to the DBus object paths 754 // that implement the ObjectManager interface 755 getObjectManagerPaths(SensorsAsyncResp, 756 std::move(getObjectManagerPathsCb)); 757 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 758 }; 759 760 // Get set of connections that provide sensor values 761 getConnections(SensorsAsyncResp, sensorNames, 762 std::move(getConnectionCb)); 763 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 764 }; 765 766 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS 767 // Get all sensor names 768 getAllSensors(SensorsAsyncResp, std::move(getChassisCb)); 769 #else 770 // Get sensor names in chassis 771 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 772 #endif 773 BMCWEB_LOG_DEBUG << "getChassisData exit"; 774 }; 775 776 /** 777 * @brief Entry point for overriding sensor values of given sensor 778 * 779 * @param res response object 780 * @param req request object 781 * @param params parameter passed for CRUD 782 * @param typeList TypeList of sensors for the resource queried 783 * @param chassisSubNode Chassis Node for which the query has to happen 784 */ 785 void setSensorOverride(crow::Response& res, const crow::Request& req, 786 const std::vector<std::string>& params, 787 const std::initializer_list<const char*> typeList, 788 const std::string& chassisSubNode) 789 { 790 791 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 792 // override) based on another d-bus announcement to be more generic. 793 if (params.size() != 1) 794 { 795 messages::internalError(res); 796 res.end(); 797 return; 798 } 799 800 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 801 std::optional<std::vector<nlohmann::json>> temperatureCollections; 802 std::optional<std::vector<nlohmann::json>> fanCollections; 803 std::vector<nlohmann::json> voltageCollections; 804 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 805 << "\n"; 806 807 if (chassisSubNode == "Thermal") 808 { 809 if (!json_util::readJson(req, res, "Temperatures", 810 temperatureCollections, "Fans", 811 fanCollections)) 812 { 813 return; 814 } 815 if (!temperatureCollections && !fanCollections) 816 { 817 messages::resourceNotFound(res, "Thermal", 818 "Temperatures / Voltages"); 819 res.end(); 820 return; 821 } 822 if (temperatureCollections) 823 { 824 allCollections.emplace("Temperatures", 825 *std::move(temperatureCollections)); 826 } 827 if (fanCollections) 828 { 829 allCollections.emplace("Fans", *std::move(fanCollections)); 830 } 831 } 832 else if (chassisSubNode == "Power") 833 { 834 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 835 { 836 return; 837 } 838 allCollections.emplace("Voltages", std::move(voltageCollections)); 839 } 840 else 841 { 842 res.result(boost::beast::http::status::not_found); 843 res.end(); 844 return; 845 } 846 847 const char* propertyValueName; 848 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 849 std::string memberId; 850 double value; 851 for (auto& collectionItems : allCollections) 852 { 853 if (collectionItems.first == "Temperatures") 854 { 855 propertyValueName = "ReadingCelsius"; 856 } 857 else if (collectionItems.first == "Fans") 858 { 859 propertyValueName = "Reading"; 860 } 861 else 862 { 863 propertyValueName = "ReadingVolts"; 864 } 865 for (auto& item : collectionItems.second) 866 { 867 if (!json_util::readJson(item, res, "MemberId", memberId, 868 propertyValueName, value)) 869 { 870 return; 871 } 872 overrideMap.emplace(memberId, 873 std::make_pair(value, collectionItems.first)); 874 } 875 } 876 const std::string& chassisName = params[0]; 877 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 878 res, chassisName, typeList, chassisSubNode); 879 // first check for valid chassis id & sensor in requested chassis. 880 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap]( 881 const boost::container::flat_set< 882 std::string>& sensorLists) { 883 boost::container::flat_set<std::string> sensorNames; 884 for (const auto& item : overrideMap) 885 { 886 const auto& sensor = item.first; 887 if (sensorLists.find(item.first) == sensorLists.end()) 888 { 889 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 890 messages::resourceNotFound(sensorAsyncResp->res, 891 item.second.second, item.first); 892 return; 893 } 894 sensorNames.emplace(sensor); 895 } 896 // Get the connection to which the memberId belongs 897 auto getObjectsWithConnectionCb = 898 [sensorAsyncResp, overrideMap]( 899 const boost::container::flat_set<std::string>& connections, 900 const std::set<std::pair<std::string, std::string>>& 901 objectsWithConnection) { 902 if (objectsWithConnection.size() != overrideMap.size()) 903 { 904 BMCWEB_LOG_INFO 905 << "Unable to find all objects with proper connection " 906 << objectsWithConnection.size() << " requested " 907 << overrideMap.size() << "\n"; 908 messages::resourceNotFound( 909 sensorAsyncResp->res, 910 sensorAsyncResp->chassisSubNode == "Thermal" 911 ? "Temperatures" 912 : "Voltages", 913 "Count"); 914 return; 915 } 916 for (const auto& item : objectsWithConnection) 917 { 918 919 auto lastPos = item.first.rfind('/'); 920 if (lastPos == std::string::npos) 921 { 922 messages::internalError(sensorAsyncResp->res); 923 return; 924 } 925 std::string sensorName = item.first.substr(lastPos + 1); 926 927 const auto& iterator = overrideMap.find(sensorName); 928 if (iterator == overrideMap.end()) 929 { 930 BMCWEB_LOG_INFO << "Unable to find sensor object" 931 << item.first << "\n"; 932 messages::internalError(sensorAsyncResp->res); 933 return; 934 } 935 crow::connections::systemBus->async_method_call( 936 [sensorAsyncResp](const boost::system::error_code ec) { 937 if (ec) 938 { 939 BMCWEB_LOG_DEBUG 940 << "setOverrideValueStatus DBUS error: " 941 << ec; 942 messages::internalError(sensorAsyncResp->res); 943 return; 944 } 945 }, 946 item.second, item.first, 947 "org.freedesktop.DBus.Properties", "Set", 948 "xyz.openbmc_project.Sensor.Value", "Value", 949 sdbusplus::message::variant<double>( 950 iterator->second.first)); 951 } 952 }; 953 // Get object with connection for the given sensor name 954 getObjectsWithConnection(sensorAsyncResp, sensorNames, 955 std::move(getObjectsWithConnectionCb)); 956 }; 957 // get full sensor list for the given chassisId and cross verify the sensor. 958 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 959 } 960 961 } // namespace redfish 962