Introduce Thermal schema
Changes:
-redfish.hpp add thermal node installation
-thermal.hpp add thermal schema for chassis
-sensor.hpp add support for retrieving:
temperature, and fan
Verification:
-web server: no regression
-RSV: pass
-build on x86 and ASPEED
This patchset builds on Dawids original.
Change-Id: Ia8e40edff3c722fa02a161248bcdf602e36e3e62
Signed-off-by: Lewanczyk, Dawid <dawid.lewanczyk@intel.com>
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 9a49738..aa25a44 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -22,6 +22,7 @@
#include "../lib/roles.hpp"
#include "../lib/service_root.hpp"
#include "../lib/ethernet.hpp"
+#include "../lib/thermal.hpp"
#include "../lib/chassis.hpp"
#include "webserver_common.hpp"
@@ -48,6 +49,7 @@
nodes.emplace_back(std::make_unique<SessionService>(app));
nodes.emplace_back(std::make_unique<EthernetCollection>(app));
nodes.emplace_back(std::make_unique<EthernetInterface>(app));
+ nodes.emplace_back(std::make_unique<Thermal>(app));
nodes.emplace_back(std::make_unique<ManagerCollection>(app));
nodes.emplace_back(std::make_unique<ChassisCollection>(app));
nodes.emplace_back(std::make_unique<Chassis>(app));
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
index 02a614b..9f3fb38 100644
--- a/redfish-core/lib/chassis.hpp
+++ b/redfish-core/lib/chassis.hpp
@@ -248,6 +248,8 @@
// Create JSON copy based on Node::json, this is to avoid possible
// race condition
nlohmann::json json_response(Node::json);
+ json_response["Thermal"] = {
+ {"@odata.id", "/redfish/v1/Chassis/" + chassis_id + "/Thermal"}};
// If success...
if (success) {
// prepare all the schema required fields.
diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp
new file mode 100644
index 0000000..c48512a
--- /dev/null
+++ b/redfish-core/lib/sensors.hpp
@@ -0,0 +1,424 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <math.h>
+#include <dbus_singleton.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/range/algorithm/replace_copy_if.hpp>
+#include <boost/variant.hpp>
+#include <boost/variant/get.hpp>
+
+namespace redfish {
+
+constexpr const char* DBUS_SENSOR_PREFIX = "/xyz/openbmc_project/Sensors/";
+
+using GetSubTreeType = std::vector<
+ std::pair<std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+using ManagedObjectsVectorType = std::vector<std::pair<
+ dbus::object_path,
+ boost::container::flat_map<
+ std::string,
+ boost::container::flat_map<dbus::string, dbus::dbus_variant>>>>;
+
+/**
+ * AsyncResp
+ * Gathers data needed for response processing after async calls are done
+ */
+class AsyncResp {
+ public:
+ AsyncResp(crow::response& response, const std::string& chassisId,
+ const std::initializer_list<const char*> types)
+ : chassisId(chassisId), res(response), types(types) {
+ res.json_value["@odata.id"] =
+ "/redfish/v1/Chassis/" + chassisId + "/Thermal";
+ }
+
+ ~AsyncResp() {
+ if (res.code != static_cast<int>(HttpRespCode::OK)) {
+ // Reset the json object to clear out any data that made it in before the
+ // error happened
+ // todo(ed) handle error condition with proper code
+ res.json_value = nlohmann::json::object();
+ }
+ res.end();
+ }
+ void setErrorStatus() {
+ res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
+ }
+
+ std::string chassisId{};
+ crow::response& res;
+ const std::vector<const char*> types;
+};
+
+/**
+ * @brief Creates connections necessary for chassis sensors
+ * @param asyncResp Pointer to object holding response data
+ * @param sensorNames Sensors retrieved from chassis
+ * @param callback Callback for processing gathered connections
+ */
+template <typename Callback>
+void getConnections(const std::shared_ptr<AsyncResp>& asyncResp,
+ const boost::container::flat_set<std::string>& sensorNames,
+ Callback&& callback) {
+ CROW_LOG_DEBUG << "getConnections";
+ const std::string path = "/xyz/openbmc_project/Sensors";
+ const std::array<std::string, 1> interfaces = {
+ "xyz.openbmc_project.Sensor.Value"};
+ const dbus::endpoint object_mapper(
+ "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+
+ // Response handler for parsing objects subtree
+ auto resp_handler = [ callback{std::move(callback)}, asyncResp, sensorNames ](
+ const boost::system::error_code ec, const GetSubTreeType& subtree) {
+ if (ec != 0) {
+ asyncResp->setErrorStatus();
+ CROW_LOG_ERROR << "Dbus error " << ec;
+ return;
+ }
+
+ CROW_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
+
+ // Make unique list of connections only for requested sensor types and
+ // found in the chassis
+ boost::container::flat_set<std::string> connections;
+ // Intrinsic to avoid malloc. Most systems will have < 8 sensor producers
+ connections.reserve(8);
+
+ CROW_LOG_DEBUG << "sensorNames list cout: " << sensorNames.size();
+ for (const std::string& tsensor : sensorNames) {
+ CROW_LOG_DEBUG << "Sensor to find: " << tsensor;
+ }
+
+ for (const std::pair<
+ std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>&
+ object : subtree) {
+ for (const char* type : asyncResp->types) {
+ if (boost::starts_with(object.first, type)) {
+ auto lastPos = object.first.rfind('/');
+ if (lastPos != std::string::npos) {
+ std::string sensorName = object.first.substr(lastPos + 1);
+
+ if (sensorNames.find(sensorName) != sensorNames.end()) {
+ // For each connection name
+ for (const std::pair<std::string, std::vector<std::string>>&
+ objData : object.second) {
+ connections.insert(objData.first);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ CROW_LOG_DEBUG << "Found " << connections.size() << " connections";
+ callback(std::move(connections));
+ };
+
+ // Make call to ObjectMapper to find all sensors objects
+ crow::connections::system_bus->async_method_call(resp_handler, object_mapper,
+ path, 2, interfaces);
+}
+
+/**
+ * @brief Retrieves requested chassis sensors and redundancy data from DBus .
+ * @param asyncResp Pointer to object holding response data
+ * @param callback Callback for next step in gathered sensor processing
+ */
+template <typename Callback>
+void getChassis(const std::shared_ptr<AsyncResp>& asyncResp,
+ Callback&& callback) {
+ CROW_LOG_DEBUG << "getChassis Done";
+ const dbus::endpoint entityManager = {
+ "xyz.openbmc_project.EntityManager",
+ "/xyz/openbmc_project/Inventory/Item/Chassis",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"};
+
+ // Process response from EntityManager and extract chassis data
+ auto resp_handler = [ callback{std::move(callback)}, asyncResp ](
+ const boost::system::error_code ec, ManagedObjectsVectorType& resp) {
+ CROW_LOG_DEBUG << "getChassis resp_handler called back Done";
+ if (ec) {
+ CROW_LOG_ERROR << "getChassis resp_handler got error " << ec;
+ asyncResp->setErrorStatus();
+ return;
+ }
+ boost::container::flat_set<std::string> sensorNames;
+ const std::string chassis_prefix =
+ "/xyz/openbmc_project/Inventory/Item/Chassis/" + asyncResp->chassisId +
+ '/';
+ CROW_LOG_DEBUG << "Chassis Prefix " << chassis_prefix;
+ bool foundChassis = false;
+ for (const auto& objDictEntry : resp) {
+ if (boost::starts_with(objDictEntry.first.value, chassis_prefix)) {
+ foundChassis = true;
+ const std::string sensorName =
+ objDictEntry.first.value.substr(chassis_prefix.size());
+ // Make sure this isn't a subobject (like a threshold)
+ const std::size_t sensorPos = sensorName.find('/');
+ if (sensorPos == std::string::npos) {
+ CROW_LOG_DEBUG << "Adding sensor " << sensorName;
+
+ sensorNames.emplace(sensorName);
+ }
+ }
+ };
+ CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
+
+ if (!foundChassis) {
+ CROW_LOG_INFO << "Unable to find chassis named " << asyncResp->chassisId;
+ asyncResp->res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+ } else {
+ callback(sensorNames);
+ }
+ };
+
+ // Make call to EntityManager to find all chassis objects
+ crow::connections::system_bus->async_method_call(resp_handler, entityManager);
+}
+
+/**
+ * @brief Builds a json sensor representation of a sensor.
+ * @param sensorName The name of the sensor to be built
+ * @param sensorType The type (temperature, fan_tach, ect) of 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
+ */
+void objectInterfacesToJson(
+ const std::string& sensorName, const std::string& sensorType,
+ const boost::container::flat_map<
+ std::string,
+ boost::container::flat_map<dbus::string, dbus::dbus_variant>>&
+ interfacesDict,
+ nlohmann::json& sensor_json) {
+ // We need a value interface before we can do anything with it
+ auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
+ if (value_it == interfacesDict.end()) {
+ CROW_LOG_ERROR << "Sensor doesn't have a value interface";
+ return;
+ }
+
+ // Assume values exist as is (10^0 == 1) if no scale exists
+ int64_t scaleMultiplier = 0;
+
+ auto scale_it = value_it->second.find("Scale");
+ // If a scale exists, pull value as int64, and use the scaling.
+ if (scale_it != value_it->second.end()) {
+ const int64_t* int64Value = boost::get<int64_t>(&scale_it->second);
+ if (int64Value != nullptr) {
+ scaleMultiplier = *int64Value;
+ }
+ }
+
+ sensor_json["MemberId"] = sensorName;
+ sensor_json["Name"] = sensorName;
+ sensor_json["Status"]["State"] = "Enabled";
+ sensor_json["Status"]["Health"] = "OK";
+
+ // Parameter to set to override the type we get from dbus, and force it to
+ // int, regardless of what is available. This is used for schemas like fan,
+ // that require integers, not floats.
+ bool forceToInt = false;
+
+ const char* unit = "Reading";
+ if (sensorType == "temperature") {
+ unit = "ReadingCelsius";
+ // TODO(ed) Documentation says that path should be type fan_tach,
+ // implementation seems to implement fan
+ } else if (sensorType == "fan" || sensorType == "fan_tach") {
+ unit = "Reading";
+ sensor_json["ReadingUnits"] = "RPM";
+ forceToInt = true;
+ } else if (sensorType == "voltage") {
+ unit = "ReadingVolts";
+ } else {
+ CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
+ return;
+ }
+ // Map of dbus interface name, dbus property name and redfish property_name
+ std::vector<std::tuple<const char*, const char*, const char*>> properties;
+ properties.reserve(7);
+
+ properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
+ properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
+ "WarningHigh", "UpperThresholdNonCritical");
+ properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
+ "WarningLow", "LowerThresholdNonCritical");
+ properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
+ "CriticalHigh", "UpperThresholdCritical");
+ properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
+ "CriticalLow", "LowerThresholdCritical");
+
+ if (sensorType == "temperature") {
+ properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
+ "MinReadingRangeTemp");
+ properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
+ "MaxReadingRangeTemp");
+ } else {
+ properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
+ "MinReadingRange");
+ properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
+ "MaxReadingRange");
+ }
+
+ for (const std::tuple<const char*, const char*, const char*>& p :
+ properties) {
+ auto interfaceProperties = interfacesDict.find(std::get<0>(p));
+ if (interfaceProperties != interfacesDict.end()) {
+ auto value_it = interfaceProperties->second.find(std::get<1>(p));
+ if (value_it != interfaceProperties->second.end()) {
+ const dbus::dbus_variant& valueVariant = value_it->second;
+ nlohmann::json& value_it = sensor_json[std::get<2>(p)];
+ // Attempt to pull the int64 directly
+ const int64_t* int64Value = boost::get<int64_t>(&valueVariant);
+
+ if (int64Value != nullptr) {
+ if (forceToInt || scaleMultiplier >= 0) {
+ value_it = *int64Value * std::pow(10, scaleMultiplier);
+ } else {
+ value_it = *int64Value *
+ std::pow(10, static_cast<double>(scaleMultiplier));
+ }
+ }
+ // Attempt to pull the float directly
+ const double* doubleValue = boost::get<double>(&valueVariant);
+
+ if (doubleValue != nullptr) {
+ if (!forceToInt) {
+ value_it = *doubleValue *
+ std::pow(10, static_cast<double>(scaleMultiplier));
+ } else {
+ value_it = static_cast<int64_t>(*doubleValue *
+ std::pow(10, scaleMultiplier));
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief Entry point for retrieving sensors data related to requested
+ * chassis.
+ * @param asyncResp Pointer to object holding response data
+ */
+void getChassisData(const std::shared_ptr<AsyncResp>& asyncResp) {
+ CROW_LOG_DEBUG << "getChassisData";
+ auto getChassisCb = [&, asyncResp](boost::container::flat_set<std::string>&
+ sensorNames) {
+ CROW_LOG_DEBUG << "getChassisCb Done";
+ auto getConnectionCb =
+ [&, asyncResp, sensorNames](
+ const boost::container::flat_set<std::string>& connections) {
+ CROW_LOG_DEBUG << "getConnectionCb Done";
+ // Get managed objects from all services exposing sensors
+ for (const std::string& connection : connections) {
+ // Response handler to process managed objects
+ auto getManagedObjectsCb = [&, asyncResp, sensorNames](
+ const boost::system::error_code ec,
+ ManagedObjectsVectorType& resp) {
+ // Go through all objects and update response with
+ // sensor data
+ for (const auto& objDictEntry : resp) {
+ const std::string& objPath = objDictEntry.first.value;
+ CROW_LOG_DEBUG << "getManagedObjectsCb parsing object "
+ << objPath;
+ if (!boost::starts_with(objPath, DBUS_SENSOR_PREFIX)) {
+ CROW_LOG_ERROR << "Got path that isn't in sensor namespace: "
+ << objPath;
+ continue;
+ }
+ std::vector<std::string> split;
+ // Reserve space for
+ // /xyz/openbmc_project/Sensors/<name>/<subname>
+ split.reserve(6);
+ boost::algorithm::split(split, objPath, boost::is_any_of("/"));
+ if (split.size() < 6) {
+ CROW_LOG_ERROR << "Got path that isn't long enough "
+ << objPath;
+ continue;
+ }
+ // These indexes aren't intuitive, as boost::split puts an empty
+ // string at the beggining
+ const std::string& sensorType = split[4];
+ const std::string& sensorName = split[5];
+ CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType "
+ << sensorType;
+ if (sensorNames.find(sensorName) == sensorNames.end()) {
+ CROW_LOG_ERROR << sensorName << " not in sensor list ";
+ continue;
+ }
+
+ const char* fieldName = nullptr;
+ if (sensorType == "temperature") {
+ fieldName = "Temperatures";
+ } else if (sensorType == "fan" || sensorType == "fan_tach") {
+ fieldName = "Fans";
+ } else if (sensorType == "voltage") {
+ fieldName = "Voltages";
+ } else if (sensorType == "current") {
+ fieldName = "PowerSupply";
+ } else if (sensorType == "power") {
+ fieldName = "PowerSupply";
+ } else {
+ CROW_LOG_ERROR << "Unsure how to handle sensorType "
+ << sensorType;
+ continue;
+ }
+
+ nlohmann::json& temp_array =
+ asyncResp->res.json_value[fieldName];
+
+ // Create the array if it doesn't yet exist
+ if (temp_array.is_array() == false) {
+ temp_array = nlohmann::json::array();
+ }
+
+ temp_array.push_back(nlohmann::json::object());
+ nlohmann::json& sensor_json = temp_array.back();
+ sensor_json["@odata.id"] = "/redfish/v1/Chassis/" +
+ asyncResp->chassisId + "/Thermal#/" +
+ sensorName;
+ objectInterfacesToJson(sensorName, sensorType,
+ objDictEntry.second, sensor_json);
+ }
+ };
+
+ dbus::endpoint ep(connection, "/xyz/openbmc_project/Sensors",
+ "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ crow::connections::system_bus->async_method_call(
+ getManagedObjectsCb, ep);
+ };
+ };
+ // Get connections and then pass it to get sensors
+ getConnections(asyncResp, sensorNames, std::move(getConnectionCb));
+ };
+
+ // Get chassis information related to sensors
+ getChassis(asyncResp, std::move(getChassisCb));
+};
+
+} // namespace redfish
diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp
new file mode 100644
index 0000000..b33c80f
--- /dev/null
+++ b/redfish-core/lib/thermal.hpp
@@ -0,0 +1,60 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include "node.hpp"
+#include "sensors.hpp"
+
+namespace redfish {
+
+class Thermal : public Node {
+ public:
+ Thermal(CrowApp& app)
+ : Node((app), "/redfish/v1/Chassis/<str>/Thermal/", std::string()) {
+ Node::json["@odata.type"] = "#Thermal.v1_2_0.Thermal";
+ Node::json["@odata.context"] = "/redfish/v1/$metadata#Thermal.Thermal";
+ Node::json["Id"] = "Thermal";
+ Node::json["Name"] = "Thermal";
+
+ entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
+ {crow::HTTPMethod::HEAD, {{"Login"}}},
+ {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
+ {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
+ {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
+ {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::response& res, const crow::request& req,
+ const std::vector<std::string>& params) override {
+ if (params.size() != 1) {
+ res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
+ res.end();
+ return;
+ }
+ const std::string& chassis_name = params[0];
+
+ res.json_value = Node::json;
+ auto asyncResp = std::make_shared<AsyncResp>(
+ res, chassis_name,
+ std::initializer_list<const char*>{
+ "/xyz/openbmc_project/Sensors/fan",
+ "/xyz/openbmc_project/Sensors/temperature"});
+ getChassisData(asyncResp);
+ }
+};
+
+} // namespace redfish