Enable retrieving mapping of Redfish URI to D-Bus sensor path

Background:
Change is necessary to support TelemetryService implementation.
TelemetryService specifies its own resource type for data - MetricDefinition.
In principle - MetricDefinition might point to Redfish Sensor in the system.
Each MetricDefinition requires MetricProperty - URI to specific property in
resource wih the value. This change allows retrieving such data alongside
corresponding D-Bus sensor to be used as source of Metrics value.

This change:
Function was implemented to allow retrieving mapping of Redfish URI paths
to D-Bus sensors in the system. Some minor refactoring in regards to
SensorsAsyncResponse were also introduced to enhance code locality. Idea for the
changes was to be the least intrusive if possible, as retrieving sensors in the
system requires lots of processing.

Existing logic was used and left intact. Utilizing existing logic required to
'fake' a HTTP request to traverse the sensors in the system and build the
response. It's crucial to use exact logic of building Redfish nodes
representation, as goal of the function is to retrieve exact Redfish URI for
sensor value.

Extra parameter was introduced to SensorsAsyncResp - callback to be called when
sensor data will be fully determined. After processing is complete caller is
notified with the outcome (success or failure) and map<URI:D-Bus sensor>.

Testing:
- all positive cases (3 chassis, 3 subnodes) in the system,
  one of obtained mappings looked like the following:
    {   /redfish/v1/Chassis/WP_Baseboard/Power#/Voltages/3/ReadingVolts :
        /xyz/openbmc_project/sensors/voltage/P1V8_PCH }
- negative cases (wrong chassis, wrong subnode) - callback with error status
  was called
- RedfishServiceValidator passed

Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com>
Change-Id: I4389eb3df275126974168d1bb9af33dbf5cdb5b7
diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp
index a159ed6..59492c3 100644
--- a/redfish-core/lib/power.hpp
+++ b/redfish-core/lib/power.hpp
@@ -38,8 +38,6 @@
     }
 
   private:
-    std::vector<const char*> typeList = {"/xyz/openbmc_project/sensors/voltage",
-                                         "/xyz/openbmc_project/sensors/power"};
     void setPowerCapOverride(
         std::shared_ptr<SensorsAsyncResp> asyncResp,
         std::vector<nlohmann::json>& powerControlCollections)
@@ -155,7 +153,8 @@
         res.jsonValue["PowerControl"] = nlohmann::json::array();
 
         auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
-            res, chassis_name, typeList, "Power");
+            res, chassis_name, sensors::dbus::types.at(sensors::node::power),
+            sensors::node::power);
 
         getChassisData(sensorAsyncResp);
 
@@ -336,8 +335,9 @@
         }
 
         const std::string& chassisName = params[0];
-        auto asyncResp = std::make_shared<SensorsAsyncResp>(res, chassisName,
-                                                            typeList, "Power");
+        auto asyncResp = std::make_shared<SensorsAsyncResp>(
+            res, chassisName, sensors::dbus::types.at(sensors::node::power),
+            sensors::node::power);
 
         std::optional<std::vector<nlohmann::json>> voltageCollections;
         std::optional<std::vector<nlohmann::json>> powerCtlCollections;
diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp
index f5ad839..212a4a4 100644
--- a/redfish-core/lib/sensors.hpp
+++ b/redfish-core/lib/sensors.hpp
@@ -42,6 +42,33 @@
     boost::container::flat_map<
         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
 
+namespace sensors
+{
+namespace node
+{
+static constexpr std::string_view power = "Power";
+static constexpr std::string_view sensors = "Sensors";
+static constexpr std::string_view thermal = "Thermal";
+} // namespace node
+
+namespace dbus
+{
+static const boost::container::flat_map<std::string_view,
+                                        std::vector<const char*>>
+    types = {{node::power,
+              {"/xyz/openbmc_project/sensors/voltage",
+               "/xyz/openbmc_project/sensors/power"}},
+             {node::sensors,
+              {"/xyz/openbmc_project/sensors/power",
+               "/xyz/openbmc_project/sensors/current",
+               "/xyz/openbmc_project/sensors/utilization"}},
+             {node::thermal,
+              {"/xyz/openbmc_project/sensors/fan_tach",
+               "/xyz/openbmc_project/sensors/temperature",
+               "/xyz/openbmc_project/sensors/fan_pwm"}}};
+}
+} // namespace sensors
+
 /**
  * SensorsAsyncResp
  * Gathers data needed for response processing after async calls are done
@@ -49,13 +76,36 @@
 class SensorsAsyncResp
 {
   public:
+    using DataCompleteCb = std::function<void(
+        const boost::beast::http::status status,
+        const boost::container::flat_map<std::string, std::string>& uriToDbus)>;
+
+    struct SensorData
+    {
+        const std::string name;
+        std::string uri;
+        const std::string valueKey;
+        const std::string dbusPath;
+    };
+
     SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn,
                      const std::vector<const char*> typesIn,
-                     const std::string& subNode) :
+                     const std::string_view& subNode) :
         res(response),
         chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode)
     {}
 
+    // Store extra data about sensor mapping and return it in callback
+    SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn,
+                     const std::vector<const char*> typesIn,
+                     const std::string_view& subNode,
+                     DataCompleteCb&& creationComplete) :
+        res(response),
+        chassisId(chassisIdIn), types(typesIn),
+        chassisSubNode(subNode), metadata{std::vector<SensorData>()},
+        dataComplete{std::move(creationComplete)}
+    {}
+
     ~SensorsAsyncResp()
     {
         if (res.result() == boost::beast::http::status::internal_server_error)
@@ -65,13 +115,57 @@
             // proper code
             res.jsonValue = nlohmann::json::object();
         }
+
+        if (dataComplete && metadata)
+        {
+            boost::container::flat_map<std::string, std::string> map;
+            if (res.result() == boost::beast::http::status::ok)
+            {
+                for (auto& sensor : *metadata)
+                {
+                    map.insert(std::make_pair(sensor.uri + sensor.valueKey,
+                                              sensor.dbusPath));
+                }
+            }
+            dataComplete(res.result(), map);
+        }
+
         res.end();
     }
 
+    void addMetadata(const nlohmann::json& sensorObject,
+                     const std::string& valueKey, const std::string& dbusPath)
+    {
+        if (metadata)
+        {
+            metadata->emplace_back(SensorData{sensorObject["Name"],
+                                              sensorObject["@odata.id"],
+                                              valueKey, dbusPath});
+        }
+    }
+
+    void updateUri(const std::string& name, const std::string& uri)
+    {
+        if (metadata)
+        {
+            for (auto& sensor : *metadata)
+            {
+                if (sensor.name == name)
+                {
+                    sensor.uri = uri;
+                }
+            }
+        }
+    }
+
     crow::Response& res;
-    std::string chassisId{};
+    const std::string chassisId;
     const std::vector<const char*> types;
-    std::string chassisSubNode{};
+    const std::string chassisSubNode;
+
+  private:
+    std::optional<std::vector<SensorData>> metadata;
+    DataCompleteCb dataComplete;
 };
 
 /**
@@ -239,8 +333,9 @@
     {
         messages::resourceNotFound(
             SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode,
-            SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures"
-                                                          : "Voltages");
+            SensorsAsyncResp->chassisSubNode == sensors::node::thermal
+                ? "Temperatures"
+                : "Voltages");
 
         return;
     }
@@ -367,12 +462,12 @@
         }
 
         const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode;
-        if (chassisSubNode == "Power")
+        if (chassisSubNode == sensors::node::power)
         {
             sensorsAsyncResp->res.jsonValue["@odata.type"] =
                 "#Power.v1_5_2.Power";
         }
-        else if (chassisSubNode == "Thermal")
+        else if (chassisSubNode == sensors::node::thermal)
         {
             sensorsAsyncResp->res.jsonValue["@odata.type"] =
                 "#Thermal.v1_4_0.Thermal";
@@ -380,7 +475,7 @@
             sensorsAsyncResp->res.jsonValue["Temperatures"] =
                 nlohmann::json::array();
         }
-        else if (chassisSubNode == "Sensors")
+        else if (chassisSubNode == sensors::node::sensors)
         {
             sensorsAsyncResp->res.jsonValue["@odata.type"] =
                 "#SensorCollection.SensorCollection";
@@ -391,7 +486,7 @@
             sensorsAsyncResp->res.jsonValue["Members@odata.count"] = 0;
         }
 
-        if (chassisSubNode != "Sensors")
+        if (chassisSubNode != sensors::node::sensors)
         {
             sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode;
         }
@@ -422,9 +517,11 @@
                 {
                     messages::resourceNotFound(
                         sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode,
-                        sensorsAsyncResp->chassisSubNode == "Thermal"
+                        sensorsAsyncResp->chassisSubNode ==
+                                sensors::node::thermal
                             ? "Temperatures"
-                            : sensorsAsyncResp->chassisSubNode == "Power"
+                            : sensorsAsyncResp->chassisSubNode ==
+                                      sensors::node::power
                                   ? "Voltages"
                                   : "Sensors");
                     return;
@@ -689,8 +786,7 @@
  * @param sensorName  The name of the sensor to be built
  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
  * build
- * @param sensorSchema  The schema (Power, Thermal, etc) being associated with
- * the sensor to build
+ * @param sensorsAsyncResp  Sensor metadata
  * @param interfacesDict  A dictionary of the interfaces and properties of said
  * interfaces to be built from
  * @param sensor_json  The json object to fill
@@ -699,7 +795,7 @@
  */
 void objectInterfacesToJson(
     const std::string& sensorName, const std::string& sensorType,
-    const std::string& sensorSchema,
+    std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
     const boost::container::flat_map<
         std::string, boost::container::flat_map<std::string, SensorVariant>>&
         interfacesDict,
@@ -727,7 +823,7 @@
         }
     }
 
-    if (sensorSchema == "Sensors")
+    if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors)
     {
         // For sensors in SensorCollection we set Id instead of MemberId,
         // including power sensors.
@@ -753,7 +849,7 @@
     bool forceToInt = false;
 
     nlohmann::json::json_pointer unit("/Reading");
-    if (sensorSchema == "Sensors")
+    if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors)
     {
         sensor_json["@odata.type"] = "#Sensor.v1_0_0.Sensor";
         if (sensorType == "power")
@@ -833,7 +929,7 @@
 
     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
 
-    if (sensorSchema == "Sensors")
+    if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors)
     {
         properties.emplace_back(
             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
@@ -866,7 +962,7 @@
 
     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
 
-    if (sensorSchema == "Sensors")
+    if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors)
     {
         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
                                 "/ReadingRangeMin"_json_pointer);
@@ -939,6 +1035,11 @@
             }
         }
     }
+
+    sensorsAsyncResp->addMetadata(sensor_json, unit.to_string(),
+                                  "/xyz/openbmc_project/sensors/" + sensorType +
+                                      "/" + sensorName);
+
     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
 }
 
@@ -1150,7 +1251,7 @@
 {
     nlohmann::json& response = SensorsAsyncResp->res.jsonValue;
     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
-    if (SensorsAsyncResp->chassisSubNode == "Power")
+    if (SensorsAsyncResp->chassisSubNode == sensors::node::power)
     {
         sensorHeaders = {"Voltages", "PowerSupplies"};
     }
@@ -1178,6 +1279,7 @@
                 {
                     *value += std::to_string(count);
                     count++;
+                    SensorsAsyncResp->updateUri(sensorJson["Name"], *value);
                 }
             }
         }
@@ -2072,7 +2174,7 @@
     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter";
 
     // Only need the power supply attributes when the Power Schema
-    if (sensorsAsyncResp->chassisSubNode != "Power")
+    if (sensorsAsyncResp->chassisSubNode != sensors::node::power)
     {
         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power";
         callback(inventoryItems);
@@ -2375,7 +2477,7 @@
 
                 nlohmann::json* sensorJson = nullptr;
 
-                if (sensorSchema == "Sensors")
+                if (sensorSchema == sensors::node::sensors)
                 {
                     SensorsAsyncResp->res.jsonValue["@odata.id"] =
                         "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId +
@@ -2464,16 +2566,15 @@
 
                 if (sensorJson != nullptr)
                 {
-                    objectInterfacesToJson(sensorName, sensorType,
-                                           SensorsAsyncResp->chassisSubNode,
-                                           objDictEntry.second, *sensorJson,
-                                           inventoryItem);
+                    objectInterfacesToJson(
+                        sensorName, sensorType, SensorsAsyncResp,
+                        objDictEntry.second, *sensorJson, inventoryItem);
                 }
             }
             if (SensorsAsyncResp.use_count() == 1)
             {
                 sortJSONResponse(SensorsAsyncResp);
-                if (SensorsAsyncResp->chassisSubNode == "Thermal")
+                if (SensorsAsyncResp->chassisSubNode == sensors::node::thermal)
                 {
                     populateFanRedundancy(SensorsAsyncResp);
                 }
@@ -2677,7 +2778,8 @@
                         << overrideMap.size() << "\n";
                     messages::resourceNotFound(
                         sensorAsyncResp->res,
-                        sensorAsyncResp->chassisSubNode == "Thermal"
+                        sensorAsyncResp->chassisSubNode ==
+                                sensors::node::thermal
                             ? "Temperatures"
                             : "Voltages",
                         "Count");
@@ -2855,6 +2957,41 @@
         "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 5, interfaces);
 }
 
+/**
+ * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
+ * path of the sensor.
+ *
+ * Function builds valid Redfish response for sensor query of given chassis and
+ * node. It then builds metadata about Redfish<->D-Bus correlations and provides
+ * it to caller in a callback.
+ *
+ * @param chassis   Chassis for which retrieval should be performed
+ * @param node  Node (group) of sensors. See sensors::node for supported values
+ * @param mapComplete   Callback to be called with retrieval result
+ */
+void retrieveUriToDbusMap(const std::string& chassis, const std::string& node,
+                          SensorsAsyncResp::DataCompleteCb&& mapComplete)
+{
+    auto typesIt = sensors::dbus::types.find(node);
+    if (typesIt == sensors::dbus::types.end())
+    {
+        BMCWEB_LOG_ERROR << "Wrong node provided : " << node;
+        mapComplete(boost::beast::http::status::bad_request, {});
+        return;
+    }
+
+    auto respBuffer = std::make_shared<crow::Response>();
+    auto callback =
+        [respBuffer, mapCompleteCb{std::move(mapComplete)}](
+            const boost::beast::http::status status,
+            const boost::container::flat_map<std::string, std::string>&
+                uriToDbus) { mapCompleteCb(status, uriToDbus); };
+
+    auto resp = std::make_shared<SensorsAsyncResp>(
+        *respBuffer, chassis, typesIt->second, node, std::move(callback));
+    getChassisData(resp);
+}
+
 class SensorCollection : public Node
 {
   public:
@@ -2871,10 +3008,6 @@
     }
 
   private:
-    std::vector<const char*> typeList = {
-        "/xyz/openbmc_project/sensors/power",
-        "/xyz/openbmc_project/sensors/current",
-        "/xyz/openbmc_project/sensors/utilization"};
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
@@ -2889,8 +3022,9 @@
 
         const std::string& chassisId = params[0];
         std::shared_ptr<SensorsAsyncResp> asyncResp =
-            std::make_shared<SensorsAsyncResp>(res, chassisId, typeList,
-                                               "Sensors");
+            std::make_shared<SensorsAsyncResp>(
+                res, chassisId, sensors::dbus::types.at(sensors::node::sensors),
+                sensors::node::sensors);
 
         auto getChassisCb =
             [asyncResp](std::shared_ptr<boost::container::flat_set<std::string>>
@@ -2959,8 +3093,9 @@
         }
         const std::string& chassisId = params[0];
         std::shared_ptr<SensorsAsyncResp> asyncResp =
-            std::make_shared<SensorsAsyncResp>(
-                res, chassisId, std::vector<const char*>(), "Sensors");
+            std::make_shared<SensorsAsyncResp>(res, chassisId,
+                                               std::vector<const char*>(),
+                                               sensors::node::sensors);
 
         const std::string& sensorName = params[1];
         const std::array<const char*, 1> interfaces = {
diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp
index 2126f85..90ad3bb 100644
--- a/redfish-core/lib/thermal.hpp
+++ b/redfish-core/lib/thermal.hpp
@@ -37,10 +37,6 @@
     }
 
   private:
-    std::vector<const char*> typeList = {
-        "/xyz/openbmc_project/sensors/fan_tach",
-        "/xyz/openbmc_project/sensors/temperature",
-        "/xyz/openbmc_project/sensors/fan_pwm"};
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
@@ -52,7 +48,8 @@
         }
         const std::string& chassisName = params[0];
         auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
-            res, chassisName, typeList, "Thermal");
+            res, chassisName, sensors::dbus::types.at(sensors::node::thermal),
+            sensors::node::thermal);
 
         // TODO Need to get Chassis Redundancy information.
         getChassisData(sensorAsyncResp);
@@ -74,7 +71,8 @@
             allCollections;
 
         auto asyncResp = std::make_shared<SensorsAsyncResp>(
-            res, chassisName, typeList, "Thermal");
+            res, chassisName, sensors::dbus::types.at(sensors::node::thermal),
+            sensors::node::thermal);
 
         if (!json_util::readJson(req, asyncResp->res, "Temperatures",
                                  temperatureCollections, "Fans",