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 <wilsonan@us.ibm.com>
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 <math.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -308,13 +310,30 @@
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 @@
sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode,
sensorsAsyncResp->chassisSubNode == "Thermal"
? "Temperatures"
- : "Voltages");
+ : sensorsAsyncResp->chassisSubNode == "Power"
+ ? "Voltages"
+ : "Sensors");
return;
}
const std::shared_ptr<boost::container::flat_set<std::string>>
@@ -582,6 +603,8 @@
* @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 @@
*/
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<std::string, SensorVariant>>&
interfacesDict,
@@ -617,11 +641,18 @@
}
}
- // 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 @@
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 @@
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 @@
// 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 @@
InventoryItem* inventoryItem =
findInventoryItemForSensor(inventoryItems, objPath);
- const char* fieldName = nullptr;
- if (sensorType == "temperature")
+ const std::string& sensorSchema =
+ SensorsAsyncResp->chassisSubNode;
+
+ nlohmann::json* sensorJson = nullptr;
+
+ if (sensorSchema == "Sensors")
{
- fieldName = "Temperatures";
- }
- else if (sensorType == "fan" || sensorType == "fan_tach" ||
- sensorType == "fan_pwm")
- {
- fieldName = "Fans";
- }
- else if (sensorType == "voltage")
- {
- 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;
- }
+ SensorsAsyncResp->res.jsonValue["@odata.id"] =
+ "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId +
+ "/" + SensorsAsyncResp->chassisSubNode + "/" +
+ sensorName;
+ sensorJson = &(SensorsAsyncResp->res.jsonValue);
}
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())
+ const char* fieldName = nullptr;
+ if (sensorType == "temperature")
{
- // Put multiple "sensors" into a single PowerControl.
- // Follows MemberId naming and naming in power.hpp.
+ fieldName = "Temperatures";
+ }
+ else if (sensorType == "fan" || sensorType == "fan_tach" ||
+ sensorType == "fan_pwm")
+ {
+ fieldName = "Fans";
+ }
+ else if (sensorType == "voltage")
+ {
+ 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
+ {
+ BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
+ << sensorType;
+ continue;
+ }
+
+ 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
+ {
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 @@
BMCWEB_LOG_DEBUG << "getSensorData exit";
}
+void processSensorList(
+ std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
+ std::shared_ptr<boost::container::flat_set<std::string>> sensorNames)
+{
+ 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<
+ boost::container::flat_map<std::string, std::string>>
+ objectMgrPaths) {
+ BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
+ auto getInventoryItemsCb =
+ [SensorsAsyncResp, sensorNames, connections,
+ objectMgrPaths](
+ std::shared_ptr<std::vector<InventoryItem>>
+ 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 @@
std::shared_ptr<boost::container::flat_set<std::string>>
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<boost::container::flat_map<std::string,
- std::string>>
- objectMgrPaths) {
- BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
- auto getInventoryItemsCb =
- [SensorsAsyncResp, sensorNames, connections,
- objectMgrPaths](
- std::shared_ptr<std::vector<InventoryItem>>
- 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 @@
getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
}
+class SensorCollection : public Node
+{
+ public:
+ SensorCollection(CrowApp& app) :
+ Node(app, "/redfish/v1/Chassis/<str>/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<const char*> typeList = {
+ "/xyz/openbmc_project/sensors/power",
+ "/xyz/openbmc_project/sensors/current"};
+ void doGet(crow::Response& res, const crow::Request& req,
+ const std::vector<std::string>& 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<SensorsAsyncResp> asyncResp =
+ std::make_shared<SensorsAsyncResp>(res, chassisId, typeList,
+ "Sensors");
+
+ auto getChassisCb =
+ [asyncResp](std::shared_ptr<boost::container::flat_set<std::string>>
+ 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/<str>/Sensors/<str>/", 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<std::string>& 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<SensorsAsyncResp> asyncResp =
+ std::make_shared<SensorsAsyncResp>(
+ res, chassisId, std::vector<const char*>(), "Sensors");
+
+ const std::string& sensorName = params[1];
+ const std::array<const char*, 1> 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<std::pair<std::string,
+ std::vector<std::string>>>>&
+ 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<boost::container::flat_set<std::string>>
+ sensorList = std::make_shared<
+ boost::container::flat_set<std::string>>();
+
+ 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