Implement LocationIndicatorActive for PowerSupplies

Implement LocationIndicatorActive for PowerSupplies schema to set/get
the status of the location LED for each power supply.[1] When working
with Redfish to get or set the "LocationIndicatorActive" property, the
initial step involves locating the corresponding LED group through the
"identifying" association.[2][3][4] Following this, we can proceed to
either get or set the "Asserted" property.[5]

[1] https://redfish.dmtf.org/schemas/v1/PowerSupply.v1_5_0.json
[2] https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/yaml/xyz/openbmc_project/Led#dbus-interfaces-for-led-groups
[3] https://github.com/openbmc/docs/blob/master/architecture/LED-architecture.md#redfish
[4] https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/58299
[5] https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/Led/Group.interface.yaml

Tested:
 - Validator passes.
 - Tested on p10bmc hardware simulator

1) Get LocationIndicatorActive
```
$ 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",
  "LocationIndicatorActive": false,
  ...
}
```

We will see the powersupply0 identify led is false too.
```
$ busctl get-property xyz.openbmc_project.LED.GroupManager /xyz/openbmc_project/led/groups/powersupply0_identify xyz.openbmc_project.Led.Group Asserted
b false
```

2) Set LocationIndicatorActive to true
```
$ curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":true}' https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/PowerSupplies/powersupply0
```

Then we will see the powersupply0 location LED lit up, and the value
becomes true:
```
$ 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",
  "LocationIndicatorActive": true,
  ...
}

$ busctl get-property xyz.openbmc_project.LED.GroupManager /xyz/openbmc_project/led/groups/powersupply0_identify xyz.openbmc_project.Led.Group Asserted
b true
```

3) Use set-property to change the value back to false
```
$ busctl set-property xyz.openbmc_project.LED.GroupManager /xyz/openbmc_project/led/groups/powersupply0_identify xyz.openbmc_project.Led.Group Asserted b false
$ busctl get-property xyz.openbmc_project.LED.GroupManager /xyz/openbmc_project/led/groups/powersupply0_identify xyz.openbmc_project.Led.Group Asserted
b false
```

Then we will see the value reflected in the Redfish query:
```
$ 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",
  "LocationIndicatorActive": false,
  ...
}
```

4) Error returned when trying to set the value to non-boolean:
```
$ curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":"unknown"}' https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/PowerSupplies/powersupply0
{
  "LocationIndicatorActive@Message.ExtendedInfo": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The value '\"unknown\"' for the property LocationIndicatorActive is not a type that the property can accept.",
      "MessageArgs": [
        "\"unknown\"",
        "LocationIndicatorActive"
      ],
      "MessageId": "Base.1.19.PropertyValueTypeError",
      "MessageSeverity": "Warning",
      "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed."
    }
  ]
}
```

5) Error returned when specifying a unknown power supply:
```
$ curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":true}' https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/PowerSupplies/powersupplyBAD
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type PowerSupplies named 'powersupplyBAD' was not found.",
        "MessageArgs": [
          "PowerSupplies",
          "powersupplyBAD"
        ],
        "MessageId": "Base.1.19.ResourceNotFound",
        "MessageSeverity": "Critical",
        "Resolution": "Provide a valid resource identifier and resubmit the request."
      }
    ],
    "code": "Base.1.19.ResourceNotFound",
    "message": "The requested resource of type PowerSupplies named 'powersupplyBAD' was not found."
  }
}

6) Forced no LED group to test failure paths for GET/PATCH
/* GET succeeds with no LocationIndicatorActive */
$ curl -k -H "X-Auth-Token: $token"  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",
  "EfficiencyRatings": [
    {
      "EfficiencyPercent": 90
    }
  ],
  "FirmwareVersion": "313033323330",
  "Id": "powersupply0",
  "Location": {
    "PartLocation": {
      "ServiceLabel": "U78DA.ND0.1234567-E0"
    }
  },
  "Manufacturer": "",
  "Model": "51E9",
  "Name": "Power Supply",
  "PartNumber": "revisio",
  "SerialNumber": "YL10K serial",
  "SparePartNumber": "c      ",
  "Status": {
    "Health": "OK",
    "State": "Enabled"
  }
}

/* PATCH fails when no LocationIndicatorActive property exists for
 * object
 */
$ curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":true}'  https://${bmc}/redfish/v1/Chassis/chassis/PowerSubsystem/PowerSupplies/powersupply0
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The property LocationIndicatorActive is not in the list of valid properties for the resource.",
        "MessageArgs": [
          "LocationIndicatorActive"
        ],
        "MessageId": "Base.1.19.PropertyUnknown",
        "MessageSeverity": "Warning",
        "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed."
      }
    ],
    "code": "Base.1.19.PropertyUnknown",
    "message": "The property LocationIndicatorActive is not in the list of valid properties for the resource."
  }
}
```

Signed-off-by: Chicago Duan <duanzhijia01@inspur.com>
Change-Id: Id0c30fa3d7c1e7ed4ff7f8fd1d5951d24053fbde
Signed-off-by: Lakshmi Yadlapati <lakshmiy@us.ibm.com>
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
Signed-off-by: Janet Adkins <janeta@us.ibm.com>
diff --git a/Redfish.md b/Redfish.md
index 238ac77..d09927f 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -454,6 +454,7 @@
   - EfficiencyPercent
 - FirmwareVersion
 - Location
+- LocationIndicatorActive
 - Manufacturer
 - Model
 - PartNumber
diff --git a/redfish-core/lib/led.hpp b/redfish-core/lib/led.hpp
index 2bb2cc8..e868bf3 100644
--- a/redfish-core/lib/led.hpp
+++ b/redfish-core/lib/led.hpp
@@ -11,13 +11,22 @@
 #include "logging.hpp"
 #include "utils/dbus_utils.hpp"
 
+#include <asm-generic/errno.h>
+
+#include <boost/system/error_code.hpp>
 #include <sdbusplus/asio/property.hpp>
 #include <sdbusplus/message/native_types.hpp>
 
+#include <array>
+#include <functional>
 #include <memory>
+#include <string_view>
+#include <utility>
 
 namespace redfish
 {
+static constexpr std::array<std::string_view, 1> ledGroupInterface = {
+    "xyz.openbmc_project.Led.Group"};
 /**
  * @brief Retrieves identify led group properties over dbus
  *
@@ -232,4 +241,185 @@
             }
         });
 }
+
+inline void handleLedGroupSubtree(
+    const std::string& objPath, const boost::system::error_code& ec,
+    const dbus::utility::MapperGetSubTreeResponse& subtree,
+    const std::function<void(const boost::system::error_code& ec,
+                             const std::string& ledGroupPath,
+                             const std::string& service)>& callback)
+{
+    if (ec)
+    {
+        // Callback will handle the error
+        callback(ec, "", "");
+        return;
+    }
+
+    if (subtree.empty())
+    {
+        // Callback will handle the error
+        BMCWEB_LOG_DEBUG(
+            "No LED group associated with the specified object path: {}",
+            objPath);
+        callback(ec, "", "");
+        return;
+    }
+
+    if (subtree.size() > 1)
+    {
+        // Callback will handle the error
+        BMCWEB_LOG_DEBUG(
+            "More than one LED group associated with the object {}: {}",
+            objPath, subtree.size());
+        callback(ec, "", "");
+        return;
+    }
+
+    const auto& [ledGroupPath, serviceMap] = *subtree.begin();
+    const auto& [service, interfaces] = *serviceMap.begin();
+    callback(ec, ledGroupPath, service);
+}
+
+inline void getLedGroupPath(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& objPath,
+    std::function<void(const boost::system::error_code& ec,
+                       const std::string& ledGroupPath,
+                       const std::string& service)>&& callback)
+{
+    static constexpr const char* ledObjectPath =
+        "/xyz/openbmc_project/led/groups";
+    sdbusplus::message::object_path ledGroupAssociatedPath =
+        objPath + "/identifying";
+
+    dbus::utility::getAssociatedSubTree(
+        ledGroupAssociatedPath, sdbusplus::message::object_path(ledObjectPath),
+        0, ledGroupInterface,
+        [asyncResp, objPath, callback{std::move(callback)}](
+            const boost::system::error_code& ec,
+            const dbus::utility::MapperGetSubTreeResponse& subtree) {
+            handleLedGroupSubtree(objPath, ec, subtree, callback);
+        });
+}
+
+inline void afterGetLedState(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const boost::system::error_code& ec, bool assert)
+{
+    if (ec)
+    {
+        if (ec.value() != EBADR)
+        {
+            BMCWEB_LOG_ERROR("DBUS response error for get ledState {}",
+                             ec.value());
+            messages::internalError(asyncResp->res);
+        }
+        return;
+    }
+
+    asyncResp->res.jsonValue["LocationIndicatorActive"] = assert;
+}
+
+inline void getLedState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                        const boost::system::error_code& ec,
+                        const std::string& ledGroupPath,
+                        const std::string& service)
+{
+    if (ec)
+    {
+        if (ec.value() != EBADR)
+        {
+            BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
+            messages::internalError(asyncResp->res);
+        }
+        return;
+    }
+
+    if (ledGroupPath.empty() || service.empty())
+    {
+        // No LED group associated, not an error
+        return;
+    }
+
+    sdbusplus::asio::getProperty<bool>(
+        *crow::connections::systemBus, service, ledGroupPath,
+        "xyz.openbmc_project.Led.Group", "Asserted",
+        std::bind_front(afterGetLedState, asyncResp));
+}
+
+/**
+ * @brief Retrieves identify led group properties over dbus
+ *
+ * @param[in] asyncResp Shared pointer for generating response
+ * message.
+ * @param[in] objPath   Object path on PIM
+ *
+ * @return None.
+ */
+inline void getLocationIndicatorActive(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& objPath)
+{
+    BMCWEB_LOG_DEBUG("Get LocationIndicatorActive for {}", objPath);
+    getLedGroupPath(asyncResp, objPath,
+                    [asyncResp](const boost::system::error_code& ec,
+                                const std::string& ledGroupPath,
+                                const std::string& service) {
+                        getLedState(asyncResp, ec, ledGroupPath, service);
+                    });
+}
+
+inline void setLedState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                        bool ledState, const boost::system::error_code& ec,
+                        const std::string& ledGroupPath,
+                        const std::string& service)
+{
+    if (ec)
+    {
+        if (ec.value() == EBADR)
+        {
+            messages::propertyUnknown(asyncResp->res,
+                                      "LocationIndicatorActive");
+            return;
+        }
+        BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    if (ledGroupPath.empty() || service.empty())
+    {
+        messages::propertyUnknown(asyncResp->res, "LocationIndicatorActive");
+        return;
+    }
+
+    setDbusProperty(asyncResp, "LocationIndicatorActive", service, ledGroupPath,
+                    "xyz.openbmc_project.Led.Group", "Asserted", ledState);
+}
+
+/**
+ * @brief Sets identify led group properties
+ *
+ * @param[in] asyncResp     Shared pointer for generating response
+ * message.
+ * @param[in] objPath       Object path on PIM
+ * @param[in] ledState      LED state passed from request
+ *
+ * @return None.
+ */
+inline void setLocationIndicatorActive(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& objPath, bool ledState)
+{
+    BMCWEB_LOG_DEBUG("Set LocationIndicatorActive for {}", objPath);
+    getLedGroupPath(
+        asyncResp, objPath,
+        [asyncResp, ledState](const boost::system::error_code& ec,
+                              const std::string& ledGroupPath,
+                              const std::string& service) {
+            setLedState(asyncResp, ledState, ec, ledGroupPath, service);
+        });
+}
+
 } // namespace redfish
diff --git a/redfish-core/lib/power_supply.hpp b/redfish-core/lib/power_supply.hpp
index 7316b5f..89c84e1 100644
--- a/redfish-core/lib/power_supply.hpp
+++ b/redfish-core/lib/power_supply.hpp
@@ -8,11 +8,13 @@
 #include "error_messages.hpp"
 #include "generated/enums/resource.hpp"
 #include "http_request.hpp"
+#include "led.hpp"
 #include "logging.hpp"
 #include "query.hpp"
 #include "registries/privilege_registry.hpp"
 #include "utils/chassis_utils.hpp"
 #include "utils/dbus_utils.hpp"
+#include "utils/json_utils.hpp"
 #include "utils/time_utils.hpp"
 
 #include <asm-generic/errno.h>
@@ -505,6 +507,7 @@
     getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath);
     getPowerSupplyLocation(asyncResp, service, powerSupplyPath);
     getEfficiencyPercent(asyncResp);
+    getLocationIndicatorActive(asyncResp, powerSupplyPath);
 }
 
 inline void handlePowerSupplyHead(
@@ -549,6 +552,43 @@
         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
 }
 
+inline void doPatchPowerSupply(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const bool locationIndicatorActive, const std::string& powerSupplyPath,
+    const std::string& /*service*/)
+{
+    setLocationIndicatorActive(asyncResp, powerSupplyPath,
+                               locationIndicatorActive);
+}
+
+inline void handlePowerSupplyPatch(
+    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;
+    }
+
+    std::optional<bool> locationIndicatorActive;
+    if (!json_util::readJsonPatch(                             //
+            req, asyncResp->res,                               //
+            "LocationIndicatorActive", locationIndicatorActive //
+            ))
+    {
+        return;
+    }
+
+    if (locationIndicatorActive)
+    {
+        // Get the correct power supply Path that match the input parameters
+        getValidPowerSupplyPath(asyncResp, chassisId, powerSupplyId,
+                                std::bind_front(doPatchPowerSupply, asyncResp,
+                                                *locationIndicatorActive));
+    }
+}
+
 inline void requestRoutesPowerSupply(App& app)
 {
     BMCWEB_ROUTE(
@@ -562,6 +602,12 @@
         .privileges(redfish::privileges::getPowerSupply)
         .methods(boost::beast::http::verb::get)(
             std::bind_front(handlePowerSupplyGet, std::ref(app)));
+
+    BMCWEB_ROUTE(
+        app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
+        .privileges(redfish::privileges::patchPowerSupply)
+        .methods(boost::beast::http::verb::patch)(
+            std::bind_front(handlePowerSupplyPatch, std::ref(app)));
 }
 
 } // namespace redfish