Patch support for sensor overrride

Support added for overriding sensor, which can be
used for validation / ad-hoc debugging. This provides
option to make PATCH call to redfish/v1/<chassisId>/Thermal
or power id. Based on schema, will accept Temperatures /
Voltages collection with properties MemberId and
ReadingCelsius / ReadingVolts.

TODO:
1. Need to make a dynamic way of enabling / disbaling this command.

Unit-Test:
1. Verified sensor values are getting updated by doing PATCH
method to a known sensor. Verified the value got updated
using ipmitool sensor list.
2. Verified negative cases of making PATCH call on invalid
chasisId, Invalid MemberId etc.

Testedeby:
Used Postman tool to issue the PATCH call to the
1. https://xx.xx.xx.xx/redfish/v1/Chassis/XXYYZZ/Thermal with
content
{
  "Temperatures": [
    {
      "MemberId" : "SensorNameXX",
      "ReadingCelsius" : valueXX
    }
  ]
}
2. https://xx.xx.xx.xx/redfish/v1/Chassis/XXYYZZ/Power with
content
{
  "Voltages": [
    {
      "MemberId" : "SensorNameXX",
      "ReadingVolts" : valueXX
    }
  ]
}

Change-Id: Idf2d891ac0d10b5d20f78c386232cae8a6896f1a
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp
index 0cb1aa0..a76f191 100644
--- a/redfish-core/lib/power.hpp
+++ b/redfish-core/lib/power.hpp
@@ -38,6 +38,9 @@
     }
 
   private:
+    std::initializer_list<const char*> typeList = {
+        "/xyz/openbmc_project/sensors/voltage",
+        "/xyz/openbmc_project/sensors/power"};
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
@@ -56,14 +59,15 @@
         res.jsonValue["Id"] = "Power";
         res.jsonValue["Name"] = "Power";
         auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
-            res, chassis_name,
-            std::initializer_list<const char*>{
-                "/xyz/openbmc_project/sensors/voltage",
-                "/xyz/openbmc_project/sensors/power"},
-            "Power");
+            res, chassis_name, typeList, "Power");
         // TODO Need to retrieve Power Control information.
         getChassisData(sensorAsyncResp);
     }
+    void doPatch(crow::Response& res, const crow::Request& req,
+                 const std::vector<std::string>& params) override
+    {
+        setSensorOverride(res, req, params, typeList, "Power");
+    }
 };
 
 } // namespace redfish
diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp
index 9fa5d88..34f8f53 100644
--- a/redfish-core/lib/sensors.hpp
+++ b/redfish-core/lib/sensors.hpp
@@ -22,6 +22,7 @@
 #include <boost/container/flat_map.hpp>
 #include <boost/range/algorithm/replace_copy_if.hpp>
 #include <dbus_singleton.hpp>
+#include <utils/json_utils.hpp>
 #include <variant>
 
 namespace redfish
@@ -54,7 +55,7 @@
         chassisId(chassisId), types(types), chassisSubNode(subNode)
     {
         res.jsonValue["@odata.id"] =
-            "/redfish/v1/Chassis/" + chassisId + "/Thermal";
+            "/redfish/v1/Chassis/" + chassisId + "/" + subNode;
     }
 
     ~SensorsAsyncResp()
@@ -76,17 +77,18 @@
 };
 
 /**
- * @brief Creates connections necessary for chassis sensors
+ * @brief Get objects with connection necessary for sensors
  * @param SensorsAsyncResp Pointer to object holding response data
  * @param sensorNames Sensors retrieved from chassis
  * @param callback Callback for processing gathered connections
  */
 template <typename Callback>
-void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
-                    const boost::container::flat_set<std::string>& sensorNames,
-                    Callback&& callback)
+void getObjectsWithConnection(
+    std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
+    const boost::container::flat_set<std::string>& sensorNames,
+    Callback&& callback)
 {
-    BMCWEB_LOG_DEBUG << "getConnections enter";
+    BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter";
     const std::string path = "/xyz/openbmc_project/sensors";
     const std::array<std::string, 1> interfaces = {
         "xyz.openbmc_project.Sensor.Value"};
@@ -95,12 +97,12 @@
     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
                         sensorNames](const boost::system::error_code ec,
                                      const GetSubTreeType& subtree) {
-        BMCWEB_LOG_DEBUG << "getConnections resp_handler enter";
+        BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter";
         if (ec)
         {
             messages::internalError(SensorsAsyncResp->res);
-            BMCWEB_LOG_ERROR << "getConnections resp_handler: Dbus error "
-                             << ec;
+            BMCWEB_LOG_ERROR
+                << "getObjectsWithConnection resp_handler: Dbus error " << ec;
             return;
         }
 
@@ -109,6 +111,7 @@
         // Make unique list of connections only for requested sensor types and
         // found in the chassis
         boost::container::flat_set<std::string> connections;
+        std::set<std::pair<std::string, std::string>> objectsWithConnection;
         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
         // producers
         connections.reserve(8);
@@ -144,6 +147,8 @@
                                 BMCWEB_LOG_DEBUG << "Adding connection: "
                                                  << objData.first;
                                 connections.insert(objData.first);
+                                objectsWithConnection.insert(std::make_pair(
+                                    object.first, objData.first));
                             }
                         }
                     }
@@ -152,16 +157,36 @@
             }
         }
         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
-        callback(std::move(connections));
-        BMCWEB_LOG_DEBUG << "getConnections resp_handler exit";
+        callback(std::move(connections), std::move(objectsWithConnection));
+        BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit";
     };
-
     // Make call to ObjectMapper to find all sensors objects
     crow::connections::systemBus->async_method_call(
         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
         "/xyz/openbmc_project/object_mapper",
         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
-    BMCWEB_LOG_DEBUG << "getConnections exit";
+    BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit";
+}
+
+/**
+ * @brief Create connections necessary for sensors
+ * @param SensorsAsyncResp Pointer to object holding response data
+ * @param sensorNames Sensors retrieved from chassis
+ * @param callback Callback for processing gathered connections
+ */
+template <typename Callback>
+void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
+                    const boost::container::flat_set<std::string>& sensorNames,
+                    Callback&& callback)
+{
+    auto objectsWithConnectionCb =
+        [callback](const boost::container::flat_set<std::string>& connections,
+                   const std::set<std::pair<std::string, std::string>>&
+                       objectsWithConnection) {
+            callback(std::move(connections));
+        };
+    getObjectsWithConnection(SensorsAsyncResp, sensorNames,
+                             std::move(objectsWithConnectionCb));
 }
 
 /**
@@ -541,4 +566,131 @@
     BMCWEB_LOG_DEBUG << "getChassisData exit";
 };
 
+/**
+ * @brief Entry point for overriding sensor values of given sensor
+ *
+ * @param res   response object
+ * @param req   request object
+ * @param params   parameter passed for CRUD
+ * @param typeList   TypeList of sensors for the resource queried
+ * @param chassisSubNode   Chassis Node for which the query has to happen
+ */
+void setSensorOverride(crow::Response& res, const crow::Request& req,
+                       const std::vector<std::string>& params,
+                       const std::initializer_list<const char*> typeList,
+                       const std::string& chassisSubNode)
+{
+
+    // TODO: Need to figure out dynamic way to restrict patch (Set Sensor
+    // override) based on another d-bus announcement to be more generic.
+    if (params.size() != 1)
+    {
+        messages::internalError(res);
+        res.end();
+        return;
+    }
+    const char* collectionName;
+    const char* propertyValueName;
+    if (chassisSubNode == "Thermal")
+    {
+        collectionName = "Temperatures";
+        propertyValueName = "ReadingCelsius";
+    }
+    else if (chassisSubNode == "Power")
+    {
+        collectionName = "Voltages";
+        propertyValueName = "ReadingVolts";
+    }
+    else
+    {
+        res.result(boost::beast::http::status::not_found);
+        res.end();
+        return;
+    }
+    std::vector<nlohmann::json> collections;
+    if (!json_util::readJson(req, res, collectionName, collections))
+    {
+        return;
+    }
+    if (collections.size() != 1)
+    {
+        messages::malformedJSON(res);
+        res.end();
+        return;
+    }
+
+    std::string memberId;
+    double value;
+    if (!json_util::readJson(collections[0], res, "MemberId", memberId,
+                             propertyValueName, value))
+    {
+        return;
+    }
+    const std::string& chassisName = params[0];
+    auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
+        res, chassisName, typeList, chassisSubNode);
+    BMCWEB_LOG_INFO << "setSensorOverride for " << memberId
+                    << "with value: " << value << "\n";
+    // first check for valid chassis id & sensor in requested chassis.
+    auto getChassisSensorListCb =
+        [sensorAsyncResp, memberId,
+         value](const boost::container::flat_set<std::string>& sensorLists) {
+            if (sensorLists.find(memberId) == sensorLists.end())
+            {
+                BMCWEB_LOG_INFO << "Unable to find memberId " << memberId;
+                messages::resourceNotFound(sensorAsyncResp->res,
+                                           sensorAsyncResp->chassisSubNode ==
+                                                   "Thermal"
+                                               ? "Temperatures"
+                                               : "Voltages",
+                                           memberId);
+                return;
+            }
+            boost::container::flat_set<std::string> sensorNames;
+            sensorNames.emplace(memberId);
+            // Get the connection to which the memberId belongs
+            auto getObjectsWithConnectionCb =
+                [sensorAsyncResp, memberId, value](
+                    const boost::container::flat_set<std::string>& connections,
+                    const std::set<std::pair<std::string, std::string>>&
+                        objectsWithConnection) {
+                    if (objectsWithConnection.size() != 1)
+                    {
+                        BMCWEB_LOG_INFO
+                            << "Unable to find object with proper connection "
+                            << objectsWithConnection.size() << "\n";
+                        messages::resourceNotFound(
+                            sensorAsyncResp->res,
+                            sensorAsyncResp->chassisSubNode == "Thermal"
+                                ? "Temperatures"
+                                : "Voltages",
+                            memberId);
+                        return;
+                    }
+                    crow::connections::systemBus->async_method_call(
+                        [sensorAsyncResp, memberId,
+                         value](const boost::system::error_code ec) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_DEBUG
+                                    << "getOverrideValueStatus DBUS error: "
+                                    << ec;
+                                messages::internalError(sensorAsyncResp->res);
+                                return;
+                            }
+                        },
+                        objectsWithConnection.begin()->second,
+                        objectsWithConnection.begin()->first,
+                        "org.freedesktop.DBus.Properties", "Set",
+                        "xyz.openbmc_project.Sensor.Value", "Value",
+                        sdbusplus::message::variant<double>(value));
+                };
+            // Get object with connection for the given sensor name
+            getObjectsWithConnection(sensorAsyncResp, sensorNames,
+                                     std::move(getObjectsWithConnectionCb));
+        };
+    // get full sensor list for the given chassisId and cross verify the sensor.
+    getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
+}
+
 } // namespace redfish
diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp
index d0fbccf..4b2a0a4 100644
--- a/redfish-core/lib/thermal.hpp
+++ b/redfish-core/lib/thermal.hpp
@@ -37,6 +37,10 @@
     }
 
   private:
+    std::initializer_list<const char*> typeList = {
+        "/xyz/openbmc_project/sensors/fan",
+        "/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
     {
@@ -58,16 +62,16 @@
             "/redfish/v1/Chassis/" + chassisName + "/Thermal";
 
         auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
-            res, chassisName,
-            std::initializer_list<const char*>{
-                "/xyz/openbmc_project/sensors/fan",
-                "/xyz/openbmc_project/sensors/temperature",
-                "/xyz/openbmc_project/sensors/fan_pwm"},
-            "Thermal");
+            res, chassisName, typeList, "Thermal");
 
         // TODO Need to get Chassis Redundancy information.
         getChassisData(sensorAsyncResp);
     }
+    void doPatch(crow::Response& res, const crow::Request& req,
+                 const std::vector<std::string>& params) override
+    {
+        setSensorOverride(res, req, params, typeList, "Thermal");
+    }
 };
 
 } // namespace redfish