From 95a3ecada41219555c5efe4080d6589dc2aaa411 Mon Sep 17 00:00:00 2001 From: Anthony Wilson Date: Tue, 11 Jun 2019 10:44:47 -0500 Subject: bmcweb: Implement SensorCollection Add collection of all power and current sensors. Testing: Verified SensorCollection and Sensor output on a Witherspoon system. Verified no errors from RedfishServiceValidator. Change-Id: Icfdc14d738bf037d5d599a3c6fc0be5ea0919929 Signed-off-by: Anthony Wilson --- redfish-core/include/redfish.hpp | 4 + redfish-core/lib/chassis.hpp | 8 +- redfish-core/lib/sensors.hpp | 463 ++++++++++++++++++++++++++++++--------- 3 files changed, 365 insertions(+), 110 deletions(-) diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index ec6d0d7..2ca3dc7 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -28,6 +28,7 @@ #include "../lib/power.hpp" #include "../lib/redfish_sessions.hpp" #include "../lib/roles.hpp" +#include "../lib/sensors.hpp" #include "../lib/service_root.hpp" #include "../lib/systems.hpp" #include "../lib/thermal.hpp" @@ -140,6 +141,9 @@ class RedfishService nodes.emplace_back(std::make_unique(app)); nodes.emplace_back(std::make_unique(app)); nodes.emplace_back(std::make_unique(app)); + + nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back(std::make_unique(app)); for (const auto& node : nodes) { node->initPrivileges(); diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp index 8553b28..8c13414 100644 --- a/redfish-core/lib/chassis.hpp +++ b/redfish-core/lib/chassis.hpp @@ -323,7 +323,7 @@ class Chassis : public Node } asyncResp->res.jsonValue["@odata.type"] = - "#Chassis.v1_4_0.Chassis"; + "#Chassis.v1_9_0.Chassis"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis/" + chassisId; asyncResp->res.jsonValue["@odata.context"] = @@ -369,6 +369,10 @@ class Chassis : public Node asyncResp->res.jsonValue["Power"] = { {"@odata.id", "/redfish/v1/Chassis/" + chassisId + "/Power"}}; + // SensorCollection + asyncResp->res.jsonValue["Sensors"] = { + {"@odata.id", "/redfish/v1/Chassis/" + + chassisId + "/Sensors"}}; asyncResp->res.jsonValue["Status"] = { {"State", "Enabled"}, }; @@ -388,7 +392,7 @@ class Chassis : public Node // Couldn't find an object with that name. return an error messages::resourceNotFound( - asyncResp->res, "#Chassis.v1_4_0.Chassis", chassisId); + asyncResp->res, "#Chassis.v1_9_0.Chassis", chassisId); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp index 3fd86d6..eaf180d 100644 --- a/redfish-core/lib/sensors.hpp +++ b/redfish-core/lib/sensors.hpp @@ -15,6 +15,8 @@ */ #pragma once +#include "node.hpp" + #include #include @@ -308,13 +310,30 @@ void getChassis(std::shared_ptr sensorsAsyncResp, sensorsAsyncResp->res.jsonValue["Temperatures"] = nlohmann::json::array(); } + else if (chassisSubNode == "Sensors") + { + sensorsAsyncResp->res.jsonValue["@odata.type"] = + "#SensorCollection.SensorCollection"; + sensorsAsyncResp->res.jsonValue["@odata.context"] = + "/redfish/v1/$metadata#SensorCollection.SensorCollection"; + sensorsAsyncResp->res.jsonValue["Description"] = + "Collection of Sensors for this Chassis"; + sensorsAsyncResp->res.jsonValue["Members"] = + nlohmann::json::array(); + sensorsAsyncResp->res.jsonValue["Members@odata.count"] = 0; + } + + if (chassisSubNode != "Sensors") + { + sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode; + sensorsAsyncResp->res.jsonValue["@odata.context"] = + "/redfish/v1/$metadata#" + chassisSubNode + "." + + chassisSubNode; + } + sensorsAsyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + chassisSubNode; - - sensorsAsyncResp->res.jsonValue["@odata.context"] = - "/redfish/v1/$metadata#" + chassisSubNode + "." + chassisSubNode; - sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode; sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode; // Get the list of all sensors for this Chassis element @@ -340,7 +359,9 @@ void getChassis(std::shared_ptr sensorsAsyncResp, sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures" - : "Voltages"); + : sensorsAsyncResp->chassisSubNode == "Power" + ? "Voltages" + : "Sensors"); return; } const std::shared_ptr> @@ -582,6 +603,8 @@ static std::string getHealth( * @param sensorName The name of the sensor to be built * @param sensorType The type (temperature, fan_tach, etc) of the sensor to * build + * @param sensorSchema The schema (Power, Thermal, etc) being associated with + * the sensor to build * @param interfacesDict A dictionary of the interfaces and properties of said * interfaces to be built from * @param sensor_json The json object to fill @@ -590,6 +613,7 @@ static std::string getHealth( */ void objectInterfacesToJson( const std::string& sensorName, const std::string& sensorType, + const std::string& sensorSchema, const boost::container::flat_map< std::string, boost::container::flat_map>& interfacesDict, @@ -617,11 +641,18 @@ void objectInterfacesToJson( } } - // Set MemberId and Name for non-power sensors. For PowerSupplies and - // PowerControl, those properties have more general values because multiple - // sensors can be stored in the same JSON object. - if (sensorType != "power") + if (sensorSchema == "Sensors") + { + // For sensors in SensorCollection we set Id instead of MemberId, + // including power sensors. + sensor_json["Id"] = sensorName; + sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " "); + } + else if (sensorType != "power") { + // Set MemberId and Name for non-power sensors. For PowerSupplies and + // PowerControl, those properties have more general values because + // multiple sensors can be stored in the same JSON object. sensor_json["MemberId"] = sensorName; sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " "); } @@ -636,7 +667,20 @@ void objectInterfacesToJson( bool forceToInt = false; const char* unit = "Reading"; - if (sensorType == "temperature") + if (sensorSchema == "Sensors") + { + sensor_json["@odata.type"] = "#Sensor.v1_0_0.Sensor"; + sensor_json["@odata.context"] = "/redfish/v1/$metadata#Sensor.Sensor"; + if (sensorType == "power") + { + sensor_json["ReadingUnits"] = "Watts"; + } + else if (sensorType == "current") + { + sensor_json["ReadingUnits"] = "Amperes"; + } + } + else if (sensorType == "temperature") { unit = "ReadingCelsius"; sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature"; @@ -696,7 +740,7 @@ void objectInterfacesToJson( properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); - if (sensorType != "power") + if (sensorType != "power" && sensorSchema != "Sensors") { properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", "UpperThresholdNonCritical"); @@ -710,7 +754,14 @@ void objectInterfacesToJson( // TODO Need to get UpperThresholdFatal and LowerThresholdFatal - if (sensorType == "temperature") + if (sensorSchema == "Sensors") + { + properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", + "ReadingRangeMin"); + properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", + "ReadingRangeMax"); + } + else if (sensorType == "temperature") { properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", "MinReadingRangeTemp"); @@ -1709,85 +1760,102 @@ void getSensorData( InventoryItem* inventoryItem = findInventoryItemForSensor(inventoryItems, objPath); - const char* fieldName = nullptr; - if (sensorType == "temperature") - { - fieldName = "Temperatures"; - } - else if (sensorType == "fan" || sensorType == "fan_tach" || - sensorType == "fan_pwm") - { - fieldName = "Fans"; - } - else if (sensorType == "voltage") + const std::string& sensorSchema = + SensorsAsyncResp->chassisSubNode; + + nlohmann::json* sensorJson = nullptr; + + if (sensorSchema == "Sensors") { - fieldName = "Voltages"; + SensorsAsyncResp->res.jsonValue["@odata.id"] = + "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId + + "/" + SensorsAsyncResp->chassisSubNode + "/" + + sensorName; + sensorJson = &(SensorsAsyncResp->res.jsonValue); } - else if (sensorType == "power") + else { - if (!sensorName.compare("total_power")) + const char* fieldName = nullptr; + if (sensorType == "temperature") + { + fieldName = "Temperatures"; + } + else if (sensorType == "fan" || sensorType == "fan_tach" || + sensorType == "fan_pwm") { - fieldName = "PowerControl"; + fieldName = "Fans"; } - else if ((inventoryItem != nullptr) && - (inventoryItem->isPowerSupply)) + else if (sensorType == "voltage") { - fieldName = "PowerSupplies"; + fieldName = "Voltages"; + } + else if (sensorType == "power") + { + if (!sensorName.compare("total_power")) + { + fieldName = "PowerControl"; + } + else if ((inventoryItem != nullptr) && + (inventoryItem->isPowerSupply)) + { + fieldName = "PowerSupplies"; + } + else + { + // Other power sensors are in SensorCollection + continue; + } } else { - // Other power sensors are in SensorCollection + BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " + << sensorType; continue; } - } - else - { - BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " - << sensorType; - continue; - } - nlohmann::json& tempArray = - SensorsAsyncResp->res.jsonValue[fieldName]; - nlohmann::json* sensorJson = nullptr; - - if (fieldName == "PowerControl") - { - if (tempArray.empty()) + nlohmann::json& tempArray = + SensorsAsyncResp->res.jsonValue[fieldName]; + if (fieldName == "PowerControl") + { + if (tempArray.empty()) + { + // Put multiple "sensors" into a single + // PowerControl. Follows MemberId naming and + // naming in power.hpp. + tempArray.push_back( + {{"@odata.id", + "/redfish/v1/Chassis/" + + SensorsAsyncResp->chassisId + "/" + + SensorsAsyncResp->chassisSubNode + "#/" + + fieldName + "/0"}}); + } + sensorJson = &(tempArray.back()); + } + else if (fieldName == "PowerSupplies") + { + if (inventoryItem != nullptr) + { + sensorJson = + &(getPowerSupply(tempArray, *inventoryItem, + SensorsAsyncResp->chassisId)); + } + } + else { - // Put multiple "sensors" into a single PowerControl. - // Follows MemberId naming and naming in power.hpp. tempArray.push_back( {{"@odata.id", "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId + "/" + SensorsAsyncResp->chassisSubNode + "#/" + - fieldName + "/0"}}); + fieldName + "/"}}); + sensorJson = &(tempArray.back()); } - sensorJson = &(tempArray.back()); - } - else if (fieldName == "PowerSupplies") - { - if (inventoryItem != nullptr) - { - sensorJson = - &(getPowerSupply(tempArray, *inventoryItem, - SensorsAsyncResp->chassisId)); - } - } - else - { - tempArray.push_back( - {{"@odata.id", "/redfish/v1/Chassis/" + - SensorsAsyncResp->chassisId + "/" + - SensorsAsyncResp->chassisSubNode + - "#/" + fieldName + "/"}}); - sensorJson = &(tempArray.back()); } if (sensorJson != nullptr) { objectInterfacesToJson(sensorName, sensorType, + SensorsAsyncResp->chassisSubNode, objDictEntry.second, *sensorJson, inventoryItem); } @@ -1818,6 +1886,52 @@ void getSensorData( BMCWEB_LOG_DEBUG << "getSensorData exit"; } +void processSensorList( + std::shared_ptr SensorsAsyncResp, + std::shared_ptr> sensorNames) +{ + auto getConnectionCb = + [SensorsAsyncResp, sensorNames]( + const boost::container::flat_set& connections) { + BMCWEB_LOG_DEBUG << "getConnectionCb enter"; + auto getObjectManagerPathsCb = + [SensorsAsyncResp, sensorNames, connections]( + std::shared_ptr< + boost::container::flat_map> + objectMgrPaths) { + BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; + auto getInventoryItemsCb = + [SensorsAsyncResp, sensorNames, connections, + objectMgrPaths]( + std::shared_ptr> + inventoryItems) { + BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; + // Get sensor data and store results in JSON + getSensorData(SensorsAsyncResp, sensorNames, + connections, objectMgrPaths, + inventoryItems); + BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; + }; + + // Get inventory items associated with sensors + getInventoryItems(SensorsAsyncResp, sensorNames, + objectMgrPaths, + std::move(getInventoryItemsCb)); + + BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; + }; + + // Get mapping from connection names to the DBus object + // paths that implement the ObjectManager interface + getObjectManagerPaths(SensorsAsyncResp, + std::move(getObjectManagerPathsCb)); + BMCWEB_LOG_DEBUG << "getConnectionCb exit"; + }; + + // Get set of connections that provide sensor values + getConnections(SensorsAsyncResp, sensorNames, std::move(getConnectionCb)); +} + /** * @brief Entry point for retrieving sensors data related to requested * chassis. @@ -1831,47 +1945,7 @@ void getChassisData(std::shared_ptr SensorsAsyncResp) std::shared_ptr> sensorNames) { BMCWEB_LOG_DEBUG << "getChassisCb enter"; - auto getConnectionCb = [SensorsAsyncResp, sensorNames]( - const boost::container::flat_set< - std::string>& connections) { - BMCWEB_LOG_DEBUG << "getConnectionCb enter"; - auto getObjectManagerPathsCb = - [SensorsAsyncResp, sensorNames, connections]( - std::shared_ptr> - objectMgrPaths) { - BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; - auto getInventoryItemsCb = - [SensorsAsyncResp, sensorNames, connections, - objectMgrPaths]( - std::shared_ptr> - inventoryItems) { - BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; - // Get sensor data and store results in JSON - getSensorData(SensorsAsyncResp, sensorNames, - connections, objectMgrPaths, - inventoryItems); - BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; - }; - - // Get inventory items associated with sensors - getInventoryItems(SensorsAsyncResp, sensorNames, - objectMgrPaths, - std::move(getInventoryItemsCb)); - - BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; - }; - - // Get mapping from connection names to the DBus object - // paths that implement the ObjectManager interface - getObjectManagerPaths(SensorsAsyncResp, - std::move(getObjectManagerPathsCb)); - BMCWEB_LOG_DEBUG << "getConnectionCb exit"; - }; - - // Get set of connections that provide sensor values - getConnections(SensorsAsyncResp, sensorNames, - std::move(getConnectionCb)); + processSensorList(SensorsAsyncResp, sensorNames); BMCWEB_LOG_DEBUG << "getChassisCb exit"; }; SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array(); @@ -2102,4 +2176,177 @@ void setSensorOverride(crow::Response& res, const crow::Request& req, getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); } +class SensorCollection : public Node +{ + public: + SensorCollection(CrowApp& app) : + Node(app, "/redfish/v1/Chassis//Sensors", std::string()) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + std::vector typeList = { + "/xyz/openbmc_project/sensors/power", + "/xyz/openbmc_project/sensors/current"}; + void doGet(crow::Response& res, const crow::Request& req, + const std::vector& params) override + { + BMCWEB_LOG_DEBUG << "SensorCollection doGet enter"; + if (params.size() != 1) + { + BMCWEB_LOG_DEBUG << "SensorCollection doGet param size < 1"; + messages::internalError(res); + res.end(); + return; + } + + const std::string& chassisId = params[0]; + std::shared_ptr asyncResp = + std::make_shared(res, chassisId, typeList, + "Sensors"); + + auto getChassisCb = + [asyncResp](std::shared_ptr> + sensorNames) { + BMCWEB_LOG_DEBUG << "getChassisCb enter"; + + nlohmann::json& entriesArray = + asyncResp->res.jsonValue["Members"]; + for (auto& sensor : *sensorNames) + { + BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; + + std::size_t lastPos = sensor.rfind("/"); + if (lastPos == std::string::npos || + lastPos + 1 >= sensor.size()) + { + BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor; + messages::internalError(asyncResp->res); + return; + } + std::string sensorName = sensor.substr(lastPos + 1); + entriesArray.push_back( + {{"@odata.id", + "/redfish/v1/Chassis/" + asyncResp->chassisId + "/" + + asyncResp->chassisSubNode + "/" + sensorName}}); + } + + asyncResp->res.jsonValue["Members@odata.count"] = + entriesArray.size(); + BMCWEB_LOG_DEBUG << "getChassisCb exit"; + }; + + // Get set of sensors in chassis + getChassis(asyncResp, std::move(getChassisCb)); + BMCWEB_LOG_DEBUG << "SensorCollection doGet exit"; + } +}; + +class Sensor : public Node +{ + public: + Sensor(CrowApp& app) : + Node(app, "/redfish/v1/Chassis//Sensors//", std::string(), + std::string()) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector& params) override + { + BMCWEB_LOG_DEBUG << "Sensor doGet enter"; + if (params.size() != 2) + { + BMCWEB_LOG_DEBUG << "Sensor doGet param size < 2"; + messages::internalError(res); + res.end(); + return; + } + const std::string& chassisId = params[0]; + std::shared_ptr asyncResp = + std::make_shared( + res, chassisId, std::vector(), "Sensors"); + + const std::string& sensorName = params[1]; + const std::array interfaces = { + "xyz.openbmc_project.Sensor.Value"}; + + // Get a list of all of the sensors that implement Sensor.Value + // and get the path and service name associated with the sensor + crow::connections::systemBus->async_method_call( + [asyncResp, sensorName](const boost::system::error_code ec, + const GetSubTreeType& subtree) { + BMCWEB_LOG_DEBUG << "respHandler1 enter"; + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR << "Sensor getSensorPaths resp_handler: " + << "Dbus error " << ec; + return; + } + + GetSubTreeType::const_iterator it = std::find_if( + subtree.begin(), subtree.end(), + [sensorName]( + const std::pair< + std::string, + std::vector>>>& + object) { + std::string_view sensor = object.first; + std::size_t lastPos = sensor.rfind("/"); + if (lastPos == std::string::npos || + lastPos + 1 >= sensor.size()) + { + BMCWEB_LOG_ERROR << "Invalid sensor path: " + << sensor; + return false; + } + std::string_view name = sensor.substr(lastPos + 1); + + return name == sensorName; + }); + + if (it == subtree.end()) + { + BMCWEB_LOG_ERROR << "Could not find path for sensor: " + << sensorName; + messages::resourceNotFound(asyncResp->res, "Sensor", + sensorName); + return; + } + std::string_view sensorPath = (*it).first; + BMCWEB_LOG_DEBUG << "Found sensor path for sensor '" + << sensorName << "': " << sensorPath; + + const std::shared_ptr> + sensorList = std::make_shared< + boost::container::flat_set>(); + + sensorList->emplace(sensorPath); + processSensorList(asyncResp, sensorList); + BMCWEB_LOG_DEBUG << "respHandler1 exit"; + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", + "/xyz/openbmc_project/sensors", 2, interfaces); + } +}; + } // namespace redfish -- cgit v1.2.3