xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 26f0389959179b37bae5410dfd5a84831fbdaee6)
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"] = sensorName;
443     sensor_json["Status"]["State"] = "Enabled";
444     sensor_json["Status"]["Health"] = "OK";
445 
446     // Parameter to set to override the type we get from dbus, and force it to
447     // int, regardless of what is available.  This is used for schemas like fan,
448     // that require integers, not floats.
449     bool forceToInt = false;
450 
451     const char* unit = "Reading";
452     if (sensorType == "temperature")
453     {
454         unit = "ReadingCelsius";
455         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature";
456         // TODO(ed) Documentation says that path should be type fan_tach,
457         // implementation seems to implement fan
458     }
459     else if (sensorType == "fan" || sensorType == "fan_tach")
460     {
461         unit = "Reading";
462         sensor_json["ReadingUnits"] = "RPM";
463         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
464         forceToInt = true;
465     }
466     else if (sensorType == "fan_pwm")
467     {
468         unit = "Reading";
469         sensor_json["ReadingUnits"] = "Percent";
470         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
471         forceToInt = true;
472     }
473     else if (sensorType == "voltage")
474     {
475         unit = "ReadingVolts";
476         sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage";
477     }
478     else if (sensorType == "power")
479     {
480         std::string sensorNameLower =
481             boost::algorithm::to_lower_copy(sensorName);
482 
483         if (sensorNameLower.find("input") != std::string::npos)
484         {
485             unit = "PowerInputWatts";
486         }
487         else
488         {
489             unit = "PowerOutputWatts";
490         }
491     }
492     else
493     {
494         BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
495         return;
496     }
497     // Map of dbus interface name, dbus property name and redfish property_name
498     std::vector<std::tuple<const char*, const char*, const char*>> properties;
499     properties.reserve(7);
500 
501     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
502 
503     // If sensor type doesn't map to Redfish PowerSupply, add threshold props
504     if ((sensorType != "current") && (sensorType != "power"))
505     {
506         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
507                                 "WarningHigh", "UpperThresholdNonCritical");
508         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
509                                 "WarningLow", "LowerThresholdNonCritical");
510         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
511                                 "CriticalHigh", "UpperThresholdCritical");
512         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
513                                 "CriticalLow", "LowerThresholdCritical");
514     }
515 
516     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
517 
518     if (sensorType == "temperature")
519     {
520         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
521                                 "MinReadingRangeTemp");
522         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
523                                 "MaxReadingRangeTemp");
524     }
525     else if ((sensorType != "current") && (sensorType != "power"))
526     {
527         // Sensor type doesn't map to Redfish PowerSupply; add min/max props
528         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
529                                 "MinReadingRange");
530         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
531                                 "MaxReadingRange");
532     }
533 
534     for (const std::tuple<const char*, const char*, const char*>& p :
535          properties)
536     {
537         auto interfaceProperties = interfacesDict.find(std::get<0>(p));
538         if (interfaceProperties != interfacesDict.end())
539         {
540             auto valueIt = interfaceProperties->second.find(std::get<1>(p));
541             if (valueIt != interfaceProperties->second.end())
542             {
543                 const SensorVariant& valueVariant = valueIt->second;
544                 nlohmann::json& valueIt = sensor_json[std::get<2>(p)];
545                 // Attempt to pull the int64 directly
546                 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant);
547 
548                 const double* doubleValue = std::get_if<double>(&valueVariant);
549                 double temp = 0.0;
550                 if (int64Value != nullptr)
551                 {
552                     temp = *int64Value;
553                 }
554                 else if (doubleValue != nullptr)
555                 {
556                     temp = *doubleValue;
557                 }
558                 else
559                 {
560                     BMCWEB_LOG_ERROR
561                         << "Got value interface that wasn't int or double";
562                     continue;
563                 }
564                 temp = temp * std::pow(10, scaleMultiplier);
565                 if (forceToInt)
566                 {
567                     valueIt = static_cast<int64_t>(temp);
568                 }
569                 else
570                 {
571                     valueIt = temp;
572                 }
573             }
574         }
575     }
576     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
577 }
578 
579 static void
580     populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp)
581 {
582     crow::connections::systemBus->async_method_call(
583         [sensorsAsyncResp](const boost::system::error_code ec,
584                            const GetSubTreeType& resp) {
585             if (ec)
586             {
587                 return; // don't have to have this interface
588             }
589             for (const std::pair<std::string,
590                                  std::vector<std::pair<
591                                      std::string, std::vector<std::string>>>>&
592                      pathPair : resp)
593             {
594                 const std::string& path = pathPair.first;
595                 const std::vector<
596                     std::pair<std::string, std::vector<std::string>>>& objDict =
597                     pathPair.second;
598                 if (objDict.empty())
599                 {
600                     continue; // this should be impossible
601                 }
602 
603                 const std::string& owner = objDict.begin()->first;
604                 crow::connections::systemBus->async_method_call(
605                     [path, owner,
606                      sensorsAsyncResp](const boost::system::error_code ec,
607                                        std::variant<std::vector<std::string>>
608                                            variantEndpoints) {
609                         if (ec)
610                         {
611                             return; // if they don't have an association we
612                                     // can't tell what chassis is
613                         }
614                         // verify part of the right chassis
615                         auto endpoints = std::get_if<std::vector<std::string>>(
616                             &variantEndpoints);
617 
618                         if (endpoints == nullptr)
619                         {
620                             BMCWEB_LOG_ERROR << "Invalid association interface";
621                             messages::internalError(sensorsAsyncResp->res);
622                             return;
623                         }
624 
625                         auto found = std::find_if(
626                             endpoints->begin(), endpoints->end(),
627                             [sensorsAsyncResp](const std::string& entry) {
628                                 return entry.find(
629                                            sensorsAsyncResp->chassisId) !=
630                                        std::string::npos;
631                             });
632 
633                         if (found == endpoints->end())
634                         {
635                             return;
636                         }
637                         crow::connections::systemBus->async_method_call(
638                             [path, sensorsAsyncResp](
639                                 const boost::system::error_code ec,
640                                 const boost::container::flat_map<
641                                     std::string,
642                                     std::variant<uint8_t,
643                                                  std::vector<std::string>,
644                                                  std::string>>& ret) {
645                                 if (ec)
646                                 {
647                                     return; // don't have to have this
648                                             // interface
649                                 }
650                                 auto findFailures = ret.find("AllowedFailures");
651                                 auto findCollection = ret.find("Collection");
652                                 auto findStatus = ret.find("Status");
653 
654                                 if (findFailures == ret.end() ||
655                                     findCollection == ret.end() ||
656                                     findStatus == ret.end())
657                                 {
658                                     BMCWEB_LOG_ERROR
659                                         << "Invalid redundancy interface";
660                                     messages::internalError(
661                                         sensorsAsyncResp->res);
662                                     return;
663                                 }
664 
665                                 auto allowedFailures = std::get_if<uint8_t>(
666                                     &(findFailures->second));
667                                 auto collection =
668                                     std::get_if<std::vector<std::string>>(
669                                         &(findCollection->second));
670                                 auto status = std::get_if<std::string>(
671                                     &(findStatus->second));
672 
673                                 if (allowedFailures == nullptr ||
674                                     collection == nullptr || status == nullptr)
675                                 {
676 
677                                     BMCWEB_LOG_ERROR
678                                         << "Invalid redundancy interface "
679                                            "types";
680                                     messages::internalError(
681                                         sensorsAsyncResp->res);
682                                     return;
683                                 }
684                                 size_t lastSlash = path.rfind("/");
685                                 if (lastSlash == std::string::npos)
686                                 {
687                                     // this should be impossible
688                                     messages::internalError(
689                                         sensorsAsyncResp->res);
690                                     return;
691                                 }
692                                 std::string name = path.substr(lastSlash + 1);
693                                 std::replace(name.begin(), name.end(), '_',
694                                              ' ');
695 
696                                 std::string health;
697 
698                                 if (boost::ends_with(*status, "Full"))
699                                 {
700                                     health = "OK";
701                                 }
702                                 else if (boost::ends_with(*status, "Degraded"))
703                                 {
704                                     health = "Warning";
705                                 }
706                                 else
707                                 {
708                                     health = "Critical";
709                                 }
710                                 std::vector<nlohmann::json> redfishCollection;
711                                 const auto& fanRedfish =
712                                     sensorsAsyncResp->res.jsonValue["Fans"];
713                                 for (const std::string& item : *collection)
714                                 {
715                                     lastSlash = item.rfind("/");
716                                     // make a copy as collection is const
717                                     std::string itemName =
718                                         item.substr(lastSlash + 1);
719                                     /*
720                                     todo(ed): merge patch that fixes the names
721                                     std::replace(itemName.begin(),
722                                                  itemName.end(), '_', ' ');*/
723                                     auto schemaItem = std::find_if(
724                                         fanRedfish.begin(), fanRedfish.end(),
725                                         [itemName](const nlohmann::json& fan) {
726                                             return fan["MemberId"] == itemName;
727                                         });
728                                     if (schemaItem != fanRedfish.end())
729                                     {
730                                         redfishCollection.push_back(
731                                             {{"@odata.id",
732                                               (*schemaItem)["@odata.id"]}});
733                                     }
734                                     else
735                                     {
736                                         BMCWEB_LOG_ERROR
737                                             << "failed to find fan in schema";
738                                         messages::internalError(
739                                             sensorsAsyncResp->res);
740                                         return;
741                                     }
742                                 }
743 
744                                 auto& resp = sensorsAsyncResp->res
745                                                  .jsonValue["Redundancy"];
746                                 resp.push_back(
747                                     {{"@odata.id",
748                                       "/refish/v1/Chassis/" +
749                                           sensorsAsyncResp->chassisId + "/" +
750                                           sensorsAsyncResp->chassisSubNode +
751                                           "#/Redundancy/" +
752                                           std::to_string(resp.size())},
753                                      {"@odata.type",
754                                       "#Redundancy.v1_3_2.Redundancy"},
755                                      {"MinNumNeeded",
756                                       collection->size() - *allowedFailures},
757                                      {"MemberId", name},
758                                      {"Mode", "N+m"},
759                                      {"Name", name},
760                                      {"RedundancySet", redfishCollection},
761                                      {"Status",
762                                       {{"Health", health},
763                                        {"State", "Enabled"}}}});
764                             },
765                             owner, path, "org.freedesktop.DBus.Properties",
766                             "GetAll",
767                             "xyz.openbmc_project.Control.FanRedundancy");
768                     },
769                     "xyz.openbmc_project.ObjectMapper", path + "/inventory",
770                     "org.freedesktop.DBus.Properties", "Get",
771                     "xyz.openbmc_project.Association", "endpoints");
772             }
773         },
774         "xyz.openbmc_project.ObjectMapper",
775         "/xyz/openbmc_project/object_mapper",
776         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
777         "/xyz/openbmc_project/control", 2,
778         std::array<const char*, 1>{
779             "xyz.openbmc_project.Control.FanRedundancy"});
780 }
781 
782 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
783 {
784     nlohmann::json& response = SensorsAsyncResp->res.jsonValue;
785     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
786     if (SensorsAsyncResp->chassisSubNode == "Power")
787     {
788         sensorHeaders = {"Voltages", "PowerSupplies"};
789     }
790     for (const std::string& sensorGroup : sensorHeaders)
791     {
792         nlohmann::json::iterator entry = response.find(sensorGroup);
793         if (entry != response.end())
794         {
795             std::sort(entry->begin(), entry->end(),
796                       [](nlohmann::json& c1, nlohmann::json& c2) {
797                           return c1["Name"] < c2["Name"];
798                       });
799 
800             // add the index counts to the end of each entry
801             size_t count = 0;
802             for (nlohmann::json& sensorJson : *entry)
803             {
804                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
805                 if (odata == sensorJson.end())
806                 {
807                     continue;
808                 }
809                 std::string* value = odata->get_ptr<std::string*>();
810                 if (value != nullptr)
811                 {
812                     *value += std::to_string(count);
813                     count++;
814                 }
815             }
816         }
817     }
818 }
819 
820 /**
821  * @brief Gets the values of the specified sensors.
822  *
823  * Stores the results as JSON in the SensorsAsyncResp.
824  *
825  * Gets the sensor values asynchronously.  Stores the results later when the
826  * information has been obtained.
827  *
828  * The sensorNames set contains all sensors for the current chassis.
829  * SensorsAsyncResp contains the requested sensor types.  Only sensors of a
830  * requested type are included in the JSON output.
831  *
832  * To minimize the number of DBus calls, the DBus method
833  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
834  * values of all sensors provided by a connection (service).
835  *
836  * The connections set contains all the connections that provide sensor values.
837  *
838  * The objectMgrPaths map contains mappings from a connection name to the
839  * corresponding DBus object path that implements ObjectManager.
840  *
841  * @param SensorsAsyncResp Pointer to object holding response data.
842  * @param sensorNames All sensors within the current chassis.
843  * @param connections Connections that provide sensor values.
844  * @param objectMgrPaths Mappings from connection name to DBus object path that
845  * implements ObjectManager.
846  */
847 void getSensorData(
848     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
849     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
850     const boost::container::flat_set<std::string>& connections,
851     const boost::container::flat_map<std::string, std::string>& objectMgrPaths)
852 {
853     BMCWEB_LOG_DEBUG << "getSensorData enter";
854     // Get managed objects from all services exposing sensors
855     for (const std::string& connection : connections)
856     {
857         // Response handler to process managed objects
858         auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames](
859                                        const boost::system::error_code ec,
860                                        ManagedObjectsVectorType& resp) {
861             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
862             if (ec)
863             {
864                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
865                 messages::internalError(SensorsAsyncResp->res);
866                 return;
867             }
868             // Go through all objects and update response with sensor data
869             for (const auto& objDictEntry : resp)
870             {
871                 const std::string& objPath =
872                     static_cast<const std::string&>(objDictEntry.first);
873                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
874                                  << objPath;
875 
876                 std::vector<std::string> split;
877                 // Reserve space for
878                 // /xyz/openbmc_project/sensors/<name>/<subname>
879                 split.reserve(6);
880                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
881                 if (split.size() < 6)
882                 {
883                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
884                                      << objPath;
885                     continue;
886                 }
887                 // These indexes aren't intuitive, as boost::split puts an empty
888                 // string at the beginning
889                 const std::string& sensorType = split[4];
890                 const std::string& sensorName = split[5];
891                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
892                                  << " sensorType " << sensorType;
893                 if (sensorNames->find(objPath) == sensorNames->end())
894                 {
895                     BMCWEB_LOG_ERROR << sensorName << " not in sensor list ";
896                     continue;
897                 }
898 
899                 const char* fieldName = nullptr;
900                 if (sensorType == "temperature")
901                 {
902                     fieldName = "Temperatures";
903                 }
904                 else if (sensorType == "fan" || sensorType == "fan_tach" ||
905                          sensorType == "fan_pwm")
906                 {
907                     fieldName = "Fans";
908                 }
909                 else if (sensorType == "voltage")
910                 {
911                     fieldName = "Voltages";
912                 }
913                 else if (sensorType == "current")
914                 {
915                     fieldName = "PowerSupplies";
916                 }
917                 else if (sensorType == "power")
918                 {
919                     fieldName = "PowerSupplies";
920                 }
921                 else
922                 {
923                     BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
924                                      << sensorType;
925                     continue;
926                 }
927 
928                 nlohmann::json& tempArray =
929                     SensorsAsyncResp->res.jsonValue[fieldName];
930 
931                 if (fieldName == "PowerSupplies" && !tempArray.empty())
932                 {
933                     // Power supplies put multiple "sensors" into a single power
934                     // supply entry, so only create the first one
935                 }
936                 else
937                 {
938                     tempArray.push_back(
939                         {{"@odata.id", "/redfish/v1/Chassis/" +
940                                            SensorsAsyncResp->chassisId + "/" +
941                                            SensorsAsyncResp->chassisSubNode +
942                                            "#/" + fieldName + "/"}});
943                 }
944                 nlohmann::json& sensorJson = tempArray.back();
945 
946                 objectInterfacesToJson(sensorName, sensorType,
947                                        objDictEntry.second, sensorJson);
948             }
949             if (SensorsAsyncResp.use_count() == 1)
950             {
951                 sortJSONResponse(SensorsAsyncResp);
952                 if (SensorsAsyncResp->chassisSubNode == "Thermal")
953                 {
954                     populateFanRedundancy(SensorsAsyncResp);
955                 }
956             }
957             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
958         };
959 
960         // Find DBus object path that implements ObjectManager for the current
961         // connection.  If no mapping found, default to "/".
962         auto iter = objectMgrPaths.find(connection);
963         const std::string& objectMgrPath =
964             (iter != objectMgrPaths.end()) ? iter->second : "/";
965         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
966                          << objectMgrPath;
967 
968         crow::connections::systemBus->async_method_call(
969             getManagedObjectsCb, connection, objectMgrPath,
970             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
971     };
972     BMCWEB_LOG_DEBUG << "getSensorData exit";
973 }
974 
975 /**
976  * @brief Entry point for retrieving sensors data related to requested
977  *        chassis.
978  * @param SensorsAsyncResp   Pointer to object holding response data
979  */
980 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
981 {
982     BMCWEB_LOG_DEBUG << "getChassisData enter";
983     auto getChassisCb =
984         [SensorsAsyncResp](
985             std::shared_ptr<boost::container::flat_set<std::string>>
986                 sensorNames) {
987             BMCWEB_LOG_DEBUG << "getChassisCb enter";
988             auto getConnectionCb =
989                 [SensorsAsyncResp,
990                  sensorNames](const boost::container::flat_set<std::string>&
991                                   connections) {
992                     BMCWEB_LOG_DEBUG << "getConnectionCb enter";
993                     auto getObjectManagerPathsCb =
994                         [SensorsAsyncResp, sensorNames, connections](
995                             const boost::container::flat_map<
996                                 std::string, std::string>& objectMgrPaths) {
997                             BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
998                             // Get sensor data and store results in JSON
999                             // response
1000                             getSensorData(SensorsAsyncResp, sensorNames,
1001                                           connections, objectMgrPaths);
1002                             BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
1003                         };
1004 
1005                     // Get mapping from connection names to the DBus object
1006                     // paths that implement the ObjectManager interface
1007                     getObjectManagerPaths(SensorsAsyncResp,
1008                                           std::move(getObjectManagerPathsCb));
1009                     BMCWEB_LOG_DEBUG << "getConnectionCb exit";
1010                 };
1011 
1012             // Get set of connections that provide sensor values
1013             getConnections(SensorsAsyncResp, sensorNames,
1014                            std::move(getConnectionCb));
1015             BMCWEB_LOG_DEBUG << "getChassisCb exit";
1016         };
1017 
1018     // Get set of sensors in chassis
1019     getChassis(SensorsAsyncResp, std::move(getChassisCb));
1020     BMCWEB_LOG_DEBUG << "getChassisData exit";
1021 };
1022 
1023 /**
1024  * @brief Find the requested sensorName in the list of all sensors supplied by
1025  * the chassis node
1026  *
1027  * @param sensorName   The sensor name supplied in the PATCH request
1028  * @param sensorsList  The list of sensors managed by the chassis node
1029  * @param sensorsModified  The list of sensors that were found as a result of
1030  *                         repeated calls to this function
1031  */
1032 bool findSensorNameUsingSensorPath(
1033     const std::string& sensorName,
1034     boost::container::flat_set<std::string>& sensorsList,
1035     boost::container::flat_set<std::string>& sensorsModified)
1036 {
1037     for (const std::string& chassisSensor : sensorsList)
1038     {
1039         std::string thisSensorName;
1040         if (!dbus::utility::getNthStringFromPath(chassisSensor, 5,
1041                                                  thisSensorName))
1042         {
1043             BMCWEB_LOG_ERROR << "Got path that isn't long enough "
1044                              << chassisSensor;
1045             continue;
1046         }
1047         if (thisSensorName == sensorName)
1048         {
1049             sensorsModified.emplace(chassisSensor);
1050             return true;
1051         }
1052     }
1053     return false;
1054 }
1055 
1056 /**
1057  * @brief Entry point for overriding sensor values of given sensor
1058  *
1059  * @param res   response object
1060  * @param req   request object
1061  * @param params   parameter passed for CRUD
1062  * @param typeList   TypeList of sensors for the resource queried
1063  * @param chassisSubNode   Chassis Node for which the query has to happen
1064  */
1065 void setSensorOverride(crow::Response& res, const crow::Request& req,
1066                        const std::vector<std::string>& params,
1067                        const std::initializer_list<const char*> typeList,
1068                        const std::string& chassisSubNode)
1069 {
1070 
1071     // TODO: Need to figure out dynamic way to restrict patch (Set Sensor
1072     // override) based on another d-bus announcement to be more generic.
1073     if (params.size() != 1)
1074     {
1075         messages::internalError(res);
1076         res.end();
1077         return;
1078     }
1079 
1080     std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections;
1081     std::optional<std::vector<nlohmann::json>> temperatureCollections;
1082     std::optional<std::vector<nlohmann::json>> fanCollections;
1083     std::vector<nlohmann::json> voltageCollections;
1084     BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode
1085                     << "\n";
1086 
1087     if (chassisSubNode == "Thermal")
1088     {
1089         if (!json_util::readJson(req, res, "Temperatures",
1090                                  temperatureCollections, "Fans",
1091                                  fanCollections))
1092         {
1093             return;
1094         }
1095         if (!temperatureCollections && !fanCollections)
1096         {
1097             messages::resourceNotFound(res, "Thermal",
1098                                        "Temperatures / Voltages");
1099             res.end();
1100             return;
1101         }
1102         if (temperatureCollections)
1103         {
1104             allCollections.emplace("Temperatures",
1105                                    *std::move(temperatureCollections));
1106         }
1107         if (fanCollections)
1108         {
1109             allCollections.emplace("Fans", *std::move(fanCollections));
1110         }
1111     }
1112     else if (chassisSubNode == "Power")
1113     {
1114         if (!json_util::readJson(req, res, "Voltages", voltageCollections))
1115         {
1116             return;
1117         }
1118         allCollections.emplace("Voltages", std::move(voltageCollections));
1119     }
1120     else
1121     {
1122         res.result(boost::beast::http::status::not_found);
1123         res.end();
1124         return;
1125     }
1126 
1127     const char* propertyValueName;
1128     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
1129     std::string memberId;
1130     double value;
1131     for (auto& collectionItems : allCollections)
1132     {
1133         if (collectionItems.first == "Temperatures")
1134         {
1135             propertyValueName = "ReadingCelsius";
1136         }
1137         else if (collectionItems.first == "Fans")
1138         {
1139             propertyValueName = "Reading";
1140         }
1141         else
1142         {
1143             propertyValueName = "ReadingVolts";
1144         }
1145         for (auto& item : collectionItems.second)
1146         {
1147             if (!json_util::readJson(item, res, "MemberId", memberId,
1148                                      propertyValueName, value))
1149             {
1150                 return;
1151             }
1152             overrideMap.emplace(memberId,
1153                                 std::make_pair(value, collectionItems.first));
1154         }
1155     }
1156     const std::string& chassisName = params[0];
1157     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
1158         res, chassisName, typeList, chassisSubNode);
1159     auto getChassisSensorListCb = [sensorAsyncResp,
1160                                    overrideMap](const std::shared_ptr<
1161                                                 boost::container::flat_set<
1162                                                     std::string>>
1163                                                     sensorsList) {
1164         // Match sensor names in the PATCH request to those managed by the
1165         // chassis node
1166         const std::shared_ptr<boost::container::flat_set<std::string>>
1167             sensorNames =
1168                 std::make_shared<boost::container::flat_set<std::string>>();
1169         for (const auto& item : overrideMap)
1170         {
1171             const auto& sensor = item.first;
1172             if (!findSensorNameUsingSensorPath(sensor, *sensorsList,
1173                                                *sensorNames))
1174             {
1175                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
1176                 messages::resourceNotFound(sensorAsyncResp->res,
1177                                            item.second.second, item.first);
1178                 return;
1179             }
1180         }
1181         // Get the connection to which the memberId belongs
1182         auto getObjectsWithConnectionCb =
1183             [sensorAsyncResp, overrideMap](
1184                 const boost::container::flat_set<std::string>& connections,
1185                 const std::set<std::pair<std::string, std::string>>&
1186                     objectsWithConnection) {
1187                 if (objectsWithConnection.size() != overrideMap.size())
1188                 {
1189                     BMCWEB_LOG_INFO
1190                         << "Unable to find all objects with proper connection "
1191                         << objectsWithConnection.size() << " requested "
1192                         << overrideMap.size() << "\n";
1193                     messages::resourceNotFound(
1194                         sensorAsyncResp->res,
1195                         sensorAsyncResp->chassisSubNode == "Thermal"
1196                             ? "Temperatures"
1197                             : "Voltages",
1198                         "Count");
1199                     return;
1200                 }
1201                 for (const auto& item : objectsWithConnection)
1202                 {
1203 
1204                     auto lastPos = item.first.rfind('/');
1205                     if (lastPos == std::string::npos)
1206                     {
1207                         messages::internalError(sensorAsyncResp->res);
1208                         return;
1209                     }
1210                     std::string sensorName = item.first.substr(lastPos + 1);
1211 
1212                     const auto& iterator = overrideMap.find(sensorName);
1213                     if (iterator == overrideMap.end())
1214                     {
1215                         BMCWEB_LOG_INFO << "Unable to find sensor object"
1216                                         << item.first << "\n";
1217                         messages::internalError(sensorAsyncResp->res);
1218                         return;
1219                     }
1220                     crow::connections::systemBus->async_method_call(
1221                         [sensorAsyncResp](const boost::system::error_code ec) {
1222                             if (ec)
1223                             {
1224                                 BMCWEB_LOG_DEBUG
1225                                     << "setOverrideValueStatus DBUS error: "
1226                                     << ec;
1227                                 messages::internalError(sensorAsyncResp->res);
1228                                 return;
1229                             }
1230                         },
1231                         item.second, item.first,
1232                         "org.freedesktop.DBus.Properties", "Set",
1233                         "xyz.openbmc_project.Sensor.Value", "Value",
1234                         sdbusplus::message::variant<double>(
1235                             iterator->second.first));
1236                 }
1237             };
1238         // Get object with connection for the given sensor name
1239         getObjectsWithConnection(sensorAsyncResp, sensorNames,
1240                                  std::move(getObjectsWithConnectionCb));
1241     };
1242     // get full sensor list for the given chassisId and cross verify the sensor.
1243     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
1244 }
1245 
1246 } // namespace redfish
1247