blob: 7f7967ed6dd7fa051113bfd741b563fec9fd5193 [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/**
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020041 * SensorsAsyncResp
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010042 * Gathers data needed for response processing after async calls are done
43 */
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020044class SensorsAsyncResp {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010045 public:
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020046 SensorsAsyncResp(crow::response& response, const std::string& chassisId,
47 const std::initializer_list<const char*> types)
48 : res(response), chassisId(chassisId), types(types) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010049 res.json_value["@odata.id"] =
50 "/redfish/v1/Chassis/" + chassisId + "/Thermal";
51 }
52
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020053 ~SensorsAsyncResp() {
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 }
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020062
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010063 void setErrorStatus() {
Ed Tanouse0d918b2018-03-27 17:41:04 -070064 res.result(boost::beast::http::status::internal_server_error);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010065 }
66
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010067 crow::response& res;
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020068 std::string chassisId{};
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010069 const std::vector<const char*> types;
70};
71
72/**
73 * @brief Creates connections necessary for chassis sensors
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020074 * @param SensorsAsyncResp Pointer to object holding response data
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010075 * @param sensorNames Sensors retrieved from chassis
76 * @param callback Callback for processing gathered connections
77 */
78template <typename Callback>
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020079void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010080 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"};
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010086
87 // Response handler for parsing objects subtree
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020088 auto resp_handler =
89 [ callback{std::move(callback)}, SensorsAsyncResp, sensorNames ](
90 const boost::system::error_code ec, const GetSubTreeType& subtree) {
Ed Tanouse0d918b2018-03-27 17:41:04 -070091 if (ec) {
Kowalski, Kamil588c3f02018-04-03 14:55:27 +020092 SensorsAsyncResp->setErrorStatus();
Ed Tanousdaf36e22018-04-20 16:01:36 -070093 CROW_LOG_ERROR << "resp_handler: Dbus error " << ec;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +010094 return;
95 }
96
97 CROW_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
98
99 // Make unique list of connections only for requested sensor types and
100 // found in the chassis
101 boost::container::flat_set<std::string> connections;
102 // Intrinsic to avoid malloc. Most systems will have < 8 sensor producers
103 connections.reserve(8);
104
105 CROW_LOG_DEBUG << "sensorNames list cout: " << sensorNames.size();
106 for (const std::string& tsensor : sensorNames) {
107 CROW_LOG_DEBUG << "Sensor to find: " << tsensor;
108 }
109
110 for (const std::pair<
111 std::string,
112 std::vector<std::pair<std::string, std::vector<std::string>>>>&
113 object : subtree) {
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200114 for (const char* type : SensorsAsyncResp->types) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100115 if (boost::starts_with(object.first, type)) {
116 auto lastPos = object.first.rfind('/');
117 if (lastPos != std::string::npos) {
118 std::string sensorName = object.first.substr(lastPos + 1);
119
120 if (sensorNames.find(sensorName) != sensorNames.end()) {
121 // For each connection name
122 for (const std::pair<std::string, std::vector<std::string>>&
123 objData : object.second) {
124 connections.insert(objData.first);
125 }
126 }
127 }
128 break;
129 }
130 }
131 }
132 CROW_LOG_DEBUG << "Found " << connections.size() << " connections";
133 callback(std::move(connections));
134 };
135
136 // Make call to ObjectMapper to find all sensors objects
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700137 crow::connections::system_bus->async_method_call(
Ed Tanousdaf36e22018-04-20 16:01:36 -0700138 std::move(resp_handler), "xyz.openbmc_project.ObjectMapper",
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700139 "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper",
140 "GetSubTree", path, 2, interfaces);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100141}
142
143/**
144 * @brief Retrieves requested chassis sensors and redundancy data from DBus .
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200145 * @param SensorsAsyncResp Pointer to object holding response data
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100146 * @param callback Callback for next step in gathered sensor processing
147 */
148template <typename Callback>
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200149void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
150 Callback&& callback) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100151 CROW_LOG_DEBUG << "getChassis Done";
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100152
153 // Process response from EntityManager and extract chassis data
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200154 auto resp_handler = [ callback{std::move(callback)}, SensorsAsyncResp ](
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100155 const boost::system::error_code ec, ManagedObjectsVectorType& resp) {
156 CROW_LOG_DEBUG << "getChassis resp_handler called back Done";
157 if (ec) {
158 CROW_LOG_ERROR << "getChassis resp_handler got error " << ec;
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200159 SensorsAsyncResp->setErrorStatus();
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100160 return;
161 }
162 boost::container::flat_set<std::string> sensorNames;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100163
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200164 // SensorsAsyncResp->chassisId
Ed Tanousdaf36e22018-04-20 16:01:36 -0700165 bool foundChassis = false;
166 std::vector<std::string> split;
167 // Reserve space for
168 // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
169 split.reserve(8);
170
171 for (const auto& objDictEntry : resp) {
172 const std::string& objectPath =
173 static_cast<const std::string&>(objDictEntry.first);
174 boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
175 if (split.size() < 2) {
176 CROW_LOG_ERROR << "Got path that isn't long enough " << objectPath;
177 split.clear();
178 continue;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100179 }
Ed Tanousdaf36e22018-04-20 16:01:36 -0700180 const std::string& sensorName = split.end()[-1];
181 const std::string& chassisName = split.end()[-2];
182
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200183 if (chassisName != SensorsAsyncResp->chassisId) {
Ed Tanousdaf36e22018-04-20 16:01:36 -0700184 split.clear();
185 continue;
186 }
187 foundChassis = true;
188 sensorNames.emplace(sensorName);
189 split.clear();
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100190 };
191 CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
192
193 if (!foundChassis) {
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200194 CROW_LOG_INFO << "Unable to find chassis named "
195 << SensorsAsyncResp->chassisId;
196 SensorsAsyncResp->res.result(boost::beast::http::status::not_found);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100197 } else {
198 callback(sensorNames);
199 }
200 };
201
202 // Make call to EntityManager to find all chassis objects
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700203 crow::connections::system_bus->async_method_call(
204 resp_handler, "xyz.openbmc_project.EntityManager",
Ed Tanousdaf36e22018-04-20 16:01:36 -0700205 "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
206 "GetManagedObjects");
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100207}
208
209/**
210 * @brief Builds a json sensor representation of a sensor.
211 * @param sensorName The name of the sensor to be built
Gunnar Mills274fad52018-06-13 15:45:36 -0500212 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100213 * build
214 * @param interfacesDict A dictionary of the interfaces and properties of said
215 * interfaces to be built from
216 * @param sensor_json The json object to fill
217 */
218void objectInterfacesToJson(
219 const std::string& sensorName, const std::string& sensorType,
220 const boost::container::flat_map<
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700221 std::string, boost::container::flat_map<std::string, SensorVariant>>&
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100222 interfacesDict,
223 nlohmann::json& sensor_json) {
224 // We need a value interface before we can do anything with it
225 auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
226 if (value_it == interfacesDict.end()) {
227 CROW_LOG_ERROR << "Sensor doesn't have a value interface";
228 return;
229 }
230
231 // Assume values exist as is (10^0 == 1) if no scale exists
232 int64_t scaleMultiplier = 0;
233
234 auto scale_it = value_it->second.find("Scale");
235 // If a scale exists, pull value as int64, and use the scaling.
236 if (scale_it != value_it->second.end()) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700237 const int64_t* int64Value =
238 mapbox::get_ptr<const int64_t>(scale_it->second);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100239 if (int64Value != nullptr) {
240 scaleMultiplier = *int64Value;
241 }
242 }
243
244 sensor_json["MemberId"] = sensorName;
245 sensor_json["Name"] = sensorName;
246 sensor_json["Status"]["State"] = "Enabled";
247 sensor_json["Status"]["Health"] = "OK";
248
249 // Parameter to set to override the type we get from dbus, and force it to
250 // int, regardless of what is available. This is used for schemas like fan,
251 // that require integers, not floats.
252 bool forceToInt = false;
253
254 const char* unit = "Reading";
255 if (sensorType == "temperature") {
256 unit = "ReadingCelsius";
257 // TODO(ed) Documentation says that path should be type fan_tach,
258 // implementation seems to implement fan
Ed Tanousdaf36e22018-04-20 16:01:36 -0700259 } else if (sensorType == "fan" || sensorType == "fan_type") {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100260 unit = "Reading";
261 sensor_json["ReadingUnits"] = "RPM";
262 forceToInt = true;
263 } else if (sensorType == "voltage") {
264 unit = "ReadingVolts";
265 } else {
266 CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
267 return;
268 }
269 // Map of dbus interface name, dbus property name and redfish property_name
270 std::vector<std::tuple<const char*, const char*, const char*>> properties;
271 properties.reserve(7);
272
273 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
274 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
275 "WarningHigh", "UpperThresholdNonCritical");
276 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
277 "WarningLow", "LowerThresholdNonCritical");
278 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
279 "CriticalHigh", "UpperThresholdCritical");
280 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
281 "CriticalLow", "LowerThresholdCritical");
282
283 if (sensorType == "temperature") {
284 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
285 "MinReadingRangeTemp");
286 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
287 "MaxReadingRangeTemp");
288 } else {
289 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
290 "MinReadingRange");
291 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
292 "MaxReadingRange");
293 }
294
295 for (const std::tuple<const char*, const char*, const char*>& p :
296 properties) {
297 auto interfaceProperties = interfacesDict.find(std::get<0>(p));
298 if (interfaceProperties != interfacesDict.end()) {
299 auto value_it = interfaceProperties->second.find(std::get<1>(p));
300 if (value_it != interfaceProperties->second.end()) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700301 const SensorVariant& valueVariant = value_it->second;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100302 nlohmann::json& value_it = sensor_json[std::get<2>(p)];
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700303
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100304 // Attempt to pull the int64 directly
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700305 const int64_t* int64Value =
306 mapbox::get_ptr<const int64_t>(valueVariant);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100307
308 if (int64Value != nullptr) {
309 if (forceToInt || scaleMultiplier >= 0) {
310 value_it = *int64Value * std::pow(10, scaleMultiplier);
311 } else {
312 value_it = *int64Value *
313 std::pow(10, static_cast<double>(scaleMultiplier));
314 }
315 }
316 // Attempt to pull the float directly
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700317 const double* doubleValue = mapbox::get_ptr<const double>(valueVariant);
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100318
319 if (doubleValue != nullptr) {
320 if (!forceToInt) {
321 value_it = *doubleValue *
322 std::pow(10, static_cast<double>(scaleMultiplier));
323 } else {
324 value_it = static_cast<int64_t>(*doubleValue *
325 std::pow(10, scaleMultiplier));
326 }
327 }
328 }
329 }
330 }
Ed Tanouse0d918b2018-03-27 17:41:04 -0700331 CROW_LOG_DEBUG << "Added sensor " << sensorName;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100332}
333
334/**
335 * @brief Entry point for retrieving sensors data related to requested
336 * chassis.
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200337 * @param SensorsAsyncResp Pointer to object holding response data
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100338 */
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200339void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100340 CROW_LOG_DEBUG << "getChassisData";
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200341 auto getChassisCb = [&, SensorsAsyncResp](
342 boost::container::flat_set<std::string>&
343 sensorNames) {
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100344 CROW_LOG_DEBUG << "getChassisCb Done";
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200345 auto getConnectionCb =
346 [&, SensorsAsyncResp, sensorNames](
347 const boost::container::flat_set<std::string>& connections) {
348 CROW_LOG_DEBUG << "getConnectionCb Done";
349 // Get managed objects from all services exposing sensors
350 for (const std::string& connection : connections) {
351 // Response handler to process managed objects
352 auto getManagedObjectsCb = [&, SensorsAsyncResp, sensorNames](
353 const boost::system::error_code ec,
354 ManagedObjectsVectorType& resp) {
355 // Go through all objects and update response with
356 // sensor data
357 for (const auto& objDictEntry : resp) {
358 const std::string& objPath =
359 static_cast<const std::string&>(objDictEntry.first);
360 CROW_LOG_DEBUG << "getManagedObjectsCb parsing object "
361 << objPath;
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100362
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200363 std::vector<std::string> split;
364 // Reserve space for
365 // /xyz/openbmc_project/Sensors/<name>/<subname>
366 split.reserve(6);
367 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
368 if (split.size() < 6) {
369 CROW_LOG_ERROR << "Got path that isn't long enough "
370 << objPath;
371 continue;
372 }
373 // These indexes aren't intuitive, as boost::split puts an empty
374 // string at the beggining
375 const std::string& sensorType = split[4];
376 const std::string& sensorName = split[5];
377 CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType "
378 << sensorType;
379 if (sensorNames.find(sensorName) == sensorNames.end()) {
380 CROW_LOG_ERROR << sensorName << " not in sensor list ";
381 continue;
382 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100383
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200384 const char* fieldName = nullptr;
385 if (sensorType == "temperature") {
386 fieldName = "Temperatures";
387 } else if (sensorType == "fan" || sensorType == "fan_tach") {
388 fieldName = "Fans";
389 } else if (sensorType == "voltage") {
390 fieldName = "Voltages";
391 } else if (sensorType == "current") {
392 fieldName = "PowerSupply";
393 } else if (sensorType == "power") {
394 fieldName = "PowerSupply";
395 } else {
396 CROW_LOG_ERROR << "Unsure how to handle sensorType "
397 << sensorType;
398 continue;
399 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100400
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200401 nlohmann::json& temp_array =
402 SensorsAsyncResp->res.json_value[fieldName];
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100403
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200404 // Create the array if it doesn't yet exist
405 if (temp_array.is_array() == false) {
406 temp_array = nlohmann::json::array();
407 }
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100408
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200409 temp_array.push_back(
410 {{"@odata.id", "/redfish/v1/Chassis/" +
411 SensorsAsyncResp->chassisId +
412 "/Thermal#/" + sensorName}});
413 nlohmann::json& sensor_json = temp_array.back();
414 objectInterfacesToJson(sensorName, sensorType,
415 objDictEntry.second, sensor_json);
416 }
417 };
418
419 crow::connections::system_bus->async_method_call(
420 getManagedObjectsCb, connection, "/xyz/openbmc_project/Sensors",
421 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
422 };
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100423 };
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100424 // Get connections and then pass it to get sensors
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200425 getConnections(SensorsAsyncResp, sensorNames, std::move(getConnectionCb));
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100426 };
427
428 // Get chassis information related to sensors
Kowalski, Kamil588c3f02018-04-03 14:55:27 +0200429 getChassis(SensorsAsyncResp, std::move(getChassisCb));
Lewanczyk, Dawid08777fb2018-03-22 23:33:49 +0100430};
431
432} // namespace redfish