Redfish: Set the power cap

Set the PowerCap with redfish patch.

Tested:
Case 1: PowerCapEnable is false
$ curl -k -H "X-Auth-Token: $token" -X PUT -d '{"data":false}' https://$bmc/xyz/openbmc_project/control/host0/power_cap/attr/PowerCapEnable
$ curl -k -H "X-Auth-Token: $token"https://${bmc}/redfish/v1/Chassis/chassis/Power
{
  "@odata.context": "/redfish/v1/$metadata#Power.Power",
  "@odata.id": "/redfish/v1/Chassis/chassis/Power",
  "@odata.type": "#Power.v1_5_2.Power",
  "Id": "Power",
  "Name": "Power",
  "PowerControl": [
    {
      "@odata.id": "/redfish/v1/Chassis/chassis/Power#/PowerControl/0",
      "@odata.type": "#Power.v1_0_0.PowerControl",
      "MemberId": "0",
      "Name": "Chassis Power Control",
      "PowerLimit": {
        "LimitInWatts": null
      }
    }
  ],
  ...
}
$curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Chassis/chassis/Power -X PATCH -d '{"PowerControl":[{"PowerLimit":{"LimitInWatts":2004}}]}'
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
        "Message": "PowerCapEnable is false, can't set the PowerCap.",
        "MessageArgs": [],
        "MessageId": "Base.1.4.0.UnableToSetPowerCap",
        "Resolution": "Set PowerCapEnable to be true before setting PowerCap.",
        "Severity": "Warning"
      }
    ],
    "code": "Base.1.4.0.UnableToSetPowerCap",
    "message": "PowerCapEnable is false, can't set the PowerCap."
  }
}

Case 2: PowerCapEnable is true, PowerControl json only
$ curl -k -H "X-Auth-Token: $token" -X PUT -d '{"data":true}' https://$bmc/xyz/openbmc_project/control/host0/power_cap/attr/PowerCapEnable
$ curl -k -H "X-Auth-Token: $token"https://${bmc}/redfish/v1/Chassis/chassis/Power
{
  "@odata.context": "/redfish/v1/$metadata#Power.Power",
  "@odata.id": "/redfish/v1/Chassis/chassis/Power",
  "@odata.type": "#Power.v1_5_2.Power",
  "Id": "Power",
  "Name": "Power",
  "PowerControl": [
    {
      "@odata.id": "/redfish/v1/Chassis/chassis/Power#/PowerControl/0",
      "@odata.type": "#Power.v1_0_0.PowerControl",
      "MemberId": "0",
      "Name": "Chassis Power Control",
      "PowerLimit": {
        "LimitInWatts": 2001.0
      }
    }
  ],
  ...
}
$ curl -k -H "X-Auth-Token: $token"https://${bmc}/redfish/v1/Chassis/chassis/Power -X PATCH -d '{"PowerControl":[{"PowerLimit":{"LimitInWatts":2004}}]}' -v
...
< HTTP/1.1 204 No Content
...
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Chassis/chassis/Power
{
  "@odata.context": "/redfish/v1/$metadata#Power.Power",
  "@odata.id": "/redfish/v1/Chassis/chassis/Power",
  "@odata.type": "#Power.v1_5_2.Power",
  "Id": "Power",
  "Name": "Power",
  "PowerControl": [
    {
      "@odata.id": "/redfish/v1/Chassis/chassis/Power#/PowerControl/0",
      "@odata.type": "#Power.v1_0_0.PowerControl",
      "MemberId": "0",
      "Name": "Chassis Power Control",
      "PowerLimit": {
        "LimitInWatts": 2004.0
      }
    }
  ],
  ...
}

Case 3: PowerCapEnable is true, PowerControl and Voltages json
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Chassis/chassis/Power -X PATCH -d '{"PowerControl":[{"PowerLimit"{"LimitInWatts":2001}}], "Voltages": [{"MemberId" : "p0_vcs_voltage", "ReadingVolts":8}]}' -v
...
< HTTP/1.1 204 No Content
...

Case 4: Wrong chassis path
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Chassis/chassi/Power -X PATCH -d '{"PowerControl":[{"PowerLimit":{"LimitInWatts":2001}}]}'
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
        "Message": "The requested resource of type Chassis named chassi was not found.",
        "MessageArgs": [
          "Chassis",
          "chassi"
        ],
        "MessageId": "Base.1.4.0.ResourceNotFound",
        "Resolution": "Provide a valid resource identifier and resubmit the request.",
        "Severity": "Critical"
      }
    ],
    "code": "Base.1.4.0.ResourceNotFound",
    "message": "The requested resource of type Chassis named chassi was not found."
  }
}

Signed-off-by: Carol Wang <wangkair@cn.ibm.com>
Change-Id: Ifabdf053005b31cf3e3539009a1ec20ce4d46d5b
diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp
index eaea5ab..ac7503d 100644
--- a/redfish-core/lib/power.hpp
+++ b/redfish-core/lib/power.hpp
@@ -40,6 +40,109 @@
   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)
+    {
+        auto getChassisPath =
+            [asyncResp, powerControlCollections](
+                const std::optional<std::string>& chassisPath) mutable {
+                if (!chassisPath)
+                {
+                    BMCWEB_LOG_ERROR << "Don't find valid chassis path ";
+                    messages::resourceNotFound(asyncResp->res, "Chassis",
+                                               asyncResp->chassisId);
+                    return;
+                }
+
+                if (powerControlCollections.size() != 1)
+                {
+                    BMCWEB_LOG_ERROR
+                        << "Don't support multiple hosts at present ";
+                    messages::resourceNotFound(asyncResp->res, "Power",
+                                               "PowerControl");
+                    return;
+                }
+
+                auto& item = powerControlCollections[0];
+
+                std::optional<nlohmann::json> powerLimit;
+                if (!json_util::readJson(item, asyncResp->res, "PowerLimit",
+                                         powerLimit))
+                {
+                    return;
+                }
+                if (!powerLimit)
+                {
+                    return;
+                }
+                std::optional<uint32_t> value;
+                if (!json_util::readJson(*powerLimit, asyncResp->res,
+                                         "LimitInWatts", value))
+                {
+                    return;
+                }
+                if (!value)
+                {
+                    return;
+                }
+                auto valueHandler = [value, asyncResp](
+                                        const boost::system::error_code ec,
+                                        const SensorVariant& powerCapEnable) {
+                    if (ec)
+                    {
+                        messages::internalError(asyncResp->res);
+                        BMCWEB_LOG_ERROR
+                            << "powerCapEnable Get handler: Dbus error " << ec;
+                        return;
+                    }
+                    // Check PowerCapEnable
+                    const bool* b =
+                        sdbusplus::message::variant_ns::get_if<bool>(
+                            &powerCapEnable);
+                    if (b == nullptr)
+                    {
+                        messages::internalError(asyncResp->res);
+                        BMCWEB_LOG_ERROR
+                            << "Fail to get PowerCapEnable status ";
+                        return;
+                    }
+                    if (!(*b))
+                    {
+                        messages::actionNotSupported(
+                            asyncResp->res,
+                            "Setting LimitInWatts when PowerLimit "
+                            "feature is disabled");
+                        BMCWEB_LOG_ERROR << "PowerLimit feature is disabled ";
+                        return;
+                    }
+
+                    crow::connections::systemBus->async_method_call(
+                        [asyncResp](const boost::system::error_code ec) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_DEBUG
+                                    << "Power Limit Set: Dbus error: " << ec;
+                                messages::internalError(asyncResp->res);
+                                return;
+                            }
+                            asyncResp->res.result(
+                                boost::beast::http::status::no_content);
+                        },
+                        "xyz.openbmc_project.Settings",
+                        "/xyz/openbmc_project/control/host0/power_cap",
+                        "org.freedesktop.DBus.Properties", "Set",
+                        "xyz.openbmc_project.Control.Power.Cap", "PowerCap",
+                        sdbusplus::message::variant<uint32_t>(*value));
+                };
+                crow::connections::systemBus->async_method_call(
+                    std::move(valueHandler), "xyz.openbmc_project.Settings",
+                    "/xyz/openbmc_project/control/host0/power_cap",
+                    "org.freedesktop.DBus.Properties", "Get",
+                    "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable");
+            };
+        getValidChassisPath(asyncResp, std::move(getChassisPath));
+    }
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
@@ -227,7 +330,38 @@
     void doPatch(crow::Response& res, const crow::Request& req,
                  const std::vector<std::string>& params) override
     {
-        setSensorOverride(res, req, params, typeList, "Power");
+        if (params.size() != 1)
+        {
+            messages::internalError(res);
+            res.end();
+            return;
+        }
+
+        const std::string& chassisName = params[0];
+        auto asyncResp = std::make_shared<SensorsAsyncResp>(res, chassisName,
+                                                            typeList, "Power");
+
+        std::optional<std::vector<nlohmann::json>> voltageCollections;
+        std::optional<std::vector<nlohmann::json>> powerCtlCollections;
+
+        if (!json_util::readJson(req, asyncResp->res, "PowerControl",
+                                 powerCtlCollections, "Voltages",
+                                 voltageCollections))
+        {
+            return;
+        }
+
+        if (powerCtlCollections)
+        {
+            setPowerCapOverride(asyncResp, *powerCtlCollections);
+        }
+        if (voltageCollections)
+        {
+            std::unordered_map<std::string, std::vector<nlohmann::json>>
+                allCollections;
+            allCollections.emplace("Voltages", *std::move(voltageCollections));
+            setSensorOverride(asyncResp, allCollections, chassisName, typeList);
+        }
     }
 };
 
diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp
index c900863..1265783 100644
--- a/redfish-core/lib/sensors.hpp
+++ b/redfish-core/lib/sensors.hpp
@@ -262,6 +262,62 @@
 }
 
 /**
+ * @brief Retrieves valid chassis path
+ * @param asyncResp   Pointer to object holding response data
+ * @param callback  Callback for next step to get valid chassis path
+ */
+template <typename Callback>
+void getValidChassisPath(std::shared_ptr<SensorsAsyncResp> asyncResp,
+                         Callback&& callback)
+{
+    BMCWEB_LOG_DEBUG << "checkChassisId enter";
+    const std::array<const char*, 2> interfaces = {
+        "xyz.openbmc_project.Inventory.Item.Board",
+        "xyz.openbmc_project.Inventory.Item.Chassis"};
+
+    auto respHandler =
+        [callback{std::move(callback)},
+         asyncResp](const boost::system::error_code ec,
+                    const std::vector<std::string>& chassisPaths) mutable {
+            BMCWEB_LOG_DEBUG << "getValidChassisPath respHandler enter";
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR
+                    << "getValidChassisPath respHandler DBUS error: " << ec;
+                messages::internalError(asyncResp->res);
+                return;
+            }
+
+            std::optional<std::string> chassisPath;
+            std::string chassisName;
+            for (const std::string& chassis : chassisPaths)
+            {
+                std::size_t lastPos = chassis.rfind("/");
+                if (lastPos == std::string::npos)
+                {
+                    BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis;
+                    continue;
+                }
+                chassisName = chassis.substr(lastPos + 1);
+                if (chassisName == asyncResp->chassisId)
+                {
+                    chassisPath = chassis;
+                    break;
+                }
+            }
+            callback(chassisPath);
+        };
+
+    // Get the Chassis Collection
+    crow::connections::systemBus->async_method_call(
+        respHandler, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
+        "/xyz/openbmc_project/inventory", 0, interfaces);
+    BMCWEB_LOG_DEBUG << "checkChassisId exit";
+}
+
+/**
  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
  * @param SensorsAsyncResp   Pointer to object holding response data
  * @param callback  Callback for next step in gathered sensor processing
@@ -2329,72 +2385,18 @@
  * @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 allCollections   Collections extract from sensors' request patch info
  * @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::vector<const char*> typeList,
-                       const std::string& chassisSubNode)
+void setSensorOverride(
+    std::shared_ptr<SensorsAsyncResp> sensorAsyncResp,
+    std::unordered_map<std::string, std::vector<nlohmann::json>>&
+        allCollections,
+    const std::string& chassisName, const std::vector<const char*> typeList)
 {
-
-    // 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;
-    }
-
-    std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections;
-    std::optional<std::vector<nlohmann::json>> temperatureCollections;
-    std::optional<std::vector<nlohmann::json>> fanCollections;
-    std::vector<nlohmann::json> voltageCollections;
-    BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode
-                    << "\n";
-
-    if (chassisSubNode == "Thermal")
-    {
-        if (!json_util::readJson(req, res, "Temperatures",
-                                 temperatureCollections, "Fans",
-                                 fanCollections))
-        {
-            return;
-        }
-        if (!temperatureCollections && !fanCollections)
-        {
-            messages::resourceNotFound(res, "Thermal",
-                                       "Temperatures / Voltages");
-            res.end();
-            return;
-        }
-        if (temperatureCollections)
-        {
-            allCollections.emplace("Temperatures",
-                                   *std::move(temperatureCollections));
-        }
-        if (fanCollections)
-        {
-            allCollections.emplace("Fans", *std::move(fanCollections));
-        }
-    }
-    else if (chassisSubNode == "Power")
-    {
-        if (!json_util::readJson(req, res, "Voltages", voltageCollections))
-        {
-            return;
-        }
-        allCollections.emplace("Voltages", std::move(voltageCollections));
-    }
-    else
-    {
-        res.result(boost::beast::http::status::not_found);
-        res.end();
-        return;
-    }
+    BMCWEB_LOG_INFO << "setSensorOverride for subNode"
+                    << sensorAsyncResp->chassisSubNode << "\n";
 
     const char* propertyValueName;
     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
@@ -2416,8 +2418,8 @@
         }
         for (auto& item : collectionItems.second)
         {
-            if (!json_util::readJson(item, res, "MemberId", memberId,
-                                     propertyValueName, value))
+            if (!json_util::readJson(item, sensorAsyncResp->res, "MemberId",
+                                     memberId, propertyValueName, value))
             {
                 return;
             }
@@ -2425,9 +2427,7 @@
                                 std::make_pair(value, collectionItems.first));
         }
     }
-    const std::string& chassisName = params[0];
-    auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
-        res, chassisName, typeList, chassisSubNode);
+
     auto getChassisSensorListCb = [sensorAsyncResp,
                                    overrideMap](const std::shared_ptr<
                                                 boost::container::flat_set<
diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp
index e39aa3e..3ab9e44 100644
--- a/redfish-core/lib/thermal.hpp
+++ b/redfish-core/lib/thermal.hpp
@@ -60,7 +60,45 @@
     void doPatch(crow::Response& res, const crow::Request& req,
                  const std::vector<std::string>& params) override
     {
-        setSensorOverride(res, req, params, typeList, "Thermal");
+        if (params.size() != 1)
+        {
+            res.end();
+            messages::internalError(res);
+            return;
+        }
+
+        const std::string& chassisName = params[0];
+        std::optional<std::vector<nlohmann::json>> temperatureCollections;
+        std::optional<std::vector<nlohmann::json>> fanCollections;
+        std::unordered_map<std::string, std::vector<nlohmann::json>>
+            allCollections;
+
+        auto asyncResp = std::make_shared<SensorsAsyncResp>(
+            res, chassisName, typeList, "Thermal");
+
+        if (!json_util::readJson(req, asyncResp->res, "Temperatures",
+                                 temperatureCollections, "Fans",
+                                 fanCollections))
+        {
+            return;
+        }
+        if (!temperatureCollections && !fanCollections)
+        {
+            messages::resourceNotFound(asyncResp->res, "Thermal",
+                                       "Temperatures / Voltages");
+            return;
+        }
+        if (temperatureCollections)
+        {
+            allCollections.emplace("Temperatures",
+                                   *std::move(temperatureCollections));
+        }
+        if (fanCollections)
+        {
+            allCollections.emplace("Fans", *std::move(fanCollections));
+        }
+
+        setSensorOverride(asyncResp, allCollections, chassisName, typeList);
     }
 };