xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 8bd25ccda8030e5725ecdf5fa64d6083040ddf8a)
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 
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 <dbus_singleton.hpp>
25 #include <utils/json_utils.hpp>
26 #include <variant>
27 
28 namespace redfish
29 {
30 
31 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/";
32 
33 using GetSubTreeType = std::vector<
34     std::pair<std::string,
35               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
36 
37 using SensorVariant = std::variant<int64_t, double>;
38 
39 using ManagedObjectsVectorType = std::vector<std::pair<
40     sdbusplus::message::object_path,
41     boost::container::flat_map<
42         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
43 
44 /**
45  * SensorsAsyncResp
46  * Gathers data needed for response processing after async calls are done
47  */
48 class SensorsAsyncResp
49 {
50   public:
51     SensorsAsyncResp(crow::Response& response, const std::string& chassisId,
52                      const std::initializer_list<const char*> types,
53                      const std::string& subNode) :
54         res(response),
55         chassisId(chassisId), types(types), chassisSubNode(subNode)
56     {
57         res.jsonValue["@odata.id"] =
58             "/redfish/v1/Chassis/" + chassisId + "/" + subNode;
59     }
60 
61     ~SensorsAsyncResp()
62     {
63         if (res.result() == boost::beast::http::status::internal_server_error)
64         {
65             // Reset the json object to clear out any data that made it in
66             // before the error happened todo(ed) handle error condition with
67             // proper code
68             res.jsonValue = nlohmann::json::object();
69         }
70         res.end();
71     }
72 
73     crow::Response& res;
74     std::string chassisId{};
75     const std::vector<const char*> types;
76     std::string chassisSubNode{};
77 };
78 
79 /**
80  * @brief Checks whether the specified sensor is one of the requested types.
81  * @param objectPath DBus object path of the sensor.
82  * @param SensorsAsyncResp Pointer to object holding response data and requested
83  * sensor types.
84  * @return true if sensor type is requested, false otherwise.
85  */
86 inline bool
87     isRequestedSensorType(const std::string& objectPath,
88                           std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
89 {
90     for (const char* type : SensorsAsyncResp->types)
91     {
92         if (boost::starts_with(objectPath, type))
93         {
94             return true;
95         }
96     }
97     return false;
98 }
99 
100 /**
101  * @brief Get objects with connection necessary for sensors
102  * @param SensorsAsyncResp Pointer to object holding response data
103  * @param sensorNames Sensors retrieved from chassis
104  * @param callback Callback for processing gathered connections
105  */
106 template <typename Callback>
107 void getObjectsWithConnection(
108     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
109     const boost::container::flat_set<std::string>& sensorNames,
110     Callback&& callback)
111 {
112     BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter";
113     const std::string path = "/xyz/openbmc_project/sensors";
114     const std::array<std::string, 1> interfaces = {
115         "xyz.openbmc_project.Sensor.Value"};
116 
117     // Response handler for parsing objects subtree
118     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
119                         sensorNames](const boost::system::error_code ec,
120                                      const GetSubTreeType& subtree) {
121         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter";
122         if (ec)
123         {
124             messages::internalError(SensorsAsyncResp->res);
125             BMCWEB_LOG_ERROR
126                 << "getObjectsWithConnection resp_handler: Dbus error " << ec;
127             return;
128         }
129 
130         BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
131 
132         // Make unique list of connections only for requested sensor types and
133         // found in the chassis
134         boost::container::flat_set<std::string> connections;
135         std::set<std::pair<std::string, std::string>> objectsWithConnection;
136         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
137         // producers
138         connections.reserve(8);
139 
140         BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames.size();
141         for (const std::string& tsensor : sensorNames)
142         {
143             BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor;
144         }
145 
146         for (const std::pair<
147                  std::string,
148                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
149                  object : subtree)
150         {
151             if (isRequestedSensorType(object.first, SensorsAsyncResp))
152             {
153                 auto lastPos = object.first.rfind('/');
154                 if (lastPos != std::string::npos)
155                 {
156                     std::string sensorName = object.first.substr(lastPos + 1);
157 
158                     if (sensorNames.find(sensorName) != sensorNames.end())
159                     {
160                         // For each Connection name
161                         for (const std::pair<std::string,
162                                              std::vector<std::string>>&
163                                  objData : object.second)
164                         {
165                             BMCWEB_LOG_DEBUG << "Adding connection: "
166                                              << objData.first;
167                             connections.insert(objData.first);
168                             objectsWithConnection.insert(
169                                 std::make_pair(object.first, objData.first));
170                         }
171                     }
172                 }
173             }
174         }
175         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
176         callback(std::move(connections), std::move(objectsWithConnection));
177         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit";
178     };
179     // Make call to ObjectMapper to find all sensors objects
180     crow::connections::systemBus->async_method_call(
181         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
182         "/xyz/openbmc_project/object_mapper",
183         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
184     BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit";
185 }
186 
187 /**
188  * @brief Create connections necessary for sensors
189  * @param SensorsAsyncResp Pointer to object holding response data
190  * @param sensorNames Sensors retrieved from chassis
191  * @param callback Callback for processing gathered connections
192  */
193 template <typename Callback>
194 void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
195                     const boost::container::flat_set<std::string>& sensorNames,
196                     Callback&& callback)
197 {
198     auto objectsWithConnectionCb =
199         [callback](const boost::container::flat_set<std::string>& connections,
200                    const std::set<std::pair<std::string, std::string>>&
201                        objectsWithConnection) {
202             callback(std::move(connections));
203         };
204     getObjectsWithConnection(SensorsAsyncResp, sensorNames,
205                              std::move(objectsWithConnectionCb));
206 }
207 
208 /**
209  * @brief Gets all DBus sensor names.
210  *
211  * Finds the sensor names asynchronously.  Invokes callback when information has
212  * been obtained.
213  *
214  * The callback must have the following signature:
215  *   @code
216  *   callback(boost::container::flat_set<std::string>& sensorNames)
217  *   @endcode
218  *
219  * @param SensorsAsyncResp Pointer to object holding response data.
220  * @param callback Callback to invoke when sensor names obtained.
221  */
222 template <typename Callback>
223 void getAllSensors(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
224                    Callback&& callback)
225 {
226     BMCWEB_LOG_DEBUG << "getAllSensors enter";
227     const std::array<std::string, 1> interfaces = {
228         "xyz.openbmc_project.Sensor.Value"};
229 
230     // Response handler for GetSubTreePaths DBus method
231     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp](
232                            const boost::system::error_code ec,
233                            const std::vector<std::string>& sensorList) {
234         BMCWEB_LOG_DEBUG << "getAllSensors respHandler enter";
235         if (ec)
236         {
237             messages::internalError(SensorsAsyncResp->res);
238             BMCWEB_LOG_ERROR << "getAllSensors respHandler: DBus error " << ec;
239             return;
240         }
241         boost::container::flat_set<std::string> sensorNames;
242         for (const std::string& sensor : sensorList)
243         {
244             std::size_t lastPos = sensor.rfind("/");
245             if (lastPos == std::string::npos)
246             {
247                 BMCWEB_LOG_ERROR << "Failed to find '/' in " << sensor;
248                 continue;
249             }
250             std::string sensorName = sensor.substr(lastPos + 1);
251             sensorNames.emplace(sensorName);
252             BMCWEB_LOG_DEBUG << "Added sensor name " << sensorName;
253         }
254         callback(sensorNames);
255         BMCWEB_LOG_DEBUG << "getAllSensors respHandler exit";
256     };
257 
258     // Query mapper for all DBus sensor object paths
259     crow::connections::systemBus->async_method_call(
260         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
261         "/xyz/openbmc_project/object_mapper",
262         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
263         "/xyz/openbmc_project/sensors", int32_t(0), interfaces);
264     BMCWEB_LOG_DEBUG << "getAllSensors exit";
265 }
266 
267 /**
268  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
269  * @param SensorsAsyncResp   Pointer to object holding response data
270  * @param callback  Callback for next step in gathered sensor processing
271  */
272 template <typename Callback>
273 void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
274                 Callback&& callback)
275 {
276     BMCWEB_LOG_DEBUG << "getChassis enter";
277     // Process response from EntityManager and extract chassis data
278     auto respHandler = [callback{std::move(callback)},
279                         SensorsAsyncResp](const boost::system::error_code ec,
280                                           ManagedObjectsVectorType& resp) {
281         BMCWEB_LOG_DEBUG << "getChassis respHandler enter";
282         if (ec)
283         {
284             BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec;
285             messages::internalError(SensorsAsyncResp->res);
286             return;
287         }
288         boost::container::flat_set<std::string> sensorNames;
289 
290         //   SensorsAsyncResp->chassisId
291         bool foundChassis = false;
292         std::vector<std::string> split;
293         // Reserve space for
294         // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
295         split.reserve(8);
296 
297         for (const auto& objDictEntry : resp)
298         {
299             const std::string& objectPath =
300                 static_cast<const std::string&>(objDictEntry.first);
301             boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
302             if (split.size() < 2)
303             {
304                 BMCWEB_LOG_ERROR << "Got path that isn't long enough "
305                                  << objectPath;
306                 split.clear();
307                 continue;
308             }
309             const std::string& sensorName = split.end()[-1];
310             const std::string& chassisName = split.end()[-2];
311 
312             if (chassisName != SensorsAsyncResp->chassisId)
313             {
314                 split.clear();
315                 continue;
316             }
317             BMCWEB_LOG_DEBUG << "New sensor: " << sensorName;
318             foundChassis = true;
319             sensorNames.emplace(sensorName);
320             split.clear();
321         };
322         BMCWEB_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
323 
324         if (!foundChassis)
325         {
326             BMCWEB_LOG_INFO << "Unable to find chassis named "
327                             << SensorsAsyncResp->chassisId;
328             messages::resourceNotFound(SensorsAsyncResp->res, "Chassis",
329                                        SensorsAsyncResp->chassisId);
330         }
331         else
332         {
333             callback(sensorNames);
334         }
335         BMCWEB_LOG_DEBUG << "getChassis respHandler exit";
336     };
337 
338     // Make call to EntityManager to find all chassis objects
339     crow::connections::systemBus->async_method_call(
340         respHandler, "xyz.openbmc_project.EntityManager", "/",
341         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
342     BMCWEB_LOG_DEBUG << "getChassis exit";
343 }
344 
345 /**
346  * @brief Finds all DBus object paths that implement ObjectManager.
347  *
348  * Creates a mapping from the associated connection name to the object path.
349  *
350  * Finds the object paths asynchronously.  Invokes callback when information has
351  * been obtained.
352  *
353  * The callback must have the following signature:
354  *   @code
355  *   callback(const boost::container::flat_map<std::string,
356  *            std::string>& objectMgrPaths)
357  *   @endcode
358  *
359  * @param SensorsAsyncResp Pointer to object holding response data.
360  * @param callback Callback to invoke when object paths obtained.
361  */
362 template <typename Callback>
363 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
364                            Callback&& callback)
365 {
366     BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter";
367     const std::array<std::string, 1> interfaces = {
368         "org.freedesktop.DBus.ObjectManager"};
369 
370     // Response handler for GetSubTree DBus method
371     auto respHandler = [callback{std::move(callback)},
372                         SensorsAsyncResp](const boost::system::error_code ec,
373                                           const GetSubTreeType& subtree) {
374         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter";
375         if (ec)
376         {
377             messages::internalError(SensorsAsyncResp->res);
378             BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error "
379                              << ec;
380             return;
381         }
382 
383         // Loop over returned object paths
384         boost::container::flat_map<std::string, std::string> objectMgrPaths;
385         for (const std::pair<
386                  std::string,
387                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
388                  object : subtree)
389         {
390             // Loop over connections for current object path
391             const std::string& objectPath = object.first;
392             for (const std::pair<std::string, std::vector<std::string>>&
393                      objData : object.second)
394             {
395                 // Add mapping from connection to object path
396                 const std::string& connection = objData.first;
397                 objectMgrPaths[connection] = objectPath;
398                 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> "
399                                  << objectPath;
400             }
401         }
402         callback(std::move(objectMgrPaths));
403         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit";
404     };
405 
406     // Query mapper for all DBus object paths that implement ObjectManager
407     crow::connections::systemBus->async_method_call(
408         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
409         "/xyz/openbmc_project/object_mapper",
410         "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
411         interfaces);
412     BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit";
413 }
414 
415 /**
416  * @brief Builds a json sensor representation of a sensor.
417  * @param sensorName  The name of the sensor to be built
418  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
419  * build
420  * @param interfacesDict  A dictionary of the interfaces and properties of said
421  * interfaces to be built from
422  * @param sensor_json  The json object to fill
423  */
424 void objectInterfacesToJson(
425     const std::string& sensorName, const std::string& sensorType,
426     const boost::container::flat_map<
427         std::string, boost::container::flat_map<std::string, SensorVariant>>&
428         interfacesDict,
429     nlohmann::json& sensor_json)
430 {
431     // We need a value interface before we can do anything with it
432     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
433     if (valueIt == interfacesDict.end())
434     {
435         BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface";
436         return;
437     }
438 
439     // Assume values exist as is (10^0 == 1) if no scale exists
440     int64_t scaleMultiplier = 0;
441 
442     auto scaleIt = valueIt->second.find("Scale");
443     // If a scale exists, pull value as int64, and use the scaling.
444     if (scaleIt != valueIt->second.end())
445     {
446         const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second);
447         if (int64Value != nullptr)
448         {
449             scaleMultiplier = *int64Value;
450         }
451     }
452 
453     sensor_json["MemberId"] = sensorName;
454     sensor_json["Name"] = sensorName;
455     sensor_json["Status"]["State"] = "Enabled";
456     sensor_json["Status"]["Health"] = "OK";
457 
458     // Parameter to set to override the type we get from dbus, and force it to
459     // int, regardless of what is available.  This is used for schemas like fan,
460     // that require integers, not floats.
461     bool forceToInt = false;
462 
463     const char* unit = "Reading";
464     if (sensorType == "temperature")
465     {
466         unit = "ReadingCelsius";
467         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature";
468         // TODO(ed) Documentation says that path should be type fan_tach,
469         // implementation seems to implement fan
470     }
471     else if (sensorType == "fan" || sensorType == "fan_tach")
472     {
473         unit = "Reading";
474         sensor_json["ReadingUnits"] = "RPM";
475         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
476         forceToInt = true;
477     }
478     else if (sensorType == "fan_pwm")
479     {
480         unit = "Reading";
481         sensor_json["ReadingUnits"] = "Percent";
482         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
483         forceToInt = true;
484     }
485     else if (sensorType == "voltage")
486     {
487         unit = "ReadingVolts";
488         sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage";
489     }
490     else if (sensorType == "power")
491     {
492         unit = "LastPowerOutputWatts";
493     }
494     else
495     {
496         BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
497         return;
498     }
499     // Map of dbus interface name, dbus property name and redfish property_name
500     std::vector<std::tuple<const char*, const char*, const char*>> properties;
501     properties.reserve(7);
502 
503     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
504 
505     // If sensor type doesn't map to Redfish PowerSupply, add threshold props
506     if ((sensorType != "current") && (sensorType != "power"))
507     {
508         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
509                                 "WarningHigh", "UpperThresholdNonCritical");
510         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
511                                 "WarningLow", "LowerThresholdNonCritical");
512         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
513                                 "CriticalHigh", "UpperThresholdCritical");
514         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
515                                 "CriticalLow", "LowerThresholdCritical");
516     }
517 
518     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
519 
520     if (sensorType == "temperature")
521     {
522         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
523                                 "MinReadingRangeTemp");
524         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
525                                 "MaxReadingRangeTemp");
526     }
527     else if ((sensorType != "current") && (sensorType != "power"))
528     {
529         // Sensor type doesn't map to Redfish PowerSupply; add min/max props
530         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
531                                 "MinReadingRange");
532         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
533                                 "MaxReadingRange");
534     }
535 
536     for (const std::tuple<const char*, const char*, const char*>& p :
537          properties)
538     {
539         auto interfaceProperties = interfacesDict.find(std::get<0>(p));
540         if (interfaceProperties != interfacesDict.end())
541         {
542             auto valueIt = interfaceProperties->second.find(std::get<1>(p));
543             if (valueIt != interfaceProperties->second.end())
544             {
545                 const SensorVariant& valueVariant = valueIt->second;
546                 nlohmann::json& valueIt = sensor_json[std::get<2>(p)];
547                 // Attempt to pull the int64 directly
548                 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant);
549 
550                 const double* doubleValue = std::get_if<double>(&valueVariant);
551                 double temp = 0.0;
552                 if (int64Value != nullptr)
553                 {
554                     temp = *int64Value;
555                 }
556                 else if (doubleValue != nullptr)
557                 {
558                     temp = *doubleValue;
559                 }
560                 else
561                 {
562                     BMCWEB_LOG_ERROR
563                         << "Got value interface that wasn't int or double";
564                     continue;
565                 }
566                 temp = temp * std::pow(10, scaleMultiplier);
567                 if (forceToInt)
568                 {
569                     valueIt = static_cast<int64_t>(temp);
570                 }
571                 else
572                 {
573                     valueIt = temp;
574                 }
575             }
576         }
577     }
578     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
579 }
580 
581 static void
582     populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp)
583 {
584     crow::connections::systemBus->async_method_call(
585         [sensorsAsyncResp](const boost::system::error_code ec,
586                            const GetSubTreeType& resp) {
587             if (ec)
588             {
589                 return; // don't have to have this interface
590             }
591             for (const auto& [path, objDict] : resp)
592             {
593                 if (objDict.empty())
594                 {
595                     continue; // this should be impossible
596                 }
597 
598                 const std::string& owner = objDict.begin()->first;
599                 crow::connections::systemBus->async_method_call(
600                     [path, owner,
601                      sensorsAsyncResp](const boost::system::error_code ec,
602                                        std::variant<std::vector<std::string>>
603                                            variantEndpoints) {
604                         if (ec)
605                         {
606                             return; // if they don't have an association we
607                                     // can't tell what chassis is
608                         }
609                         // verify part of the right chassis
610                         auto endpoints = std::get_if<std::vector<std::string>>(
611                             &variantEndpoints);
612 
613                         if (endpoints == nullptr)
614                         {
615                             BMCWEB_LOG_ERROR << "Invalid association interface";
616                             messages::internalError(sensorsAsyncResp->res);
617                             return;
618                         }
619 
620                         auto found = std::find_if(
621                             endpoints->begin(), endpoints->end(),
622                             [sensorsAsyncResp](const std::string& entry) {
623                                 return entry.find(
624                                            sensorsAsyncResp->chassisId) !=
625                                        std::string::npos;
626                             });
627 
628                         if (found == endpoints->end())
629                         {
630                             return;
631                         }
632                         crow::connections::systemBus->async_method_call(
633                             [path, sensorsAsyncResp](
634                                 const boost::system::error_code ec,
635                                 const boost::container::flat_map<
636                                     std::string,
637                                     std::variant<uint8_t,
638                                                  std::vector<std::string>,
639                                                  std::string>>& ret) {
640                                 if (ec)
641                                 {
642                                     return; // don't have to have this
643                                             // interface
644                                 }
645                                 auto findFailures = ret.find("AllowedFailures");
646                                 auto findCollection = ret.find("Collection");
647                                 auto findStatus = ret.find("Status");
648 
649                                 if (findFailures == ret.end() ||
650                                     findCollection == ret.end() ||
651                                     findStatus == ret.end())
652                                 {
653                                     BMCWEB_LOG_ERROR
654                                         << "Invalid redundancy interface";
655                                     messages::internalError(
656                                         sensorsAsyncResp->res);
657                                     return;
658                                 }
659 
660                                 auto allowedFailures = std::get_if<uint8_t>(
661                                     &(findFailures->second));
662                                 auto collection =
663                                     std::get_if<std::vector<std::string>>(
664                                         &(findCollection->second));
665                                 auto status = std::get_if<std::string>(
666                                     &(findStatus->second));
667 
668                                 if (allowedFailures == nullptr ||
669                                     collection == nullptr || status == nullptr)
670                                 {
671 
672                                     BMCWEB_LOG_ERROR
673                                         << "Invalid redundancy interface "
674                                            "types";
675                                     messages::internalError(
676                                         sensorsAsyncResp->res);
677                                     return;
678                                 }
679                                 size_t lastSlash = path.rfind("/");
680                                 if (lastSlash == std::string::npos)
681                                 {
682                                     // this should be impossible
683                                     messages::internalError(
684                                         sensorsAsyncResp->res);
685                                     return;
686                                 }
687                                 std::string name = path.substr(lastSlash + 1);
688                                 std::replace(name.begin(), name.end(), '_',
689                                              ' ');
690 
691                                 std::string health;
692 
693                                 if (boost::ends_with(*status, "Full"))
694                                 {
695                                     health = "OK";
696                                 }
697                                 else if (boost::ends_with(*status, "Degraded"))
698                                 {
699                                     health = "Warning";
700                                 }
701                                 else
702                                 {
703                                     health = "Critical";
704                                 }
705                                 std::vector<nlohmann::json> redfishCollection;
706                                 const auto& fanRedfish =
707                                     sensorsAsyncResp->res.jsonValue["Fans"];
708                                 for (const std::string& item : *collection)
709                                 {
710                                     lastSlash = item.rfind("/");
711                                     // make a copy as collection is const
712                                     std::string itemName =
713                                         item.substr(lastSlash + 1);
714                                     /*
715                                     todo(ed): merge patch that fixes the names
716                                     std::replace(itemName.begin(),
717                                                  itemName.end(), '_', ' ');*/
718                                     auto schemaItem = std::find_if(
719                                         fanRedfish.begin(), fanRedfish.end(),
720                                         [itemName](const nlohmann::json& fan) {
721                                             return fan["MemberId"] == itemName;
722                                         });
723                                     if (schemaItem != fanRedfish.end())
724                                     {
725                                         redfishCollection.push_back(
726                                             {{"@odata.id",
727                                               (*schemaItem)["@odata.id"]}});
728                                     }
729                                     else
730                                     {
731                                         BMCWEB_LOG_ERROR
732                                             << "failed to find fan in schema";
733                                         messages::internalError(
734                                             sensorsAsyncResp->res);
735                                         return;
736                                     }
737                                 }
738 
739                                 auto& resp = sensorsAsyncResp->res
740                                                  .jsonValue["Redundancy"];
741                                 resp.push_back(
742                                     {{"@odata.id",
743                                       "/refish/v1/Chassis/" +
744                                           sensorsAsyncResp->chassisId + "/" +
745                                           sensorsAsyncResp->chassisSubNode +
746                                           "#/Redundancy/" +
747                                           std::to_string(resp.size())},
748                                      {"@odata.type",
749                                       "#Redundancy.v1_3_2.Redundancy"},
750                                      {"MinNumNeeded",
751                                       collection->size() - *allowedFailures},
752                                      {"MemberId", name},
753                                      {"Mode", "N+m"},
754                                      {"Name", name},
755                                      {"RedundancySet", redfishCollection},
756                                      {"Status",
757                                       {{"Health", health},
758                                        {"State", "Enabled"}}}});
759                             },
760                             owner, path, "org.freedesktop.DBus.Properties",
761                             "GetAll",
762                             "xyz.openbmc_project.Control.FanRedundancy");
763                     },
764                     "xyz.openbmc_project.ObjectMapper", path + "/inventory",
765                     "org.freedesktop.DBus.Properties", "Get",
766                     "xyz.openbmc_project.Association", "endpoints");
767             }
768         },
769         "xyz.openbmc_project.ObjectMapper",
770         "/xyz/openbmc_project/object_mapper",
771         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
772         "/xyz/openbmc_project/control", 2,
773         std::array<const char*, 1>{
774             "xyz.openbmc_project.Control.FanRedundancy"});
775 }
776 
777 /**
778  * @brief Gets the values of the specified sensors.
779  *
780  * Stores the results as JSON in the SensorsAsyncResp.
781  *
782  * Gets the sensor values asynchronously.  Stores the results later when the
783  * information has been obtained.
784  *
785  * The sensorNames set contains all sensors for the current chassis.
786  * SensorsAsyncResp contains the requested sensor types.  Only sensors of a
787  * requested type are included in the JSON output.
788  *
789  * To minimize the number of DBus calls, the DBus method
790  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
791  * values of all sensors provided by a connection (service).
792  *
793  * The connections set contains all the connections that provide sensor values.
794  *
795  * The objectMgrPaths map contains mappings from a connection name to the
796  * corresponding DBus object path that implements ObjectManager.
797  *
798  * @param SensorsAsyncResp Pointer to object holding response data.
799  * @param sensorNames All sensors within the current chassis.
800  * @param connections Connections that provide sensor values.
801  * @param objectMgrPaths Mappings from connection name to DBus object path that
802  * implements ObjectManager.
803  */
804 void getSensorData(
805     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
806     const boost::container::flat_set<std::string>& sensorNames,
807     const boost::container::flat_set<std::string>& connections,
808     const boost::container::flat_map<std::string, std::string>& objectMgrPaths)
809 {
810     BMCWEB_LOG_DEBUG << "getSensorData enter";
811     // Get managed objects from all services exposing sensors
812     for (const std::string& connection : connections)
813     {
814         // Response handler to process managed objects
815         auto getManagedObjectsCb = [&, SensorsAsyncResp, sensorNames](
816                                        const boost::system::error_code ec,
817                                        ManagedObjectsVectorType& resp) {
818             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
819             if (ec)
820             {
821                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
822                 messages::internalError(SensorsAsyncResp->res);
823                 return;
824             }
825             // Go through all objects and update response with sensor data
826             for (const auto& objDictEntry : resp)
827             {
828                 const std::string& objPath =
829                     static_cast<const std::string&>(objDictEntry.first);
830                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
831                                  << objPath;
832 
833                 // Skip sensor if it is not one of the requested types
834                 if (!isRequestedSensorType(objPath, SensorsAsyncResp))
835                 {
836                     continue;
837                 }
838 
839                 std::vector<std::string> split;
840                 // Reserve space for
841                 // /xyz/openbmc_project/sensors/<name>/<subname>
842                 split.reserve(6);
843                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
844                 if (split.size() < 6)
845                 {
846                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
847                                      << objPath;
848                     continue;
849                 }
850                 // These indexes aren't intuitive, as boost::split puts an empty
851                 // string at the beginning
852                 const std::string& sensorType = split[4];
853                 const std::string& sensorName = split[5];
854                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
855                                  << " sensorType " << sensorType;
856                 if (sensorNames.find(sensorName) == sensorNames.end())
857                 {
858                     BMCWEB_LOG_ERROR << sensorName << " not in sensor list ";
859                     continue;
860                 }
861 
862                 const char* fieldName = nullptr;
863                 if (sensorType == "temperature")
864                 {
865                     fieldName = "Temperatures";
866                 }
867                 else if (sensorType == "fan" || sensorType == "fan_tach" ||
868                          sensorType == "fan_pwm")
869                 {
870                     fieldName = "Fans";
871                 }
872                 else if (sensorType == "voltage")
873                 {
874                     fieldName = "Voltages";
875                 }
876                 else if (sensorType == "current")
877                 {
878                     fieldName = "PowerSupplies";
879                 }
880                 else if (sensorType == "power")
881                 {
882                     fieldName = "PowerSupplies";
883                 }
884                 else
885                 {
886                     BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
887                                      << sensorType;
888                     continue;
889                 }
890 
891                 nlohmann::json& tempArray =
892                     SensorsAsyncResp->res.jsonValue[fieldName];
893 
894                 tempArray.push_back(
895                     {{"@odata.id",
896                       "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId +
897                           "/" + SensorsAsyncResp->chassisSubNode + "#/" +
898                           fieldName + "/" + std::to_string(tempArray.size())}});
899                 nlohmann::json& sensorJson = tempArray.back();
900 
901                 objectInterfacesToJson(sensorName, sensorType,
902                                        objDictEntry.second, sensorJson);
903             }
904             if (SensorsAsyncResp.use_count() == 1 &&
905                 SensorsAsyncResp->chassisSubNode == "Thermal")
906             {
907                 populateFanRedundancy(SensorsAsyncResp);
908             }
909             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
910         };
911 
912         // Find DBus object path that implements ObjectManager for the current
913         // connection.  If no mapping found, default to "/".
914         auto iter = objectMgrPaths.find(connection);
915         const std::string& objectMgrPath =
916             (iter != objectMgrPaths.end()) ? iter->second : "/";
917         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
918                          << objectMgrPath;
919 
920         crow::connections::systemBus->async_method_call(
921             getManagedObjectsCb, connection, objectMgrPath,
922             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
923     };
924     BMCWEB_LOG_DEBUG << "getSensorData exit";
925 }
926 
927 /**
928  * @brief Entry point for retrieving sensors data related to requested
929  *        chassis.
930  * @param SensorsAsyncResp   Pointer to object holding response data
931  */
932 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
933 {
934     BMCWEB_LOG_DEBUG << "getChassisData enter";
935     auto getChassisCb = [&, SensorsAsyncResp](
936                             boost::container::flat_set<std::string>&
937                                 sensorNames) {
938         BMCWEB_LOG_DEBUG << "getChassisCb enter";
939         auto getConnectionCb =
940             [&, SensorsAsyncResp, sensorNames](
941                 const boost::container::flat_set<std::string>& connections) {
942                 BMCWEB_LOG_DEBUG << "getConnectionCb enter";
943                 auto getObjectManagerPathsCb =
944                     [SensorsAsyncResp, sensorNames,
945                      connections](const boost::container::flat_map<
946                                   std::string, std::string>& objectMgrPaths) {
947                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
948                         // Get sensor data and store results in JSON response
949                         getSensorData(SensorsAsyncResp, sensorNames,
950                                       connections, objectMgrPaths);
951                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
952                     };
953 
954                 // Get mapping from connection names to the DBus object paths
955                 // that implement the ObjectManager interface
956                 getObjectManagerPaths(SensorsAsyncResp,
957                                       std::move(getObjectManagerPathsCb));
958                 BMCWEB_LOG_DEBUG << "getConnectionCb exit";
959             };
960 
961         // Get set of connections that provide sensor values
962         getConnections(SensorsAsyncResp, sensorNames,
963                        std::move(getConnectionCb));
964         BMCWEB_LOG_DEBUG << "getChassisCb exit";
965     };
966 
967 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS
968     // Get all sensor names
969     getAllSensors(SensorsAsyncResp, std::move(getChassisCb));
970 #else
971     // Get sensor names in chassis
972     getChassis(SensorsAsyncResp, std::move(getChassisCb));
973 #endif
974     BMCWEB_LOG_DEBUG << "getChassisData exit";
975 };
976 
977 /**
978  * @brief Entry point for overriding sensor values of given sensor
979  *
980  * @param res   response object
981  * @param req   request object
982  * @param params   parameter passed for CRUD
983  * @param typeList   TypeList of sensors for the resource queried
984  * @param chassisSubNode   Chassis Node for which the query has to happen
985  */
986 void setSensorOverride(crow::Response& res, const crow::Request& req,
987                        const std::vector<std::string>& params,
988                        const std::initializer_list<const char*> typeList,
989                        const std::string& chassisSubNode)
990 {
991 
992     // TODO: Need to figure out dynamic way to restrict patch (Set Sensor
993     // override) based on another d-bus announcement to be more generic.
994     if (params.size() != 1)
995     {
996         messages::internalError(res);
997         res.end();
998         return;
999     }
1000 
1001     std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections;
1002     std::optional<std::vector<nlohmann::json>> temperatureCollections;
1003     std::optional<std::vector<nlohmann::json>> fanCollections;
1004     std::vector<nlohmann::json> voltageCollections;
1005     BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode
1006                     << "\n";
1007 
1008     if (chassisSubNode == "Thermal")
1009     {
1010         if (!json_util::readJson(req, res, "Temperatures",
1011                                  temperatureCollections, "Fans",
1012                                  fanCollections))
1013         {
1014             return;
1015         }
1016         if (!temperatureCollections && !fanCollections)
1017         {
1018             messages::resourceNotFound(res, "Thermal",
1019                                        "Temperatures / Voltages");
1020             res.end();
1021             return;
1022         }
1023         if (temperatureCollections)
1024         {
1025             allCollections.emplace("Temperatures",
1026                                    *std::move(temperatureCollections));
1027         }
1028         if (fanCollections)
1029         {
1030             allCollections.emplace("Fans", *std::move(fanCollections));
1031         }
1032     }
1033     else if (chassisSubNode == "Power")
1034     {
1035         if (!json_util::readJson(req, res, "Voltages", voltageCollections))
1036         {
1037             return;
1038         }
1039         allCollections.emplace("Voltages", std::move(voltageCollections));
1040     }
1041     else
1042     {
1043         res.result(boost::beast::http::status::not_found);
1044         res.end();
1045         return;
1046     }
1047 
1048     const char* propertyValueName;
1049     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
1050     std::string memberId;
1051     double value;
1052     for (auto& collectionItems : allCollections)
1053     {
1054         if (collectionItems.first == "Temperatures")
1055         {
1056             propertyValueName = "ReadingCelsius";
1057         }
1058         else if (collectionItems.first == "Fans")
1059         {
1060             propertyValueName = "Reading";
1061         }
1062         else
1063         {
1064             propertyValueName = "ReadingVolts";
1065         }
1066         for (auto& item : collectionItems.second)
1067         {
1068             if (!json_util::readJson(item, res, "MemberId", memberId,
1069                                      propertyValueName, value))
1070             {
1071                 return;
1072             }
1073             overrideMap.emplace(memberId,
1074                                 std::make_pair(value, collectionItems.first));
1075         }
1076     }
1077     const std::string& chassisName = params[0];
1078     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
1079         res, chassisName, typeList, chassisSubNode);
1080     // first check for valid chassis id & sensor in requested chassis.
1081     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap](
1082                                       const boost::container::flat_set<
1083                                           std::string>& sensorLists) {
1084         boost::container::flat_set<std::string> sensorNames;
1085         for (const auto& item : overrideMap)
1086         {
1087             const auto& sensor = item.first;
1088             if (sensorLists.find(item.first) == sensorLists.end())
1089             {
1090                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
1091                 messages::resourceNotFound(sensorAsyncResp->res,
1092                                            item.second.second, item.first);
1093                 return;
1094             }
1095             sensorNames.emplace(sensor);
1096         }
1097         // Get the connection to which the memberId belongs
1098         auto getObjectsWithConnectionCb =
1099             [sensorAsyncResp, overrideMap](
1100                 const boost::container::flat_set<std::string>& connections,
1101                 const std::set<std::pair<std::string, std::string>>&
1102                     objectsWithConnection) {
1103                 if (objectsWithConnection.size() != overrideMap.size())
1104                 {
1105                     BMCWEB_LOG_INFO
1106                         << "Unable to find all objects with proper connection "
1107                         << objectsWithConnection.size() << " requested "
1108                         << overrideMap.size() << "\n";
1109                     messages::resourceNotFound(
1110                         sensorAsyncResp->res,
1111                         sensorAsyncResp->chassisSubNode == "Thermal"
1112                             ? "Temperatures"
1113                             : "Voltages",
1114                         "Count");
1115                     return;
1116                 }
1117                 for (const auto& item : objectsWithConnection)
1118                 {
1119 
1120                     auto lastPos = item.first.rfind('/');
1121                     if (lastPos == std::string::npos)
1122                     {
1123                         messages::internalError(sensorAsyncResp->res);
1124                         return;
1125                     }
1126                     std::string sensorName = item.first.substr(lastPos + 1);
1127 
1128                     const auto& iterator = overrideMap.find(sensorName);
1129                     if (iterator == overrideMap.end())
1130                     {
1131                         BMCWEB_LOG_INFO << "Unable to find sensor object"
1132                                         << item.first << "\n";
1133                         messages::internalError(sensorAsyncResp->res);
1134                         return;
1135                     }
1136                     crow::connections::systemBus->async_method_call(
1137                         [sensorAsyncResp](const boost::system::error_code ec) {
1138                             if (ec)
1139                             {
1140                                 BMCWEB_LOG_DEBUG
1141                                     << "setOverrideValueStatus DBUS error: "
1142                                     << ec;
1143                                 messages::internalError(sensorAsyncResp->res);
1144                                 return;
1145                             }
1146                         },
1147                         item.second, item.first,
1148                         "org.freedesktop.DBus.Properties", "Set",
1149                         "xyz.openbmc_project.Sensor.Value", "Value",
1150                         sdbusplus::message::variant<double>(
1151                             iterator->second.first));
1152                 }
1153             };
1154         // Get object with connection for the given sensor name
1155         getObjectsWithConnection(sensorAsyncResp, sensorNames,
1156                                  std::move(getObjectsWithConnectionCb));
1157     };
1158     // get full sensor list for the given chassisId and cross verify the sensor.
1159     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
1160 }
1161 
1162 } // namespace redfish
1163