blob: c48512af972cc7af71b80cd4a2e6e5471fdd4e75 [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>
24#include <boost/variant.hpp>
25#include <boost/variant/get.hpp>
26
27namespace redfish {
28
29constexpr const char* DBUS_SENSOR_PREFIX = "/xyz/openbmc_project/Sensors/";
30
31using GetSubTreeType = std::vector<
32 std::pair<std::string,
33 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
34
35using 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 */
45class 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 */
78template <typename Callback>
79void 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 */
148template <typename Callback>
149void 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, ect) 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 */
209void 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 */
327void 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 beggining
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