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 using GetSubTreeType = std::vector< 32 std::pair<std::string, 33 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 34 35 using SensorVariant = std::variant<int64_t, double, uint32_t, bool>; 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 const std::string& subNode) : 52 res(response), 53 chassisId(chassisId), types(types), chassisSubNode(subNode) 54 { 55 } 56 57 ~SensorsAsyncResp() 58 { 59 if (res.result() == boost::beast::http::status::internal_server_error) 60 { 61 // Reset the json object to clear out any data that made it in 62 // before the error happened todo(ed) handle error condition with 63 // proper code 64 res.jsonValue = nlohmann::json::object(); 65 } 66 res.end(); 67 } 68 69 crow::Response& res; 70 std::string chassisId{}; 71 const std::vector<const char*> types; 72 std::string chassisSubNode{}; 73 }; 74 75 /** 76 * @brief Get objects with connection necessary for sensors 77 * @param SensorsAsyncResp Pointer to object holding response data 78 * @param sensorNames Sensors retrieved from chassis 79 * @param callback Callback for processing gathered connections 80 */ 81 template <typename Callback> 82 void getObjectsWithConnection( 83 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 84 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 85 Callback&& callback) 86 { 87 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 88 const std::string path = "/xyz/openbmc_project/sensors"; 89 const std::array<std::string, 1> interfaces = { 90 "xyz.openbmc_project.Sensor.Value"}; 91 92 // Response handler for parsing objects subtree 93 auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp, 94 sensorNames](const boost::system::error_code ec, 95 const GetSubTreeType& subtree) { 96 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 97 if (ec) 98 { 99 messages::internalError(SensorsAsyncResp->res); 100 BMCWEB_LOG_ERROR 101 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 102 return; 103 } 104 105 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 106 107 // Make unique list of connections only for requested sensor types and 108 // found in the chassis 109 boost::container::flat_set<std::string> connections; 110 std::set<std::pair<std::string, std::string>> objectsWithConnection; 111 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 112 // producers 113 connections.reserve(8); 114 115 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size(); 116 for (const std::string& tsensor : *sensorNames) 117 { 118 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 119 } 120 121 for (const std::pair< 122 std::string, 123 std::vector<std::pair<std::string, std::vector<std::string>>>>& 124 object : subtree) 125 { 126 if (sensorNames->find(object.first) != sensorNames->end()) 127 { 128 for (const std::pair<std::string, std::vector<std::string>>& 129 objData : object.second) 130 { 131 BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first; 132 connections.insert(objData.first); 133 objectsWithConnection.insert( 134 std::make_pair(object.first, objData.first)); 135 } 136 } 137 } 138 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 139 callback(std::move(connections), std::move(objectsWithConnection)); 140 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 141 }; 142 // Make call to ObjectMapper to find all sensors objects 143 crow::connections::systemBus->async_method_call( 144 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 145 "/xyz/openbmc_project/object_mapper", 146 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 147 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 148 } 149 150 /** 151 * @brief Create connections necessary for sensors 152 * @param SensorsAsyncResp Pointer to object holding response data 153 * @param sensorNames Sensors retrieved from chassis 154 * @param callback Callback for processing gathered connections 155 */ 156 template <typename Callback> 157 void getConnections( 158 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 159 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 160 Callback&& callback) 161 { 162 auto objectsWithConnectionCb = 163 [callback](const boost::container::flat_set<std::string>& connections, 164 const std::set<std::pair<std::string, std::string>>& 165 objectsWithConnection) { 166 callback(std::move(connections)); 167 }; 168 getObjectsWithConnection(SensorsAsyncResp, sensorNames, 169 std::move(objectsWithConnectionCb)); 170 } 171 172 /** 173 * @brief Shrinks the list of sensors for processing 174 * @param SensorsAysncResp The class holding the Redfish response 175 * @param allSensors A list of all the sensors associated to the 176 * chassis element (i.e. baseboard, front panel, etc...) 177 * @param activeSensors A list that is a reduction of the incoming 178 * allSensors list. Eliminate Thermal sensors when a Power request is 179 * made, and eliminate Power sensors when a Thermal request is made. 180 */ 181 void reduceSensorList( 182 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 183 const std::vector<std::string>* allSensors, 184 std::shared_ptr<boost::container::flat_set<std::string>> activeSensors) 185 { 186 if (SensorsAsyncResp == nullptr) 187 { 188 return; 189 } 190 if ((allSensors == nullptr) || (activeSensors == nullptr)) 191 { 192 messages::resourceNotFound( 193 SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode, 194 SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures" 195 : "Voltages"); 196 197 return; 198 } 199 if (allSensors->empty()) 200 { 201 // Nothing to do, the activeSensors object is also empty 202 return; 203 } 204 205 for (const char* type : SensorsAsyncResp->types) 206 { 207 for (const std::string& sensor : *allSensors) 208 { 209 if (boost::starts_with(sensor, type)) 210 { 211 activeSensors->emplace(sensor); 212 } 213 } 214 } 215 } 216 217 /** 218 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 219 * @param SensorsAsyncResp Pointer to object holding response data 220 * @param callback Callback for next step in gathered sensor processing 221 */ 222 template <typename Callback> 223 void getChassis(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 224 Callback&& callback) 225 { 226 BMCWEB_LOG_DEBUG << "getChassis enter"; 227 const std::array<const char*, 3> interfaces = { 228 "xyz.openbmc_project.Inventory.Item.Board", 229 "xyz.openbmc_project.Inventory.Item.Chassis", 230 "xyz.openbmc_project.Inventory.Item.PowerSupply"}; 231 auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp]( 232 const boost::system::error_code ec, 233 const std::vector<std::string>& chassisPaths) { 234 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 235 if (ec) 236 { 237 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 238 messages::internalError(sensorsAsyncResp->res); 239 return; 240 } 241 242 const std::string* chassisPath = nullptr; 243 std::string chassisName; 244 for (const std::string& chassis : chassisPaths) 245 { 246 std::size_t lastPos = chassis.rfind("/"); 247 if (lastPos == std::string::npos) 248 { 249 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 250 continue; 251 } 252 chassisName = chassis.substr(lastPos + 1); 253 if (chassisName == sensorsAsyncResp->chassisId) 254 { 255 chassisPath = &chassis; 256 break; 257 } 258 } 259 if (chassisPath == nullptr) 260 { 261 messages::resourceNotFound(sensorsAsyncResp->res, "Chassis", 262 sensorsAsyncResp->chassisId); 263 return; 264 } 265 266 const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode; 267 if (chassisSubNode == "Power") 268 { 269 sensorsAsyncResp->res.jsonValue["@odata.type"] = 270 "#Power.v1_5_2.Power"; 271 } 272 else if (chassisSubNode == "Thermal") 273 { 274 sensorsAsyncResp->res.jsonValue["@odata.type"] = 275 "#Thermal.v1_4_0.Thermal"; 276 sensorsAsyncResp->res.jsonValue["Fans"] = nlohmann::json::array(); 277 sensorsAsyncResp->res.jsonValue["Temperatures"] = 278 nlohmann::json::array(); 279 } 280 sensorsAsyncResp->res.jsonValue["@odata.id"] = 281 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + 282 chassisSubNode; 283 284 sensorsAsyncResp->res.jsonValue["@odata.context"] = 285 "/redfish/v1/$metadata#" + chassisSubNode + "." + chassisSubNode; 286 sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode; 287 sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode; 288 289 // Get the list of sensors for this Chassis element 290 std::string sensorPath = *chassisPath + "/sensors"; 291 crow::connections::systemBus->async_method_call( 292 [sensorsAsyncResp, callback{std::move(callback)}]( 293 const boost::system::error_code ec, 294 const std::variant<std::vector<std::string>>& 295 variantEndpoints) { 296 if (ec) 297 { 298 if (ec.value() != EBADR) 299 { 300 messages::internalError(sensorsAsyncResp->res); 301 return; 302 } 303 } 304 const std::vector<std::string>* nodeSensorList = 305 std::get_if<std::vector<std::string>>(&(variantEndpoints)); 306 if (nodeSensorList == nullptr) 307 { 308 messages::resourceNotFound( 309 sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode, 310 sensorsAsyncResp->chassisSubNode == "Thermal" 311 ? "Temperatures" 312 : "Voltages"); 313 return; 314 } 315 const std::shared_ptr<boost::container::flat_set<std::string>> 316 culledSensorList = std::make_shared< 317 boost::container::flat_set<std::string>>(); 318 reduceSensorList(sensorsAsyncResp, nodeSensorList, 319 culledSensorList); 320 callback(culledSensorList); 321 }, 322 "xyz.openbmc_project.ObjectMapper", sensorPath, 323 "org.freedesktop.DBus.Properties", "Get", 324 "xyz.openbmc_project.Association", "endpoints"); 325 }; 326 327 // Get the Chassis Collection 328 crow::connections::systemBus->async_method_call( 329 respHandler, "xyz.openbmc_project.ObjectMapper", 330 "/xyz/openbmc_project/object_mapper", 331 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 332 "/xyz/openbmc_project/inventory", int32_t(0), interfaces); 333 BMCWEB_LOG_DEBUG << "getChassis exit"; 334 } 335 336 /** 337 * @brief Finds all DBus object paths that implement ObjectManager. 338 * 339 * Creates a mapping from the associated connection name to the object path. 340 * 341 * Finds the object paths asynchronously. Invokes callback when information has 342 * been obtained. 343 * 344 * The callback must have the following signature: 345 * @code 346 * callback(const boost::container::flat_map<std::string, 347 * std::string>& objectMgrPaths) 348 * @endcode 349 * 350 * @param sensorsAsyncResp Pointer to object holding response data. 351 * @param callback Callback to invoke when object paths obtained. 352 */ 353 template <typename Callback> 354 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 355 Callback&& callback) 356 { 357 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 358 const std::array<std::string, 1> interfaces = { 359 "org.freedesktop.DBus.ObjectManager"}; 360 361 // Response handler for GetSubTree DBus method 362 auto respHandler = [callback{std::move(callback)}, 363 SensorsAsyncResp](const boost::system::error_code ec, 364 const GetSubTreeType& subtree) { 365 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 366 if (ec) 367 { 368 messages::internalError(SensorsAsyncResp->res); 369 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 370 << ec; 371 return; 372 } 373 374 // Loop over returned object paths 375 boost::container::flat_map<std::string, std::string> objectMgrPaths; 376 for (const std::pair< 377 std::string, 378 std::vector<std::pair<std::string, std::vector<std::string>>>>& 379 object : subtree) 380 { 381 // Loop over connections for current object path 382 const std::string& objectPath = object.first; 383 for (const std::pair<std::string, std::vector<std::string>>& 384 objData : object.second) 385 { 386 // Add mapping from connection to object path 387 const std::string& connection = objData.first; 388 objectMgrPaths[connection] = objectPath; 389 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 390 << objectPath; 391 } 392 } 393 callback(std::move(objectMgrPaths)); 394 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 395 }; 396 397 // Query mapper for all DBus object paths that implement ObjectManager 398 crow::connections::systemBus->async_method_call( 399 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 400 "/xyz/openbmc_project/object_mapper", 401 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0), 402 interfaces); 403 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 404 } 405 406 /** 407 * @brief Retrieves the health from a sensor . 408 * @param interfacesDict Map of all sensor interfaces 409 */ 410 411 static std::string getHealth( 412 const boost::container::flat_map< 413 std::string, boost::container::flat_map<std::string, SensorVariant>>& 414 interfacesDict) 415 { 416 auto criticalThresholdIt = 417 interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 418 if (criticalThresholdIt != interfacesDict.end()) 419 { 420 auto thresholdHighIt = 421 criticalThresholdIt->second.find("CriticalAlarmHigh"); 422 auto thresholdLowIt = 423 criticalThresholdIt->second.find("CriticalAlarmLow"); 424 if (thresholdHighIt != criticalThresholdIt->second.end()) 425 { 426 const bool* asserted = std::get_if<bool>(&thresholdHighIt->second); 427 if (asserted == nullptr) 428 { 429 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 430 } 431 else if (*asserted) 432 { 433 return "Critical"; 434 } 435 } 436 if (thresholdLowIt != criticalThresholdIt->second.end()) 437 { 438 const bool* asserted = std::get_if<bool>(&thresholdLowIt->second); 439 if (asserted == nullptr) 440 { 441 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 442 } 443 else if (*asserted) 444 { 445 return "Critical"; 446 } 447 } 448 } 449 450 auto warningThresholdIt = 451 interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 452 if (warningThresholdIt != interfacesDict.end()) 453 { 454 auto thresholdHighIt = 455 warningThresholdIt->second.find("WarningAlarmHigh"); 456 auto thresholdLowIt = 457 warningThresholdIt->second.find("WarningAlarmLow"); 458 if (thresholdHighIt != warningThresholdIt->second.end()) 459 { 460 const bool* asserted = std::get_if<bool>(&thresholdHighIt->second); 461 if (asserted == nullptr) 462 { 463 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 464 } 465 else if (*asserted) 466 { 467 return "Warning"; 468 } 469 } 470 if (thresholdLowIt != warningThresholdIt->second.end()) 471 { 472 const bool* asserted = std::get_if<bool>(&thresholdLowIt->second); 473 if (asserted == nullptr) 474 { 475 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 476 } 477 else if (*asserted) 478 { 479 return "Warning"; 480 } 481 } 482 } 483 return "OK"; 484 } 485 486 /** 487 * @brief Builds a json sensor representation of a sensor. 488 * @param sensorName The name of the sensor to be built 489 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 490 * build 491 * @param interfacesDict A dictionary of the interfaces and properties of said 492 * interfaces to be built from 493 * @param sensor_json The json object to fill 494 */ 495 void objectInterfacesToJson( 496 const std::string& sensorName, const std::string& sensorType, 497 const boost::container::flat_map< 498 std::string, boost::container::flat_map<std::string, SensorVariant>>& 499 interfacesDict, 500 nlohmann::json& sensor_json) 501 { 502 // We need a value interface before we can do anything with it 503 auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value"); 504 if (valueIt == interfacesDict.end()) 505 { 506 BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface"; 507 return; 508 } 509 510 // Assume values exist as is (10^0 == 1) if no scale exists 511 int64_t scaleMultiplier = 0; 512 513 auto scaleIt = valueIt->second.find("Scale"); 514 // If a scale exists, pull value as int64, and use the scaling. 515 if (scaleIt != valueIt->second.end()) 516 { 517 const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second); 518 if (int64Value != nullptr) 519 { 520 scaleMultiplier = *int64Value; 521 } 522 } 523 524 sensor_json["MemberId"] = sensorName; 525 sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " "); 526 527 sensor_json["Status"]["State"] = "Enabled"; 528 sensor_json["Status"]["Health"] = getHealth(interfacesDict); 529 530 // Parameter to set to override the type we get from dbus, and force it to 531 // int, regardless of what is available. This is used for schemas like fan, 532 // that require integers, not floats. 533 bool forceToInt = false; 534 535 const char* unit = "Reading"; 536 if (sensorType == "temperature") 537 { 538 unit = "ReadingCelsius"; 539 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 540 // TODO(ed) Documentation says that path should be type fan_tach, 541 // implementation seems to implement fan 542 } 543 else if (sensorType == "fan" || sensorType == "fan_tach") 544 { 545 unit = "Reading"; 546 sensor_json["ReadingUnits"] = "RPM"; 547 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 548 forceToInt = true; 549 } 550 else if (sensorType == "fan_pwm") 551 { 552 unit = "Reading"; 553 sensor_json["ReadingUnits"] = "Percent"; 554 sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan"; 555 forceToInt = true; 556 } 557 else if (sensorType == "voltage") 558 { 559 unit = "ReadingVolts"; 560 sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage"; 561 } 562 else if (sensorType == "power") 563 { 564 std::string sensorNameLower = 565 boost::algorithm::to_lower_copy(sensorName); 566 567 if (!sensorName.compare("total_power")) 568 { 569 unit = "PowerConsumedWatts"; 570 } 571 else if (sensorNameLower.find("input") != std::string::npos) 572 { 573 unit = "PowerInputWatts"; 574 } 575 else 576 { 577 unit = "PowerOutputWatts"; 578 } 579 } 580 else 581 { 582 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 583 return; 584 } 585 // Map of dbus interface name, dbus property name and redfish property_name 586 std::vector<std::tuple<const char*, const char*, const char*>> properties; 587 properties.reserve(7); 588 589 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 590 591 // If sensor type doesn't map to Redfish PowerSupply, add threshold props 592 if ((sensorType != "current") && (sensorType != "power")) 593 { 594 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 595 "WarningHigh", "UpperThresholdNonCritical"); 596 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 597 "WarningLow", "LowerThresholdNonCritical"); 598 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 599 "CriticalHigh", "UpperThresholdCritical"); 600 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 601 "CriticalLow", "LowerThresholdCritical"); 602 } 603 604 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 605 606 if (sensorType == "temperature") 607 { 608 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 609 "MinReadingRangeTemp"); 610 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 611 "MaxReadingRangeTemp"); 612 } 613 else if ((sensorType != "current") && (sensorType != "power")) 614 { 615 // Sensor type doesn't map to Redfish PowerSupply; add min/max props 616 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 617 "MinReadingRange"); 618 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 619 "MaxReadingRange"); 620 } 621 622 for (const std::tuple<const char*, const char*, const char*>& p : 623 properties) 624 { 625 auto interfaceProperties = interfacesDict.find(std::get<0>(p)); 626 if (interfaceProperties != interfacesDict.end()) 627 { 628 auto valueIt = interfaceProperties->second.find(std::get<1>(p)); 629 if (valueIt != interfaceProperties->second.end()) 630 { 631 const SensorVariant& valueVariant = valueIt->second; 632 nlohmann::json& valueIt = sensor_json[std::get<2>(p)]; 633 // Attempt to pull the int64 directly 634 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 635 636 const double* doubleValue = std::get_if<double>(&valueVariant); 637 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant); 638 double temp = 0.0; 639 if (int64Value != nullptr) 640 { 641 temp = *int64Value; 642 } 643 else if (doubleValue != nullptr) 644 { 645 temp = *doubleValue; 646 } 647 else if (uValue != nullptr) 648 { 649 temp = *uValue; 650 } 651 else 652 { 653 BMCWEB_LOG_ERROR 654 << "Got value interface that wasn't int or double"; 655 continue; 656 } 657 temp = temp * std::pow(10, scaleMultiplier); 658 if (forceToInt) 659 { 660 valueIt = static_cast<int64_t>(temp); 661 } 662 else 663 { 664 valueIt = temp; 665 } 666 } 667 } 668 } 669 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 670 } 671 672 static void 673 populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp) 674 { 675 crow::connections::systemBus->async_method_call( 676 [sensorsAsyncResp](const boost::system::error_code ec, 677 const GetSubTreeType& resp) { 678 if (ec) 679 { 680 return; // don't have to have this interface 681 } 682 for (const std::pair<std::string, 683 std::vector<std::pair< 684 std::string, std::vector<std::string>>>>& 685 pathPair : resp) 686 { 687 const std::string& path = pathPair.first; 688 const std::vector< 689 std::pair<std::string, std::vector<std::string>>>& objDict = 690 pathPair.second; 691 if (objDict.empty()) 692 { 693 continue; // this should be impossible 694 } 695 696 const std::string& owner = objDict.begin()->first; 697 crow::connections::systemBus->async_method_call( 698 [path, owner, 699 sensorsAsyncResp](const boost::system::error_code ec, 700 std::variant<std::vector<std::string>> 701 variantEndpoints) { 702 if (ec) 703 { 704 return; // if they don't have an association we 705 // can't tell what chassis is 706 } 707 // verify part of the right chassis 708 auto endpoints = std::get_if<std::vector<std::string>>( 709 &variantEndpoints); 710 711 if (endpoints == nullptr) 712 { 713 BMCWEB_LOG_ERROR << "Invalid association interface"; 714 messages::internalError(sensorsAsyncResp->res); 715 return; 716 } 717 718 auto found = std::find_if( 719 endpoints->begin(), endpoints->end(), 720 [sensorsAsyncResp](const std::string& entry) { 721 return entry.find( 722 sensorsAsyncResp->chassisId) != 723 std::string::npos; 724 }); 725 726 if (found == endpoints->end()) 727 { 728 return; 729 } 730 crow::connections::systemBus->async_method_call( 731 [path, sensorsAsyncResp]( 732 const boost::system::error_code ec, 733 const boost::container::flat_map< 734 std::string, 735 std::variant<uint8_t, 736 std::vector<std::string>, 737 std::string>>& ret) { 738 if (ec) 739 { 740 return; // don't have to have this 741 // interface 742 } 743 auto findFailures = ret.find("AllowedFailures"); 744 auto findCollection = ret.find("Collection"); 745 auto findStatus = ret.find("Status"); 746 747 if (findFailures == ret.end() || 748 findCollection == ret.end() || 749 findStatus == ret.end()) 750 { 751 BMCWEB_LOG_ERROR 752 << "Invalid redundancy interface"; 753 messages::internalError( 754 sensorsAsyncResp->res); 755 return; 756 } 757 758 auto allowedFailures = std::get_if<uint8_t>( 759 &(findFailures->second)); 760 auto collection = 761 std::get_if<std::vector<std::string>>( 762 &(findCollection->second)); 763 auto status = std::get_if<std::string>( 764 &(findStatus->second)); 765 766 if (allowedFailures == nullptr || 767 collection == nullptr || status == nullptr) 768 { 769 770 BMCWEB_LOG_ERROR 771 << "Invalid redundancy interface " 772 "types"; 773 messages::internalError( 774 sensorsAsyncResp->res); 775 return; 776 } 777 size_t lastSlash = path.rfind("/"); 778 if (lastSlash == std::string::npos) 779 { 780 // this should be impossible 781 messages::internalError( 782 sensorsAsyncResp->res); 783 return; 784 } 785 std::string name = path.substr(lastSlash + 1); 786 std::replace(name.begin(), name.end(), '_', 787 ' '); 788 789 std::string health; 790 791 if (boost::ends_with(*status, "Full")) 792 { 793 health = "OK"; 794 } 795 else if (boost::ends_with(*status, "Degraded")) 796 { 797 health = "Warning"; 798 } 799 else 800 { 801 health = "Critical"; 802 } 803 std::vector<nlohmann::json> redfishCollection; 804 const auto& fanRedfish = 805 sensorsAsyncResp->res.jsonValue["Fans"]; 806 for (const std::string& item : *collection) 807 { 808 lastSlash = item.rfind("/"); 809 // make a copy as collection is const 810 std::string itemName = 811 item.substr(lastSlash + 1); 812 /* 813 todo(ed): merge patch that fixes the names 814 std::replace(itemName.begin(), 815 itemName.end(), '_', ' ');*/ 816 auto schemaItem = std::find_if( 817 fanRedfish.begin(), fanRedfish.end(), 818 [itemName](const nlohmann::json& fan) { 819 return fan["MemberId"] == itemName; 820 }); 821 if (schemaItem != fanRedfish.end()) 822 { 823 redfishCollection.push_back( 824 {{"@odata.id", 825 (*schemaItem)["@odata.id"]}}); 826 } 827 else 828 { 829 BMCWEB_LOG_ERROR 830 << "failed to find fan in schema"; 831 messages::internalError( 832 sensorsAsyncResp->res); 833 return; 834 } 835 } 836 837 auto& resp = sensorsAsyncResp->res 838 .jsonValue["Redundancy"]; 839 resp.push_back( 840 {{"@odata.id", 841 "/refish/v1/Chassis/" + 842 sensorsAsyncResp->chassisId + "/" + 843 sensorsAsyncResp->chassisSubNode + 844 "#/Redundancy/" + 845 std::to_string(resp.size())}, 846 {"@odata.type", 847 "#Redundancy.v1_3_2.Redundancy"}, 848 {"MinNumNeeded", 849 collection->size() - *allowedFailures}, 850 {"MemberId", name}, 851 {"Mode", "N+m"}, 852 {"Name", name}, 853 {"RedundancySet", redfishCollection}, 854 {"Status", 855 {{"Health", health}, 856 {"State", "Enabled"}}}}); 857 }, 858 owner, path, "org.freedesktop.DBus.Properties", 859 "GetAll", 860 "xyz.openbmc_project.Control.FanRedundancy"); 861 }, 862 "xyz.openbmc_project.ObjectMapper", path + "/inventory", 863 "org.freedesktop.DBus.Properties", "Get", 864 "xyz.openbmc_project.Association", "endpoints"); 865 } 866 }, 867 "xyz.openbmc_project.ObjectMapper", 868 "/xyz/openbmc_project/object_mapper", 869 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 870 "/xyz/openbmc_project/control", 2, 871 std::array<const char*, 1>{ 872 "xyz.openbmc_project.Control.FanRedundancy"}); 873 } 874 875 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 876 { 877 nlohmann::json& response = SensorsAsyncResp->res.jsonValue; 878 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 879 if (SensorsAsyncResp->chassisSubNode == "Power") 880 { 881 sensorHeaders = {"Voltages", "PowerSupplies"}; 882 } 883 for (const std::string& sensorGroup : sensorHeaders) 884 { 885 nlohmann::json::iterator entry = response.find(sensorGroup); 886 if (entry != response.end()) 887 { 888 std::sort(entry->begin(), entry->end(), 889 [](nlohmann::json& c1, nlohmann::json& c2) { 890 return c1["Name"] < c2["Name"]; 891 }); 892 893 // add the index counts to the end of each entry 894 size_t count = 0; 895 for (nlohmann::json& sensorJson : *entry) 896 { 897 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 898 if (odata == sensorJson.end()) 899 { 900 continue; 901 } 902 std::string* value = odata->get_ptr<std::string*>(); 903 if (value != nullptr) 904 { 905 *value += std::to_string(count); 906 count++; 907 } 908 } 909 } 910 } 911 } 912 913 /** 914 * @brief Gets the values of the specified sensors. 915 * 916 * Stores the results as JSON in the SensorsAsyncResp. 917 * 918 * Gets the sensor values asynchronously. Stores the results later when the 919 * information has been obtained. 920 * 921 * The sensorNames set contains all sensors for the current chassis. 922 * SensorsAsyncResp contains the requested sensor types. Only sensors of a 923 * requested type are included in the JSON output. 924 * 925 * To minimize the number of DBus calls, the DBus method 926 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 927 * values of all sensors provided by a connection (service). 928 * 929 * The connections set contains all the connections that provide sensor values. 930 * 931 * The objectMgrPaths map contains mappings from a connection name to the 932 * corresponding DBus object path that implements ObjectManager. 933 * 934 * @param SensorsAsyncResp Pointer to object holding response data. 935 * @param sensorNames All sensors within the current chassis. 936 * @param connections Connections that provide sensor values. 937 * @param objectMgrPaths Mappings from connection name to DBus object path that 938 * implements ObjectManager. 939 */ 940 void getSensorData( 941 std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp, 942 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 943 const boost::container::flat_set<std::string>& connections, 944 const boost::container::flat_map<std::string, std::string>& objectMgrPaths) 945 { 946 BMCWEB_LOG_DEBUG << "getSensorData enter"; 947 // Get managed objects from all services exposing sensors 948 for (const std::string& connection : connections) 949 { 950 // Response handler to process managed objects 951 auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames]( 952 const boost::system::error_code ec, 953 ManagedObjectsVectorType& resp) { 954 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 955 if (ec) 956 { 957 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 958 messages::internalError(SensorsAsyncResp->res); 959 return; 960 } 961 // Go through all objects and update response with sensor data 962 for (const auto& objDictEntry : resp) 963 { 964 const std::string& objPath = 965 static_cast<const std::string&>(objDictEntry.first); 966 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 967 << objPath; 968 969 std::vector<std::string> split; 970 // Reserve space for 971 // /xyz/openbmc_project/sensors/<name>/<subname> 972 split.reserve(6); 973 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 974 if (split.size() < 6) 975 { 976 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 977 << objPath; 978 continue; 979 } 980 // These indexes aren't intuitive, as boost::split puts an empty 981 // string at the beginning 982 const std::string& sensorType = split[4]; 983 const std::string& sensorName = split[5]; 984 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 985 << " sensorType " << sensorType; 986 if (sensorNames->find(objPath) == sensorNames->end()) 987 { 988 BMCWEB_LOG_ERROR << sensorName << " not in sensor list "; 989 continue; 990 } 991 992 const char* fieldName = nullptr; 993 if (sensorType == "temperature") 994 { 995 fieldName = "Temperatures"; 996 } 997 else if (sensorType == "fan" || sensorType == "fan_tach" || 998 sensorType == "fan_pwm") 999 { 1000 fieldName = "Fans"; 1001 } 1002 else if (sensorType == "voltage") 1003 { 1004 fieldName = "Voltages"; 1005 } 1006 else if (sensorType == "current") 1007 { 1008 fieldName = "PowerSupplies"; 1009 } 1010 else if (sensorType == "power") 1011 { 1012 if (!sensorName.compare("total_power")) 1013 { 1014 fieldName = "PowerControl"; 1015 } 1016 else 1017 { 1018 fieldName = "PowerSupplies"; 1019 } 1020 } 1021 else 1022 { 1023 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 1024 << sensorType; 1025 continue; 1026 } 1027 1028 nlohmann::json& tempArray = 1029 SensorsAsyncResp->res.jsonValue[fieldName]; 1030 1031 if (fieldName == "PowerSupplies" && !tempArray.empty()) 1032 { 1033 // Power supplies put multiple "sensors" into a single power 1034 // supply entry, so only create the first one 1035 } 1036 else 1037 { 1038 tempArray.push_back( 1039 {{"@odata.id", "/redfish/v1/Chassis/" + 1040 SensorsAsyncResp->chassisId + "/" + 1041 SensorsAsyncResp->chassisSubNode + 1042 "#/" + fieldName + "/"}}); 1043 } 1044 nlohmann::json& sensorJson = tempArray.back(); 1045 1046 objectInterfacesToJson(sensorName, sensorType, 1047 objDictEntry.second, sensorJson); 1048 } 1049 if (SensorsAsyncResp.use_count() == 1) 1050 { 1051 sortJSONResponse(SensorsAsyncResp); 1052 if (SensorsAsyncResp->chassisSubNode == "Thermal") 1053 { 1054 populateFanRedundancy(SensorsAsyncResp); 1055 } 1056 } 1057 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 1058 }; 1059 1060 // Find DBus object path that implements ObjectManager for the current 1061 // connection. If no mapping found, default to "/". 1062 auto iter = objectMgrPaths.find(connection); 1063 const std::string& objectMgrPath = 1064 (iter != objectMgrPaths.end()) ? iter->second : "/"; 1065 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1066 << objectMgrPath; 1067 1068 crow::connections::systemBus->async_method_call( 1069 getManagedObjectsCb, connection, objectMgrPath, 1070 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1071 }; 1072 BMCWEB_LOG_DEBUG << "getSensorData exit"; 1073 } 1074 1075 /** 1076 * @brief Entry point for retrieving sensors data related to requested 1077 * chassis. 1078 * @param SensorsAsyncResp Pointer to object holding response data 1079 */ 1080 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) 1081 { 1082 BMCWEB_LOG_DEBUG << "getChassisData enter"; 1083 auto getChassisCb = 1084 [SensorsAsyncResp]( 1085 std::shared_ptr<boost::container::flat_set<std::string>> 1086 sensorNames) { 1087 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 1088 auto getConnectionCb = 1089 [SensorsAsyncResp, 1090 sensorNames](const boost::container::flat_set<std::string>& 1091 connections) { 1092 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 1093 auto getObjectManagerPathsCb = 1094 [SensorsAsyncResp, sensorNames, connections]( 1095 const boost::container::flat_map< 1096 std::string, std::string>& objectMgrPaths) { 1097 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 1098 // Get sensor data and store results in JSON 1099 // response 1100 getSensorData(SensorsAsyncResp, sensorNames, 1101 connections, objectMgrPaths); 1102 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 1103 }; 1104 1105 // Get mapping from connection names to the DBus object 1106 // paths that implement the ObjectManager interface 1107 getObjectManagerPaths(SensorsAsyncResp, 1108 std::move(getObjectManagerPathsCb)); 1109 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 1110 }; 1111 1112 // Get set of connections that provide sensor values 1113 getConnections(SensorsAsyncResp, sensorNames, 1114 std::move(getConnectionCb)); 1115 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 1116 }; 1117 SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array(); 1118 1119 // Get set of sensors in chassis 1120 getChassis(SensorsAsyncResp, std::move(getChassisCb)); 1121 BMCWEB_LOG_DEBUG << "getChassisData exit"; 1122 }; 1123 1124 /** 1125 * @brief Find the requested sensorName in the list of all sensors supplied by 1126 * the chassis node 1127 * 1128 * @param sensorName The sensor name supplied in the PATCH request 1129 * @param sensorsList The list of sensors managed by the chassis node 1130 * @param sensorsModified The list of sensors that were found as a result of 1131 * repeated calls to this function 1132 */ 1133 bool findSensorNameUsingSensorPath( 1134 std::string_view sensorName, 1135 boost::container::flat_set<std::string>& sensorsList, 1136 boost::container::flat_set<std::string>& sensorsModified) 1137 { 1138 for (std::string_view chassisSensor : sensorsList) 1139 { 1140 std::size_t pos = chassisSensor.rfind("/"); 1141 if (pos >= (chassisSensor.size() - 1)) 1142 { 1143 continue; 1144 } 1145 std::string_view thisSensorName = chassisSensor.substr(pos + 1); 1146 if (thisSensorName == sensorName) 1147 { 1148 sensorsModified.emplace(chassisSensor); 1149 return true; 1150 } 1151 } 1152 return false; 1153 } 1154 1155 /** 1156 * @brief Entry point for overriding sensor values of given sensor 1157 * 1158 * @param res response object 1159 * @param req request object 1160 * @param params parameter passed for CRUD 1161 * @param typeList TypeList of sensors for the resource queried 1162 * @param chassisSubNode Chassis Node for which the query has to happen 1163 */ 1164 void setSensorOverride(crow::Response& res, const crow::Request& req, 1165 const std::vector<std::string>& params, 1166 const std::initializer_list<const char*> typeList, 1167 const std::string& chassisSubNode) 1168 { 1169 1170 // TODO: Need to figure out dynamic way to restrict patch (Set Sensor 1171 // override) based on another d-bus announcement to be more generic. 1172 if (params.size() != 1) 1173 { 1174 messages::internalError(res); 1175 res.end(); 1176 return; 1177 } 1178 1179 std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections; 1180 std::optional<std::vector<nlohmann::json>> temperatureCollections; 1181 std::optional<std::vector<nlohmann::json>> fanCollections; 1182 std::vector<nlohmann::json> voltageCollections; 1183 BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode 1184 << "\n"; 1185 1186 if (chassisSubNode == "Thermal") 1187 { 1188 if (!json_util::readJson(req, res, "Temperatures", 1189 temperatureCollections, "Fans", 1190 fanCollections)) 1191 { 1192 return; 1193 } 1194 if (!temperatureCollections && !fanCollections) 1195 { 1196 messages::resourceNotFound(res, "Thermal", 1197 "Temperatures / Voltages"); 1198 res.end(); 1199 return; 1200 } 1201 if (temperatureCollections) 1202 { 1203 allCollections.emplace("Temperatures", 1204 *std::move(temperatureCollections)); 1205 } 1206 if (fanCollections) 1207 { 1208 allCollections.emplace("Fans", *std::move(fanCollections)); 1209 } 1210 } 1211 else if (chassisSubNode == "Power") 1212 { 1213 if (!json_util::readJson(req, res, "Voltages", voltageCollections)) 1214 { 1215 return; 1216 } 1217 allCollections.emplace("Voltages", std::move(voltageCollections)); 1218 } 1219 else 1220 { 1221 res.result(boost::beast::http::status::not_found); 1222 res.end(); 1223 return; 1224 } 1225 1226 const char* propertyValueName; 1227 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 1228 std::string memberId; 1229 double value; 1230 for (auto& collectionItems : allCollections) 1231 { 1232 if (collectionItems.first == "Temperatures") 1233 { 1234 propertyValueName = "ReadingCelsius"; 1235 } 1236 else if (collectionItems.first == "Fans") 1237 { 1238 propertyValueName = "Reading"; 1239 } 1240 else 1241 { 1242 propertyValueName = "ReadingVolts"; 1243 } 1244 for (auto& item : collectionItems.second) 1245 { 1246 if (!json_util::readJson(item, res, "MemberId", memberId, 1247 propertyValueName, value)) 1248 { 1249 return; 1250 } 1251 overrideMap.emplace(memberId, 1252 std::make_pair(value, collectionItems.first)); 1253 } 1254 } 1255 const std::string& chassisName = params[0]; 1256 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 1257 res, chassisName, typeList, chassisSubNode); 1258 auto getChassisSensorListCb = [sensorAsyncResp, 1259 overrideMap](const std::shared_ptr< 1260 boost::container::flat_set< 1261 std::string>> 1262 sensorsList) { 1263 // Match sensor names in the PATCH request to those managed by the 1264 // chassis node 1265 const std::shared_ptr<boost::container::flat_set<std::string>> 1266 sensorNames = 1267 std::make_shared<boost::container::flat_set<std::string>>(); 1268 for (const auto& item : overrideMap) 1269 { 1270 const auto& sensor = item.first; 1271 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 1272 *sensorNames)) 1273 { 1274 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 1275 messages::resourceNotFound(sensorAsyncResp->res, 1276 item.second.second, item.first); 1277 return; 1278 } 1279 } 1280 // Get the connection to which the memberId belongs 1281 auto getObjectsWithConnectionCb = 1282 [sensorAsyncResp, overrideMap]( 1283 const boost::container::flat_set<std::string>& connections, 1284 const std::set<std::pair<std::string, std::string>>& 1285 objectsWithConnection) { 1286 if (objectsWithConnection.size() != overrideMap.size()) 1287 { 1288 BMCWEB_LOG_INFO 1289 << "Unable to find all objects with proper connection " 1290 << objectsWithConnection.size() << " requested " 1291 << overrideMap.size() << "\n"; 1292 messages::resourceNotFound( 1293 sensorAsyncResp->res, 1294 sensorAsyncResp->chassisSubNode == "Thermal" 1295 ? "Temperatures" 1296 : "Voltages", 1297 "Count"); 1298 return; 1299 } 1300 for (const auto& item : objectsWithConnection) 1301 { 1302 1303 auto lastPos = item.first.rfind('/'); 1304 if (lastPos == std::string::npos) 1305 { 1306 messages::internalError(sensorAsyncResp->res); 1307 return; 1308 } 1309 std::string sensorName = item.first.substr(lastPos + 1); 1310 1311 const auto& iterator = overrideMap.find(sensorName); 1312 if (iterator == overrideMap.end()) 1313 { 1314 BMCWEB_LOG_INFO << "Unable to find sensor object" 1315 << item.first << "\n"; 1316 messages::internalError(sensorAsyncResp->res); 1317 return; 1318 } 1319 crow::connections::systemBus->async_method_call( 1320 [sensorAsyncResp](const boost::system::error_code ec) { 1321 if (ec) 1322 { 1323 BMCWEB_LOG_DEBUG 1324 << "setOverrideValueStatus DBUS error: " 1325 << ec; 1326 messages::internalError(sensorAsyncResp->res); 1327 return; 1328 } 1329 }, 1330 item.second, item.first, 1331 "org.freedesktop.DBus.Properties", "Set", 1332 "xyz.openbmc_project.Sensor.Value", "Value", 1333 sdbusplus::message::variant<double>( 1334 iterator->second.first)); 1335 } 1336 }; 1337 // Get object with connection for the given sensor name 1338 getObjectsWithConnection(sensorAsyncResp, sensorNames, 1339 std::move(getObjectsWithConnectionCb)); 1340 }; 1341 // get full sensor list for the given chassisId and cross verify the sensor. 1342 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 1343 } 1344 1345 } // namespace redfish 1346