Implement LocationIndicatorActive for Memory resource

Implement LocationIndicatorActive for Memory schema to set/get the
status of the location LED for a Dimm.[1]

Uses the utility functions getLocationIndicatorActive() and
setLocationIndicatorActive() to follow the association and get or set
the LED value.

[1] https://redfish.dmtf.org/schemas/v1/Memory.v1_20_0.json

Tested:
 - Note: The pre-existing code had support for finding objects
   implementing the interface
   "xyz.openbmc_project.Inventory.Item.PersistentMemory.Partition". From
   the comments it looks like these are separate objects on the D-Bus.
   The property information for these objects are added to the response
   for the Dimm. I kept the GET path similar to avoid regressing that
   support. However I was not able to test it as our hardware does not
   implement that interface. (Those interfaces are not part of the PATCH
   path.)
 - Redfish service validator passes
 - Tested on p10bmc hardware simulator:

 1. Get LocationIndicatorActive
```
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Systems/system/Memory/dimm0
{
  "@odata.id": "/redfish/v1/Systems/system/Memory/dimm0",
  "@odata.type": "#Memory.v1_11_0.Memory",
  "LocationIndicatorActive": 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/Systems/system/Memory/dimm0
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Systems/system/Memory/dimm0
{
  "@odata.id": "/redfish/v1/Systems/system/Memory/dimm0",
  "@odata.type": "#Memory.v1_11_0.Memory",
  "LocationIndicatorActive": true,
  ...
}
```

 3. Use busctl set-propery to change the value back to false
```
$ busctl --json=pretty call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/object_mapper xyz.openbmc_project.ObjectMapper GetAssociatedSubTreePaths ooias /xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0/identifying /xyz/openbmc_project/led/groups 0 1 xyz.openbmc_project.Led.Group
{
        "type" : "as",
        "data" : [
                [
                        "/xyz/openbmc_project/led/groups/ddimm16_identify"
                ]
        ]
}
$ busctl set-property xyz.openbmc_project.LED.GroupManager /xyz/openbmc_project/led/groups/ddimm16_identify xyz.openbmc_project.Led.Group Asserted b false
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Systems/system/Memory/dimm0
{
  "@odata.id": "/redfish/v1/Systems/system/Memory/dimm0",
  "@odata.type": "#Memory.v1_11_0.Memory",
  "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/Systems/system/Memory/dimm0
{
  "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 an unknown Memory resource
```
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Systems/system/Memory/dimm300
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Memory named 'dimm300' was not found.",
        "MessageArgs": [
          "Memory",
          "dimm300"
        ],
        "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 Memory named 'dimm300' was not found."
  }
}

$ curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":true}' https://${bmc}/redfish/v1/Systems/system/Memory/dimm300
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Memory named 'dimm300' was not found.",
        "MessageArgs": [
          "Memory",
          "dimm300"
        ],
        "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 Memory named 'dimm300' was not found."
  }
}
```

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: Ifb705003380c2a83ada8a645b346b0ae52a06183
Signed-off-by: Janet Adkins <janeta@us.ibm.com>
diff --git a/redfish-core/lib/memory.hpp b/redfish-core/lib/memory.hpp
index 59b473d..79ccdac 100644
--- a/redfish-core/lib/memory.hpp
+++ b/redfish-core/lib/memory.hpp
@@ -12,12 +12,16 @@
 #include "generated/enums/memory.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/collection.hpp"
 #include "utils/dbus_utils.hpp"
 #include "utils/hex_utils.hpp"
+#include "utils/json_utils.hpp"
+
+#include <asm-generic/errno.h>
 
 #include <boost/beast/http/verb.hpp>
 #include <boost/system/error_code.hpp>
@@ -29,7 +33,9 @@
 #include <array>
 #include <cstddef>
 #include <cstdint>
+#include <functional>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <utility>
@@ -720,71 +726,239 @@
     );
 }
 
-inline void getDimmData(std::shared_ptr<bmcweb::AsyncResp> asyncResp,
-                        const std::string& dimmId)
+inline void afterGetDimmData(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& dimmId, const boost::system::error_code& ec,
+    const dbus::utility::MapperGetSubTreeResponse& subtree)
 {
-    BMCWEB_LOG_DEBUG("Get available system dimm resources.");
-    constexpr std::array<std::string_view, 2> dimmInterfaces = {
-        "xyz.openbmc_project.Inventory.Item.Dimm",
-        "xyz.openbmc_project.Inventory.Item.PersistentMemory.Partition"};
-    dbus::utility::getSubTree(
-        "/xyz/openbmc_project/inventory", 0, dimmInterfaces,
-        [dimmId, asyncResp{std::move(asyncResp)}](
-            const boost::system::error_code& ec,
-            const dbus::utility::MapperGetSubTreeResponse& subtree) {
-            if (ec)
-            {
-                BMCWEB_LOG_DEBUG("DBUS response error");
-                messages::internalError(asyncResp->res);
+    if (ec)
+    {
+        if (ec.value() != EBADR)
+        {
+            BMCWEB_LOG_ERROR("DBUS response error: {}", ec.value());
+            messages::internalError(asyncResp->res);
+        }
+        return;
+    }
 
-                return;
-            }
-            bool found = false;
-            for (const auto& [rawPath, object] : subtree)
+    bool found = false;
+    for (const auto& [objectPath, serviceMap] : subtree)
+    {
+        sdbusplus::message::object_path path(objectPath);
+
+        bool dimmInterface = false;
+        bool associationInterface = false;
+        /* Note: Multiple D-Bus objects can provide details for the Memory
+         * object: 1) Dimm is the primary object 2) Additional partitions could
+         * exist per Dimm. Only consider the object found if the Dimm is found.
+         */
+        for (const auto& [serviceName, interfaceList] : serviceMap)
+        {
+            for (const auto& interface : interfaceList)
             {
-                sdbusplus::message::object_path path(rawPath);
-                for (const auto& [service, interfaces] : object)
+                if (interface == "xyz.openbmc_project.Inventory.Item.Dimm" &&
+                    path.filename() == dimmId)
                 {
-                    for (const auto& interface : interfaces)
-                    {
-                        if (interface ==
-                                "xyz.openbmc_project.Inventory.Item.Dimm" &&
-                            path.filename() == dimmId)
-                        {
-                            getDimmDataByService(asyncResp, dimmId, service,
-                                                 rawPath);
-                            found = true;
-                        }
-
-                        // partitions are separate as there can be multiple
-                        // per
-                        // device, i.e.
-                        // /xyz/openbmc_project/Inventory/Item/Dimm1/Partition1
-                        // /xyz/openbmc_project/Inventory/Item/Dimm1/Partition2
-                        if (interface ==
-                                "xyz.openbmc_project.Inventory.Item.PersistentMemory.Partition" &&
-                            path.parent_path().filename() == dimmId)
-                        {
-                            getDimmPartitionData(asyncResp, service, rawPath);
-                        }
-                    }
+                    // Found the single Dimm
+                    getDimmDataByService(asyncResp, dimmId, serviceName,
+                                         objectPath);
+                    dimmInterface = true;
+                    found = true;
+                }
+                else if (interface ==
+                         "xyz.openbmc_project.Association.Definitions")
+                {
+                    /* Object has associations. If this object is also a Dimm
+                     * then the association might provide the LED state
+                     * information. After all interfaces for this object have
+                     * been checked the LED information will be gathered if the
+                     * object was a Dimm
+                     */
+                    associationInterface = true;
+                }
+                else if (
+                    interface ==
+                        "xyz.openbmc_project.Inventory.Item.PersistentMemory.Partition" &&
+                    path.parent_path().filename() == dimmId)
+                {
+                    // partitions are separate as there can be multiple per
+                    // device, i.e.
+                    // /xyz/openbmc_project/Inventory/Item/Dimm1/Partition1
+                    // /xyz/openbmc_project/Inventory/Item/Dimm1/Partition2
+                    getDimmPartitionData(asyncResp, serviceName, objectPath);
                 }
             }
-            // Object not found
-            if (!found)
-            {
-                messages::resourceNotFound(asyncResp->res, "Memory", dimmId);
-                return;
-            }
-            // Set @odata only if object is found
-            asyncResp->res.jsonValue["@odata.type"] = "#Memory.v1_11_0.Memory";
-            asyncResp->res.jsonValue["@odata.id"] =
-                boost::urls::format("/redfish/v1/Systems/{}/Memory/{}",
-                                    BMCWEB_REDFISH_SYSTEM_URI_NAME, dimmId);
-            return;
+        }
+
+        /* If a Dimm has an Association check if it has a LED */
+        if (associationInterface && dimmInterface)
+        {
+            getLocationIndicatorActive(asyncResp, objectPath);
+        }
+    }
+
+    if (!found)
+    {
+        // Dimm object not found
+        messages::resourceNotFound(asyncResp->res, "Memory", dimmId);
+        return;
+    }
+    // Set @odata only if object is found
+    asyncResp->res.jsonValue["@odata.type"] = "#Memory.v1_11_0.Memory";
+    asyncResp->res.jsonValue["@odata.id"] =
+        boost::urls::format("/redfish/v1/Systems/{}/Memory/{}",
+                            BMCWEB_REDFISH_SYSTEM_URI_NAME, dimmId);
+}
+
+inline void getDimmData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                        const std::string& dimmId)
+{
+    BMCWEB_LOG_DEBUG("Get dimm path for {}", dimmId);
+    constexpr std::array<std::string_view, 2> interfaces = {
+        "xyz.openbmc_project.Inventory.Item.Dimm",
+        "xyz.openbmc_project.Inventory.Item.PersistentMemory.Partition"};
+
+    dbus::utility::getSubTree(
+        "/xyz/openbmc_project/inventory", 0, interfaces,
+        [asyncResp,
+         dimmId](const boost::system::error_code& ec,
+                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
+            afterGetDimmData(asyncResp, dimmId, ec, subtree);
         });
 }
 
+inline void handleSetDimmData(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    bool locationIndicatorActive, const std::string& dimmPath)
+{
+    setLocationIndicatorActive(asyncResp, dimmPath, locationIndicatorActive);
+}
+
+inline void afterGetValidDimmPath(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& dimmId, const boost::system::error_code& ec,
+    const dbus::utility::MapperGetSubTreePathsResponse& subtree,
+    const std::function<void(const std::string& dimmPath)>& callback)
+{
+    if (ec)
+    {
+        if (ec.value() == EBADR)
+        {
+            /* Need to report error for PATCH */
+            BMCWEB_LOG_WARNING("Dimm not found in inventory");
+            messages::resourceNotFound(asyncResp->res, "Memory", dimmId);
+        }
+        else
+        {
+            BMCWEB_LOG_ERROR("DBUS response error: {}", ec.value());
+            messages::internalError(asyncResp->res);
+        }
+        return;
+    }
+
+    for (const auto& objectPath : subtree)
+    {
+        // Ignore any objects which don't end with our desired dimm name
+        sdbusplus::message::object_path path(objectPath);
+        if (path.filename() == dimmId)
+        {
+            callback(path);
+            return;
+        }
+    }
+
+    // Object not found
+    messages::resourceNotFound(asyncResp->res, "Memory", dimmId);
+}
+
+inline void getValidDimmPath(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& dimmId,
+    std::function<void(const std::string& dimmPath)>&& callback)
+{
+    BMCWEB_LOG_DEBUG("Get dimm path for {}", dimmId);
+    constexpr std::array<std::string_view, 1> interfaces = {
+        "xyz.openbmc_project.Inventory.Item.Dimm"};
+
+    dbus::utility::getSubTreePaths(
+        "/xyz/openbmc_project/inventory", 0, interfaces,
+        [asyncResp, dimmId, callback{std::move(callback)}](
+            const boost::system::error_code& ec,
+            const dbus::utility::MapperGetSubTreePathsResponse& subtree) {
+            afterGetValidDimmPath(asyncResp, dimmId, ec, subtree, callback);
+        });
+}
+
+inline void handleMemoryPatch(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName, const std::string& dimmId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+    {
+        // Option currently returns no systems.  TBD
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    std::optional<bool> locationIndicatorActive;
+    if (!json_util::readJsonPatch(                             //
+            req, asyncResp->res,                               //
+            "LocationIndicatorActive", locationIndicatorActive //
+            ))
+    {
+        return;
+    }
+
+    if (locationIndicatorActive)
+    {
+        getValidDimmPath(asyncResp, dimmId,
+                         std::bind_front(handleSetDimmData, asyncResp,
+                                         *locationIndicatorActive));
+    }
+}
+
+inline void handleMemoryGet(App& app, const crow::Request& req,
+                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                            const std::string& systemName,
+                            const std::string& dimmId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+    {
+        // Option currently returns no systems.  TBD
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    getDimmData(asyncResp, dimmId);
+}
+
 inline void requestRoutesMemoryCollection(App& app)
 {
     /**
@@ -839,31 +1013,12 @@
     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Memory/<str>/")
         .privileges(redfish::privileges::getMemory)
         .methods(boost::beast::http::verb::get)(
-            [&app](const crow::Request& req,
-                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-                   const std::string& systemName, const std::string& dimmId) {
-                if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-                {
-                    return;
-                }
+            std::bind_front(handleMemoryGet, std::ref(app)));
 
-                if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
-                {
-                    // Option currently returns no systems.  TBD
-                    messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                               systemName);
-                    return;
-                }
-
-                if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
-                {
-                    messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                               systemName);
-                    return;
-                }
-
-                getDimmData(asyncResp, dimmId);
-            });
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Memory/<str>/")
+        .privileges(redfish::privileges::patchMemory)
+        .methods(boost::beast::http::verb::patch)(
+            std::bind_front(handleMemoryPatch, std::ref(app)));
 }
 
 } // namespace redfish