Implements PowerSupply schema

This commit implements the Redfish PowerSupply schema and populates
the PowerSupplyCollection members. The PowerSupply is a grandchild
of the PowerSubsystem. PowerSupply is part of the new
PowerSubsystme/ThermalSubsystem schemas, released in Redfish Version
2020.4.

This commit only displays the PowerSupplies in the chassis with
common fields like odata.id, odata.type, Id, and Name. Future commits
will add PowerSupply properties like FirmwareVersion,
LocationIndicatorActive, Status, and Asset information like
SerialNumber, PartNumber, Model.

This commit looks at the powered_by association from
Inventory.Item.Chassis to Inventory.Item.PowerSupply to find a
PowerSupply [1].

[1] https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/58609

Tested: Validator passes
1. curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/
PowerSupplies/powersupply0
{
"@odata.id": "/redfish/v1/Chassis/chassis/PowerSubsystem/
              PowerSupplies/powersupply0",
"@odata.type": "#PowerSupply.v1_5_0.PowerSupply",
"Id": "powersupply0",
"Name": "powersupply0"
}

2. Bad power supply name
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/
PowerSupplies/ERROR
{
"error": {
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The requested resource of type PowerSupply named 'ERROR'
            was not found.",
"MessageArgs": [
"PowerSupply",
"ERROR"
],
"MessageId": "Base.1.13.0.ResourceNotFound",
"MessageSeverity": "Critical",
"Resolution": "Provide a valid resource identifier and resubmit the
               request."
}
],
"code": "Base.1.13.0.ResourceNotFound",
"message": "The requested resource of type PowerSupply named 'ERROR'
            was not found."
}
}

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I7b7c0e40c090a3f253f1a778edbe36be9b4317b0
Signed-off-by: Lakshmi Yadlapati <lakshmiy@us.ibm.com>
diff --git a/redfish-core/lib/power_supply.hpp b/redfish-core/lib/power_supply.hpp
index 430a29a..ebc61c5 100644
--- a/redfish-core/lib/power_supply.hpp
+++ b/redfish-core/lib/power_supply.hpp
@@ -15,13 +15,26 @@
 namespace redfish
 {
 
-inline void updatePowerSupplyList(
-    const std::shared_ptr<bmcweb::AsyncResp>& /* asyncResp */,
-    const std::string& /* chassisId */,
-    const std::string& /* powerSupplyPath */)
+inline void
+    updatePowerSupplyList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          const std::string& chassisId,
+                          const std::string& powerSupplyPath)
 {
-    // TODO In order for the validator to pass, the Members property will be
-    // implemented on the next commit
+    std::string powerSupplyName =
+        sdbusplus::message::object_path(powerSupplyPath).filename();
+    if (powerSupplyName.empty())
+    {
+        return;
+    }
+
+    nlohmann::json item = nlohmann::json::object();
+    item["@odata.id"] = boost::urls::format(
+        "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
+        powerSupplyName);
+
+    nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"];
+    powerSupplyList.emplace_back(std::move(item));
+    asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size();
 }
 
 inline void
@@ -123,4 +136,149 @@
             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
 }
 
+inline bool checkPowerSupplyId(const std::string& powerSupplyPath,
+                               const std::string& powerSupplyId)
+{
+    std::string powerSupplyName =
+        sdbusplus::message::object_path(powerSupplyPath).filename();
+
+    return !(powerSupplyName.empty() || powerSupplyName != powerSupplyId);
+}
+
+inline void
+    getValidPowerSupplyPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                            const std::string& validChassisPath,
+                            const std::string& powerSupplyId,
+                            std::function<void()>&& callback)
+{
+    std::string powerPath = validChassisPath + "/powered_by";
+    dbus::utility::getAssociationEndPoints(
+        powerPath, [asyncResp, powerSupplyId, callback{std::move(callback)}](
+                       const boost::system::error_code& ec,
+                       const dbus::utility::MapperEndPoints& endpoints) {
+            if (ec)
+            {
+                if (ec.value() != EBADR)
+                {
+                    BMCWEB_LOG_ERROR
+                        << "DBUS response error for getAssociationEndPoints"
+                        << ec.value();
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                messages::resourceNotFound(asyncResp->res, "PowerSupplies",
+                                           powerSupplyId);
+                return;
+            }
+
+            for (const auto& endpoint : endpoints)
+            {
+                if (checkPowerSupplyId(endpoint, powerSupplyId))
+                {
+                    callback();
+                    return;
+                }
+            }
+
+            if (!endpoints.empty())
+            {
+                BMCWEB_LOG_WARNING << "Power supply not found: "
+                                   << powerSupplyId;
+                messages::resourceNotFound(asyncResp->res, "PowerSupplies",
+                                           powerSupplyId);
+                return;
+            }
+        });
+}
+
+inline void
+    doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                     const std::string& chassisId,
+                     const std::string& powerSupplyId,
+                     const std::optional<std::string>& validChassisPath)
+{
+    if (!validChassisPath)
+    {
+        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
+        return;
+    }
+
+    // Get the correct Path and Service that match the input parameters
+    getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
+                            [asyncResp, chassisId, powerSupplyId]() {
+        asyncResp->res.addHeader(
+            boost::beast::http::field::link,
+            "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
+        asyncResp->res.jsonValue["@odata.type"] =
+            "#PowerSupply.v1_5_0.PowerSupply";
+        asyncResp->res.jsonValue["Name"] = "Power Supply";
+        asyncResp->res.jsonValue["Id"] = powerSupplyId;
+        asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
+            "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
+            powerSupplyId);
+    });
+}
+
+inline void
+    handlePowerSupplyHead(App& app, const crow::Request& req,
+                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          const std::string& chassisId,
+                          const std::string& powerSupplyId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    redfish::chassis_utils::getValidChassisPath(
+        asyncResp, chassisId,
+        [asyncResp, chassisId,
+         powerSupplyId](const std::optional<std::string>& validChassisPath) {
+        if (!validChassisPath)
+        {
+            messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
+            return;
+        }
+
+        // Get the correct Path and Service that match the input parameters
+        getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
+                                [asyncResp]() {
+            asyncResp->res.addHeader(
+                boost::beast::http::field::link,
+                "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
+        });
+        });
+}
+
+inline void
+    handlePowerSupplyGet(App& app, const crow::Request& req,
+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& chassisId,
+                         const std::string& powerSupplyId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    redfish::chassis_utils::getValidChassisPath(
+        asyncResp, chassisId,
+        std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
+}
+
+inline void requestRoutesPowerSupply(App& app)
+{
+    BMCWEB_ROUTE(
+        app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
+        .privileges(redfish::privileges::headPowerSupply)
+        .methods(boost::beast::http::verb::head)(
+            std::bind_front(handlePowerSupplyHead, std::ref(app)));
+
+    BMCWEB_ROUTE(
+        app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
+        .privileges(redfish::privileges::getPowerSupply)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handlePowerSupplyGet, std::ref(app)));
+}
+
 } // namespace redfish