Implement LocationIndicatorActive for CPU resource

Implement LocationIndicatorActive for Processor schema to set and get
the status of the location LED for a CPU.[1]
A client uses the `LocationIndicatorActive` property to physically
identify or locate the processor.

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/Processor.v1_20_1.json

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

1. Get LocationIndicatorActive
```
curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/Processors/cpu0
{
  "@odata.id": "/redfish/v1/Systems/system/Processors/cpu0",
  "@odata.type": "#Processor.v1_18_0.Processor",
  "Id": "cpu0",
  ...
  "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/Processors/cpu0

curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/Processors/cpu0
{
  "@odata.id": "/redfish/v1/Systems/system/Processors/cpu0",
  "@odata.type": "#Processor.v1_18_0.Processor",
  "Id": "cpu0",
  ...
  "LocationIndicatorActive": true,
  ...
}
```

3. Set LocationIndicatorActive to false
```
curl -k -H "X-Auth-Token: $token" -H "Content-Type: application/json" -X PATCH -d '{"LocationIndicatorActive":false}' https://${bmc}/redfish/v1/Systems/system/Processors/cpu0

curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/Processors/cpu0
{
  "@odata.id": "/redfish/v1/Systems/system/Processors/cpu0",
  "@odata.type": "#Processor.v1_18_0.Processor",
  "Id": "cpu0",
  ...
  "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":"badvalue"}' https://${bmc}/redfish/v1/Systems/system/Processors/cpu0
{
  "LocationIndicatorActive@Message.ExtendedInfo": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The value '\"badvalue\"' for the property LocationIndicatorActive is not a type that the property can accept.",
      "MessageArgs": [
        "\"badvalue\"",
        "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 CPU resource
```
curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/Processors/cpu100
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Processor named 'cpu100' was not found.",
        "MessageArgs": [
          "Processor",
          "cpu100"
        ],
        "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 Processor named 'cpu100' 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/Processors/cpu100
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Processor named 'cpu100' was not found.",
        "MessageArgs": [
          "Processor",
          "cpu100"
        ],
        "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 Processor named 'cpu100' was not found."
  }
}
```

Signed-off-by: George Liu <liuxiwei@inspur.com>
Signed-off-by: Janet Adkins <janeta@us.ibm.com>
Change-Id: I511dc9ee0373c227c171d87e0475296eb326741e
diff --git a/Redfish.md b/Redfish.md
index 3770aab..e8a22fa 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -986,6 +986,7 @@
 #### Processor
 
 - InstructionSet
+- LocationIndicatorActive
 - Manufacturer
 - MaxSpeedMHz
 - PartNumber
diff --git a/redfish-core/lib/processor.hpp b/redfish-core/lib/processor.hpp
index d4afd3d..9f41b8d 100644
--- a/redfish-core/lib/processor.hpp
+++ b/redfish-core/lib/processor.hpp
@@ -7,11 +7,13 @@
 
 #include "app.hpp"
 #include "async_resp.hpp"
+#include "dbus_singleton.hpp"
 #include "dbus_utility.hpp"
 #include "error_messages.hpp"
 #include "generated/enums/processor.hpp"
 #include "generated/enums/resource.hpp"
 #include "http_request.hpp"
+#include "led.hpp"
 #include "logging.hpp"
 #include "query.hpp"
 #include "registries/privilege_registry.hpp"
@@ -728,20 +730,66 @@
         });
 }
 
+inline void handleProcessorSubtree(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& processorId,
+    const std::function<
+        void(const std::string& objectPath,
+             const dbus::utility::MapperServiceMap& serviceMap)>& callback,
+    const boost::system::error_code& ec,
+    const dbus::utility::MapperGetSubTreeResponse& subtree)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    for (const auto& [objectPath, serviceMap] : subtree)
+    {
+        // Ignore any objects which don't end with our desired cpu name
+        sdbusplus::message::object_path path(objectPath);
+        if (path.filename() == processorId)
+        {
+            // Filter out objects that don't have the CPU-specific
+            // interfaces to make sure we can return 404 on non-CPUs
+            // (e.g. /redfish/../Processors/dimm0)
+            for (const auto& [serviceName, interfaceList] : serviceMap)
+            {
+                if (std::ranges::find_first_of(interfaceList,
+                                               processorInterfaces) !=
+                    interfaceList.end())
+                {
+                    // Process the first object which matches cpu name and
+                    // required interfaces, and potentially ignore any other
+                    // matching objects. Assume all interfaces we want to
+                    // process must be on the same object path.
+
+                    callback(objectPath, serviceMap);
+                    return;
+                }
+            }
+        }
+    }
+    messages::resourceNotFound(asyncResp->res, "Processor", processorId);
+}
+
 /**
  * Find the D-Bus object representing the requested Processor, and call the
  * handler with the results. If matching object is not found, add 404 error to
  * response and don't call the handler.
  *
- * @param[in,out]   resp            Async HTTP response.
+ * @param[in,out]   asyncResp       Async HTTP response.
  * @param[in]       processorId     Redfish Processor Id.
- * @param[in]       handler         Callback to continue processing request upon
+ * @param[in]       callback        Callback to continue processing request upon
  *                                  successfully finding object.
  */
-template <typename Handler>
-inline void getProcessorObject(const std::shared_ptr<bmcweb::AsyncResp>& resp,
-                               const std::string& processorId,
-                               Handler&& handler)
+inline void getProcessorObject(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& processorId,
+    std::function<void(const std::string& objectPath,
+                       const dbus::utility::MapperServiceMap& serviceMap)>&&
+        callback)
 {
     BMCWEB_LOG_DEBUG("Get available system processor resources.");
 
@@ -758,52 +806,11 @@
         "xyz.openbmc_project.Control.Power.Throttle"};
     dbus::utility::getSubTree(
         "/xyz/openbmc_project/inventory", 0, interfaces,
-        [resp, processorId, handler = std::forward<Handler>(handler)](
+        [asyncResp, processorId, callback{std::move(callback)}](
             const boost::system::error_code& ec,
             const dbus::utility::MapperGetSubTreeResponse& subtree) {
-            if (ec)
-            {
-                BMCWEB_LOG_DEBUG("DBUS response error: {}", ec);
-                messages::internalError(resp->res);
-                return;
-            }
-            for (const auto& [objectPath, serviceMap] : subtree)
-            {
-                // Ignore any objects which don't end with our desired cpu name
-                if (!objectPath.ends_with(processorId))
-                {
-                    continue;
-                }
-
-                bool found = false;
-                // Filter out objects that don't have the CPU-specific
-                // interfaces to make sure we can return 404 on non-CPUs
-                // (e.g. /redfish/../Processors/dimm0)
-                for (const auto& [serviceName, interfaceList] : serviceMap)
-                {
-                    if (std::ranges::find_first_of(interfaceList,
-                                                   processorInterfaces) !=
-                        std::end(interfaceList))
-                    {
-                        found = true;
-                        break;
-                    }
-                }
-
-                if (!found)
-                {
-                    continue;
-                }
-
-                // Process the first object which does match our cpu name and
-                // required interfaces, and potentially ignore any other
-                // matching objects. Assume all interfaces we want to process
-                // must be on the same object path.
-
-                handler(objectPath, serviceMap);
-                return;
-            }
-            messages::resourceNotFound(resp->res, "Processor", processorId);
+            handleProcessorSubtree(asyncResp, processorId, callback, ec,
+                                   subtree);
         });
 }
 
@@ -812,6 +819,14 @@
     const std::string& processorId, const std::string& objectPath,
     const dbus::utility::MapperServiceMap& serviceMap)
 {
+    asyncResp->res.addHeader(
+        boost::beast::http::field::link,
+        "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby");
+    asyncResp->res.jsonValue["@odata.type"] = "#Processor.v1_18_0.Processor";
+    asyncResp->res.jsonValue["@odata.id"] =
+        boost::urls::format("/redfish/v1/Systems/{}/Processors/{}",
+                            BMCWEB_REDFISH_SYSTEM_URI_NAME, processorId);
+
     for (const auto& [serviceName, interfaceList] : serviceMap)
     {
         for (const auto& interface : interfaceList)
@@ -861,6 +876,10 @@
             {
                 getThrottleProperties(asyncResp, serviceName, objectPath);
             }
+            else if (interface == "xyz.openbmc_project.Association.Definitions")
+            {
+                getLocationIndicatorActive(asyncResp, objectPath);
+            }
         }
     }
 }
@@ -1063,6 +1082,97 @@
         "</redfish/v1/JsonSchemas/ProcessorCollection/ProcessorCollection.json>; rel=describedby");
 }
 
+inline void handleProcessorGet(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName, const std::string& processorId)
+{
+    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;
+    }
+
+    getProcessorObject(
+        asyncResp, processorId,
+        std::bind_front(getProcessorData, asyncResp, processorId));
+}
+
+inline void doPatchProcessor(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& processorId,
+    const std::optional<std::string>& appliedConfigUri,
+    std::optional<bool> locationIndicatorActive, const std::string& objectPath,
+    const dbus::utility::MapperServiceMap& serviceMap)
+{
+    if (appliedConfigUri)
+    {
+        patchAppliedOperatingConfig(asyncResp, processorId, *appliedConfigUri,
+                                    objectPath, serviceMap);
+    }
+
+    if (locationIndicatorActive)
+    {
+        // Utility function handles reporting errors
+        setLocationIndicatorActive(asyncResp, objectPath,
+                                   *locationIndicatorActive);
+    }
+}
+
+inline void handleProcessorPatch(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName, const std::string& processorId)
+{
+    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<std::string> appliedConfigUri;
+    std::optional<bool> locationIndicatorActive;
+    if (!json_util::readJsonPatch(
+            req, asyncResp->res,                                  //
+            "AppliedOperatingConfig/@odata.id", appliedConfigUri, //
+            "LocationIndicatorActive", locationIndicatorActive    //
+            ))
+    {
+        return;
+    }
+
+    // Check for 404 and find matching D-Bus object, then run
+    // property patch handlers if that all succeeds.
+    getProcessorObject(
+        asyncResp, processorId,
+        std::bind_front(doPatchProcessor, asyncResp, processorId,
+                        appliedConfigUri, locationIndicatorActive));
+}
+
 inline void requestRoutesOperatingConfigCollection(App& app)
 {
     BMCWEB_ROUTE(app,
@@ -1295,88 +1405,13 @@
 
     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/")
         .privileges(redfish::privileges::getProcessor)
-        .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& processorId) {
-            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;
-            }
-
-            asyncResp->res.addHeader(
-                boost::beast::http::field::link,
-                "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby");
-            asyncResp->res.jsonValue["@odata.type"] =
-                "#Processor.v1_18_0.Processor";
-            asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
-                "/redfish/v1/Systems/{}/Processors/{}",
-                BMCWEB_REDFISH_SYSTEM_URI_NAME, processorId);
-
-            getProcessorObject(
-                asyncResp, processorId,
-                std::bind_front(getProcessorData, asyncResp, processorId));
-        });
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handleProcessorGet, std::ref(app)));
 
     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/")
         .privileges(redfish::privileges::patchProcessor)
         .methods(boost::beast::http::verb::patch)(
-            [&app](const crow::Request& req,
-                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-                   const std::string& systemName,
-                   const std::string& processorId) {
-                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<std::string> appliedConfigUri;
-                if (!json_util::readJsonPatch(
-                        req, asyncResp->res,                                 //
-                        "AppliedOperatingConfig/@odata.id", appliedConfigUri //
-                        ))
-                {
-                    return;
-                }
-
-                if (appliedConfigUri)
-                {
-                    // Check for 404 and find matching D-Bus object, then run
-                    // property patch handlers if that all succeeds.
-                    getProcessorObject(
-                        asyncResp, processorId,
-                        std::bind_front(patchAppliedOperatingConfig, asyncResp,
-                                        processorId, *appliedConfigUri));
-                }
-            });
+            std::bind_front(handleProcessorPatch, std::ref(app)));
 }
 
 } // namespace redfish