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