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