blob: a8b74ffb3804811f92577882a5ac682205e687aa [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() {
Ed Tanouse0d918b2018-03-27 17:41:04 -070054 if (res.result() == boost::beast::http::status::internal_server_error) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010055 // 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() {
Ed Tanouse0d918b2018-03-27 17:41:04 -070063 res.result(boost::beast::http::status::internal_server_error);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010064 }
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>
Ed Tanouse0d918b2018-03-27 17:41:04 -070078void getConnections(std::shared_ptr<AsyncResp> asyncResp,
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010079 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) {
Ed Tanouse0d918b2018-03-27 17:41:04 -070089 if (ec) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010090 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>
Ed Tanouse0d918b2018-03-27 17:41:04 -0700147void getChassis(std::shared_ptr<AsyncResp> asyncResp, Callback&& callback) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100148 CROW_LOG_DEBUG << "getChassis Done";
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100149
150 // Process response from EntityManager and extract chassis data
151 auto resp_handler = [ callback{std::move(callback)}, asyncResp ](
152 const boost::system::error_code ec, ManagedObjectsVectorType& resp) {
153 CROW_LOG_DEBUG << "getChassis resp_handler called back Done";
154 if (ec) {
155 CROW_LOG_ERROR << "getChassis resp_handler got error " << ec;
156 asyncResp->setErrorStatus();
157 return;
158 }
159 boost::container::flat_set<std::string> sensorNames;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100160
Ed Tanousdaf36e22018-04-20 16:01:36 -0700161 // asyncResp->chassisId
162 bool foundChassis = false;
163 std::vector<std::string> split;
164 // Reserve space for
165 // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
166 split.reserve(8);
167
168 for (const auto& objDictEntry : resp) {
169 const std::string& objectPath =
170 static_cast<const std::string&>(objDictEntry.first);
171 boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
172 if (split.size() < 2) {
173 CROW_LOG_ERROR << "Got path that isn't long enough " << objectPath;
174 split.clear();
175 continue;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100176 }
Ed Tanousdaf36e22018-04-20 16:01:36 -0700177 const std::string& sensorName = split.end()[-1];
178 const std::string& chassisName = split.end()[-2];
179
180 if (chassisName != asyncResp->chassisId) {
181 split.clear();
182 continue;
183 }
184 foundChassis = true;
185 sensorNames.emplace(sensorName);
186 split.clear();
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100187 };
188 CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
189
190 if (!foundChassis) {
191 CROW_LOG_INFO << "Unable to find chassis named " << asyncResp->chassisId;
Ed Tanouse0d918b2018-03-27 17:41:04 -0700192 asyncResp->res.result(boost::beast::http::status::not_found);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100193 } else {
194 callback(sensorNames);
195 }
196 };
197
198 // Make call to EntityManager to find all chassis objects
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700199 crow::connections::system_bus->async_method_call(
200 resp_handler, "xyz.openbmc_project.EntityManager",
Ed Tanousdaf36e22018-04-20 16:01:36 -0700201 "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
202 "GetManagedObjects");
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100203}
204
205/**
206 * @brief Builds a json sensor representation of a sensor.
207 * @param sensorName The name of the sensor to be built
Gunnar Mills274fad52018-06-13 15:45:36 -0500208 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100209 * build
210 * @param interfacesDict A dictionary of the interfaces and properties of said
211 * interfaces to be built from
212 * @param sensor_json The json object to fill
213 */
214void objectInterfacesToJson(
215 const std::string& sensorName, const std::string& sensorType,
216 const boost::container::flat_map<
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700217 std::string, boost::container::flat_map<std::string, SensorVariant>>&
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100218 interfacesDict,
219 nlohmann::json& sensor_json) {
220 // We need a value interface before we can do anything with it
221 auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
222 if (value_it == interfacesDict.end()) {
223 CROW_LOG_ERROR << "Sensor doesn't have a value interface";
224 return;
225 }
226
227 // Assume values exist as is (10^0 == 1) if no scale exists
228 int64_t scaleMultiplier = 0;
229
230 auto scale_it = value_it->second.find("Scale");
231 // If a scale exists, pull value as int64, and use the scaling.
232 if (scale_it != value_it->second.end()) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700233 const int64_t* int64Value =
234 mapbox::get_ptr<const int64_t>(scale_it->second);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100235 if (int64Value != nullptr) {
236 scaleMultiplier = *int64Value;
237 }
238 }
239
240 sensor_json["MemberId"] = sensorName;
241 sensor_json["Name"] = sensorName;
242 sensor_json["Status"]["State"] = "Enabled";
243 sensor_json["Status"]["Health"] = "OK";
244
245 // Parameter to set to override the type we get from dbus, and force it to
246 // int, regardless of what is available. This is used for schemas like fan,
247 // that require integers, not floats.
248 bool forceToInt = false;
249
250 const char* unit = "Reading";
251 if (sensorType == "temperature") {
252 unit = "ReadingCelsius";
253 // TODO(ed) Documentation says that path should be type fan_tach,
254 // implementation seems to implement fan
Ed Tanousdaf36e22018-04-20 16:01:36 -0700255 } else if (sensorType == "fan" || sensorType == "fan_type") {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100256 unit = "Reading";
257 sensor_json["ReadingUnits"] = "RPM";
258 forceToInt = true;
259 } else if (sensorType == "voltage") {
260 unit = "ReadingVolts";
261 } else {
262 CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
263 return;
264 }
265 // Map of dbus interface name, dbus property name and redfish property_name
266 std::vector<std::tuple<const char*, const char*, const char*>> properties;
267 properties.reserve(7);
268
269 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
270 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
271 "WarningHigh", "UpperThresholdNonCritical");
272 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
273 "WarningLow", "LowerThresholdNonCritical");
274 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
275 "CriticalHigh", "UpperThresholdCritical");
276 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
277 "CriticalLow", "LowerThresholdCritical");
278
279 if (sensorType == "temperature") {
280 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
281 "MinReadingRangeTemp");
282 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
283 "MaxReadingRangeTemp");
284 } else {
285 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
286 "MinReadingRange");
287 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
288 "MaxReadingRange");
289 }
290
291 for (const std::tuple<const char*, const char*, const char*>& p :
292 properties) {
293 auto interfaceProperties = interfacesDict.find(std::get<0>(p));
294 if (interfaceProperties != interfacesDict.end()) {
295 auto value_it = interfaceProperties->second.find(std::get<1>(p));
296 if (value_it != interfaceProperties->second.end()) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700297 const SensorVariant& valueVariant = value_it->second;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100298 nlohmann::json& value_it = sensor_json[std::get<2>(p)];
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700299
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100300 // Attempt to pull the int64 directly
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700301 const int64_t* int64Value =
302 mapbox::get_ptr<const int64_t>(valueVariant);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100303
304 if (int64Value != nullptr) {
305 if (forceToInt || scaleMultiplier >= 0) {
306 value_it = *int64Value * std::pow(10, scaleMultiplier);
307 } else {
308 value_it = *int64Value *
309 std::pow(10, static_cast<double>(scaleMultiplier));
310 }
311 }
312 // Attempt to pull the float directly
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700313 const double* doubleValue = mapbox::get_ptr<const double>(valueVariant);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100314
315 if (doubleValue != nullptr) {
316 if (!forceToInt) {
317 value_it = *doubleValue *
318 std::pow(10, static_cast<double>(scaleMultiplier));
319 } else {
320 value_it = static_cast<int64_t>(*doubleValue *
321 std::pow(10, scaleMultiplier));
322 }
323 }
324 }
325 }
326 }
Ed Tanouse0d918b2018-03-27 17:41:04 -0700327 CROW_LOG_DEBUG << "Added sensor " << sensorName;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100328}
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 */
Ed Tanouse0d918b2018-03-27 17:41:04 -0700335void getChassisData(std::shared_ptr<AsyncResp> asyncResp) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100336 CROW_LOG_DEBUG << "getChassisData";
337 auto getChassisCb = [&, asyncResp](boost::container::flat_set<std::string>&
338 sensorNames) {
339 CROW_LOG_DEBUG << "getChassisCb Done";
Ed Tanouse0d918b2018-03-27 17:41:04 -0700340 auto getConnectionCb = [&, asyncResp, sensorNames](
341 const boost::container::flat_set<std::string>&
342 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) {
353 const std::string& objPath =
354 static_cast<const std::string&>(objDictEntry.first);
355 CROW_LOG_DEBUG << "getManagedObjectsCb parsing object " << objPath;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100356
Ed Tanouse0d918b2018-03-27 17:41:04 -0700357 std::vector<std::string> split;
358 // Reserve space for
359 // /xyz/openbmc_project/Sensors/<name>/<subname>
360 split.reserve(6);
361 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
362 if (split.size() < 6) {
363 CROW_LOG_ERROR << "Got path that isn't long enough " << objPath;
364 continue;
365 }
366 // These indexes aren't intuitive, as boost::split puts an empty
367 // string at the beggining
368 const std::string& sensorType = split[4];
369 const std::string& sensorName = split[5];
370 CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType "
371 << sensorType;
372 if (sensorNames.find(sensorName) == sensorNames.end()) {
373 CROW_LOG_ERROR << sensorName << " not in sensor list ";
374 continue;
375 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100376
Ed Tanouse0d918b2018-03-27 17:41:04 -0700377 const char* fieldName = nullptr;
378 if (sensorType == "temperature") {
379 fieldName = "Temperatures";
380 } else if (sensorType == "fan" || sensorType == "fan_tach") {
381 fieldName = "Fans";
382 } else if (sensorType == "voltage") {
383 fieldName = "Voltages";
384 } else if (sensorType == "current") {
385 fieldName = "PowerSupply";
386 } else if (sensorType == "power") {
387 fieldName = "PowerSupply";
388 } else {
389 CROW_LOG_ERROR << "Unsure how to handle sensorType "
390 << sensorType;
391 continue;
392 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100393
Ed Tanouse0d918b2018-03-27 17:41:04 -0700394 nlohmann::json& temp_array = asyncResp->res.json_value[fieldName];
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100395
Ed Tanouse0d918b2018-03-27 17:41:04 -0700396 // Create the array if it doesn't yet exist
397 if (temp_array.is_array() == false) {
398 temp_array = nlohmann::json::array();
399 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100400
Ed Tanouse0d918b2018-03-27 17:41:04 -0700401 temp_array.push_back(
402 {{"@odata.id", "/redfish/v1/Chassis/" + asyncResp->chassisId +
403 "/Thermal#/" + sensorName}});
404 nlohmann::json& sensor_json = temp_array.back();
405 objectInterfacesToJson(sensorName, sensorType, objDictEntry.second,
406 sensor_json);
407 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100408 };
Ed Tanouse0d918b2018-03-27 17:41:04 -0700409
410 crow::connections::system_bus->async_method_call(
411 getManagedObjectsCb, connection, "/xyz/openbmc_project/Sensors",
412 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
413 };
414 };
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100415 // Get connections and then pass it to get sensors
416 getConnections(asyncResp, sensorNames, std::move(getConnectionCb));
417 };
418
419 // Get chassis information related to sensors
420 getChassis(asyncResp, std::move(getChassisCb));
421};
422
423} // namespace redfish