xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 274fad5ad0e7634d9ed3e174695136e674688e0c)
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 #include <dbus_singleton.hpp>
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 <boost/variant.hpp>
25 #include <boost/variant/get.hpp>
26 
27 namespace redfish {
28 
29 constexpr const char* DBUS_SENSOR_PREFIX = "/xyz/openbmc_project/Sensors/";
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 ManagedObjectsVectorType = std::vector<std::pair<
36     dbus::object_path,
37     boost::container::flat_map<
38         std::string,
39         boost::container::flat_map<dbus::string, dbus::dbus_variant>>>>;
40 
41 /**
42  * AsyncResp
43  * Gathers data needed for response processing after async calls are done
44  */
45 class AsyncResp {
46  public:
47   AsyncResp(crow::response& response, const std::string& chassisId,
48             const std::initializer_list<const char*> types)
49       : chassisId(chassisId), res(response), types(types) {
50     res.json_value["@odata.id"] =
51         "/redfish/v1/Chassis/" + chassisId + "/Thermal";
52   }
53 
54   ~AsyncResp() {
55     if (res.code != static_cast<int>(HttpRespCode::OK)) {
56       // Reset the json object to clear out any data that made it in before the
57       // error happened
58       // todo(ed) handle error condition with proper code
59       res.json_value = nlohmann::json::object();
60     }
61     res.end();
62   }
63   void setErrorStatus() {
64     res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
65   }
66 
67   std::string chassisId{};
68   crow::response& res;
69   const std::vector<const char*> types;
70 };
71 
72 /**
73  * @brief Creates connections necessary for chassis sensors
74  * @param asyncResp Pointer to object holding response data
75  * @param sensorNames Sensors retrieved from chassis
76  * @param callback Callback for processing gathered connections
77  */
78 template <typename Callback>
79 void getConnections(const std::shared_ptr<AsyncResp>& asyncResp,
80                     const boost::container::flat_set<std::string>& sensorNames,
81                     Callback&& callback) {
82   CROW_LOG_DEBUG << "getConnections";
83   const std::string path = "/xyz/openbmc_project/Sensors";
84   const std::array<std::string, 1> interfaces = {
85       "xyz.openbmc_project.Sensor.Value"};
86   const dbus::endpoint object_mapper(
87       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
88       "xyz.openbmc_project.ObjectMapper", "GetSubTree");
89 
90   // Response handler for parsing objects subtree
91   auto resp_handler = [ callback{std::move(callback)}, asyncResp, sensorNames ](
92       const boost::system::error_code ec, const GetSubTreeType& subtree) {
93     if (ec != 0) {
94       asyncResp->setErrorStatus();
95       CROW_LOG_ERROR << "Dbus error " << ec;
96       return;
97     }
98 
99     CROW_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
100 
101     // Make unique list of connections only for requested sensor types and
102     // found in the chassis
103     boost::container::flat_set<std::string> connections;
104     // Intrinsic to avoid malloc.  Most systems will have < 8 sensor producers
105     connections.reserve(8);
106 
107     CROW_LOG_DEBUG << "sensorNames list cout: " << sensorNames.size();
108     for (const std::string& tsensor : sensorNames) {
109       CROW_LOG_DEBUG << "Sensor to find: " << tsensor;
110     }
111 
112     for (const std::pair<
113              std::string,
114              std::vector<std::pair<std::string, std::vector<std::string>>>>&
115              object : subtree) {
116       for (const char* type : asyncResp->types) {
117         if (boost::starts_with(object.first, type)) {
118           auto lastPos = object.first.rfind('/');
119           if (lastPos != std::string::npos) {
120             std::string sensorName = object.first.substr(lastPos + 1);
121 
122             if (sensorNames.find(sensorName) != sensorNames.end()) {
123               // For each connection name
124               for (const std::pair<std::string, std::vector<std::string>>&
125                        objData : object.second) {
126                 connections.insert(objData.first);
127               }
128             }
129           }
130           break;
131         }
132       }
133     }
134     CROW_LOG_DEBUG << "Found " << connections.size() << " connections";
135     callback(std::move(connections));
136   };
137 
138   // Make call to ObjectMapper to find all sensors objects
139   crow::connections::system_bus->async_method_call(resp_handler, object_mapper,
140                                                    path, 2, interfaces);
141 }
142 
143 /**
144  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
145  * @param asyncResp   Pointer to object holding response data
146  * @param callback  Callback for next step in gathered sensor processing
147  */
148 template <typename Callback>
149 void getChassis(const std::shared_ptr<AsyncResp>& asyncResp,
150                 Callback&& callback) {
151   CROW_LOG_DEBUG << "getChassis Done";
152   const dbus::endpoint entityManager = {
153       "xyz.openbmc_project.EntityManager",
154       "/xyz/openbmc_project/Inventory/Item/Chassis",
155       "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"};
156 
157   // Process response from EntityManager and extract chassis data
158   auto resp_handler = [ callback{std::move(callback)}, asyncResp ](
159       const boost::system::error_code ec, ManagedObjectsVectorType& resp) {
160     CROW_LOG_DEBUG << "getChassis resp_handler called back Done";
161     if (ec) {
162       CROW_LOG_ERROR << "getChassis resp_handler got error " << ec;
163       asyncResp->setErrorStatus();
164       return;
165     }
166     boost::container::flat_set<std::string> sensorNames;
167     const std::string chassis_prefix =
168         "/xyz/openbmc_project/Inventory/Item/Chassis/" + asyncResp->chassisId +
169         '/';
170     CROW_LOG_DEBUG << "Chassis Prefix " << chassis_prefix;
171     bool foundChassis = false;
172     for (const auto& objDictEntry : resp) {
173       if (boost::starts_with(objDictEntry.first.value, chassis_prefix)) {
174         foundChassis = true;
175         const std::string sensorName =
176             objDictEntry.first.value.substr(chassis_prefix.size());
177         // Make sure this isn't a subobject (like a threshold)
178         const std::size_t sensorPos = sensorName.find('/');
179         if (sensorPos == std::string::npos) {
180           CROW_LOG_DEBUG << "Adding sensor " << sensorName;
181 
182           sensorNames.emplace(sensorName);
183         }
184       }
185     };
186     CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
187 
188     if (!foundChassis) {
189       CROW_LOG_INFO << "Unable to find chassis named " << asyncResp->chassisId;
190       asyncResp->res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
191     } else {
192       callback(sensorNames);
193     }
194   };
195 
196   // Make call to EntityManager to find all chassis objects
197   crow::connections::system_bus->async_method_call(resp_handler, entityManager);
198 }
199 
200 /**
201  * @brief Builds a json sensor representation of a sensor.
202  * @param sensorName  The name of the sensor to be built
203  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
204  * build
205  * @param interfacesDict  A dictionary of the interfaces and properties of said
206  * interfaces to be built from
207  * @param sensor_json  The json object to fill
208  */
209 void objectInterfacesToJson(
210     const std::string& sensorName, const std::string& sensorType,
211     const boost::container::flat_map<
212         std::string,
213         boost::container::flat_map<dbus::string, dbus::dbus_variant>>&
214         interfacesDict,
215     nlohmann::json& sensor_json) {
216   // We need a value interface before we can do anything with it
217   auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
218   if (value_it == interfacesDict.end()) {
219     CROW_LOG_ERROR << "Sensor doesn't have a value interface";
220     return;
221   }
222 
223   // Assume values exist as is (10^0 == 1) if no scale exists
224   int64_t scaleMultiplier = 0;
225 
226   auto scale_it = value_it->second.find("Scale");
227   // If a scale exists, pull value as int64, and use the scaling.
228   if (scale_it != value_it->second.end()) {
229     const int64_t* int64Value = boost::get<int64_t>(&scale_it->second);
230     if (int64Value != nullptr) {
231       scaleMultiplier = *int64Value;
232     }
233   }
234 
235   sensor_json["MemberId"] = sensorName;
236   sensor_json["Name"] = sensorName;
237   sensor_json["Status"]["State"] = "Enabled";
238   sensor_json["Status"]["Health"] = "OK";
239 
240   // Parameter to set to override the type we get from dbus, and force it to
241   // int, regardless of what is available.  This is used for schemas like fan,
242   // that require integers, not floats.
243   bool forceToInt = false;
244 
245   const char* unit = "Reading";
246   if (sensorType == "temperature") {
247     unit = "ReadingCelsius";
248     // TODO(ed) Documentation says that path should be type fan_tach,
249     // implementation seems to implement fan
250   } else if (sensorType == "fan" || sensorType == "fan_tach") {
251     unit = "Reading";
252     sensor_json["ReadingUnits"] = "RPM";
253     forceToInt = true;
254   } else if (sensorType == "voltage") {
255     unit = "ReadingVolts";
256   } else {
257     CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
258     return;
259   }
260   // Map of dbus interface name, dbus property name and redfish property_name
261   std::vector<std::tuple<const char*, const char*, const char*>> properties;
262   properties.reserve(7);
263 
264   properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
265   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
266                           "WarningHigh", "UpperThresholdNonCritical");
267   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
268                           "WarningLow", "LowerThresholdNonCritical");
269   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
270                           "CriticalHigh", "UpperThresholdCritical");
271   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
272                           "CriticalLow", "LowerThresholdCritical");
273 
274   if (sensorType == "temperature") {
275     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
276                             "MinReadingRangeTemp");
277     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
278                             "MaxReadingRangeTemp");
279   } else {
280     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
281                             "MinReadingRange");
282     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
283                             "MaxReadingRange");
284   }
285 
286   for (const std::tuple<const char*, const char*, const char*>& p :
287        properties) {
288     auto interfaceProperties = interfacesDict.find(std::get<0>(p));
289     if (interfaceProperties != interfacesDict.end()) {
290       auto value_it = interfaceProperties->second.find(std::get<1>(p));
291       if (value_it != interfaceProperties->second.end()) {
292         const dbus::dbus_variant& valueVariant = value_it->second;
293         nlohmann::json& value_it = sensor_json[std::get<2>(p)];
294         // Attempt to pull the int64 directly
295         const int64_t* int64Value = boost::get<int64_t>(&valueVariant);
296 
297         if (int64Value != nullptr) {
298           if (forceToInt || scaleMultiplier >= 0) {
299             value_it = *int64Value * std::pow(10, scaleMultiplier);
300           } else {
301             value_it = *int64Value *
302                        std::pow(10, static_cast<double>(scaleMultiplier));
303           }
304         }
305         // Attempt to pull the float directly
306         const double* doubleValue = boost::get<double>(&valueVariant);
307 
308         if (doubleValue != nullptr) {
309           if (!forceToInt) {
310             value_it = *doubleValue *
311                        std::pow(10, static_cast<double>(scaleMultiplier));
312           } else {
313             value_it = static_cast<int64_t>(*doubleValue *
314                                             std::pow(10, scaleMultiplier));
315           }
316         }
317       }
318     }
319   }
320 }
321 
322 /**
323  * @brief Entry point for retrieving sensors data related to requested
324  *        chassis.
325  * @param asyncResp   Pointer to object holding response data
326  */
327 void getChassisData(const std::shared_ptr<AsyncResp>& asyncResp) {
328   CROW_LOG_DEBUG << "getChassisData";
329   auto getChassisCb = [&, asyncResp](boost::container::flat_set<std::string>&
330                                          sensorNames) {
331     CROW_LOG_DEBUG << "getChassisCb Done";
332     auto getConnectionCb =
333         [&, asyncResp, sensorNames](
334             const boost::container::flat_set<std::string>& connections) {
335           CROW_LOG_DEBUG << "getConnectionCb Done";
336           // Get managed objects from all services exposing sensors
337           for (const std::string& connection : connections) {
338             // Response handler to process managed objects
339             auto getManagedObjectsCb = [&, asyncResp, sensorNames](
340                                            const boost::system::error_code ec,
341                                            ManagedObjectsVectorType& resp) {
342               // Go through all objects and update response with
343               // sensor data
344               for (const auto& objDictEntry : resp) {
345                 const std::string& objPath = objDictEntry.first.value;
346                 CROW_LOG_DEBUG << "getManagedObjectsCb parsing object "
347                                << objPath;
348                 if (!boost::starts_with(objPath, DBUS_SENSOR_PREFIX)) {
349                   CROW_LOG_ERROR << "Got path that isn't in sensor namespace: "
350                                  << objPath;
351                   continue;
352                 }
353                 std::vector<std::string> split;
354                 // Reserve space for
355                 // /xyz/openbmc_project/Sensors/<name>/<subname>
356                 split.reserve(6);
357                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
358                 if (split.size() < 6) {
359                   CROW_LOG_ERROR << "Got path that isn't long enough "
360                                  << objPath;
361                   continue;
362                 }
363                 // These indexes aren't intuitive, as boost::split puts an empty
364                 // string at the beginning
365                 const std::string& sensorType = split[4];
366                 const std::string& sensorName = split[5];
367                 CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType "
368                                << sensorType;
369                 if (sensorNames.find(sensorName) == sensorNames.end()) {
370                   CROW_LOG_ERROR << sensorName << " not in sensor list ";
371                   continue;
372                 }
373 
374                 const char* fieldName = nullptr;
375                 if (sensorType == "temperature") {
376                   fieldName = "Temperatures";
377                 } else if (sensorType == "fan" || sensorType == "fan_tach") {
378                   fieldName = "Fans";
379                 } else if (sensorType == "voltage") {
380                   fieldName = "Voltages";
381                 } else if (sensorType == "current") {
382                   fieldName = "PowerSupply";
383                 } else if (sensorType == "power") {
384                   fieldName = "PowerSupply";
385                 } else {
386                   CROW_LOG_ERROR << "Unsure how to handle sensorType "
387                                  << sensorType;
388                   continue;
389                 }
390 
391                 nlohmann::json& temp_array =
392                     asyncResp->res.json_value[fieldName];
393 
394                 // Create the array if it doesn't yet exist
395                 if (temp_array.is_array() == false) {
396                   temp_array = nlohmann::json::array();
397                 }
398 
399                 temp_array.push_back(nlohmann::json::object());
400                 nlohmann::json& sensor_json = temp_array.back();
401                 sensor_json["@odata.id"] = "/redfish/v1/Chassis/" +
402                                            asyncResp->chassisId + "/Thermal#/" +
403                                            sensorName;
404                 objectInterfacesToJson(sensorName, sensorType,
405                                        objDictEntry.second, sensor_json);
406               }
407             };
408 
409             dbus::endpoint ep(connection, "/xyz/openbmc_project/Sensors",
410                               "org.freedesktop.DBus.ObjectManager",
411                               "GetManagedObjects");
412             crow::connections::system_bus->async_method_call(
413                 getManagedObjectsCb, ep);
414           };
415         };
416     // Get connections and then pass it to get sensors
417     getConnections(asyncResp, sensorNames, std::move(getConnectionCb));
418   };
419 
420   // Get chassis information related to sensors
421   getChassis(asyncResp, std::move(getChassisCb));
422 };
423 
424 }  // namespace redfish
425