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