Implement indicator LED control in Redfish routes

PiperOrigin-RevId: 975965402
Change-Id: Ic3e62ab3e0eb665164c98ea6c63d3f9e6d32a24b
diff --git a/tlbmc/redfish/routes/all_routes.cc b/tlbmc/redfish/routes/all_routes.cc
index afaf813..9324be4 100644
--- a/tlbmc/redfish/routes/all_routes.cc
+++ b/tlbmc/redfish/routes/all_routes.cc
@@ -111,6 +111,9 @@
            PowerControlType::POWER_CONTROL_TYPE_GPIO)) {
     system::RegisterAppendModeRoutes(app);
   }
+  if (GetTlbmcConfig().led_collector_module().enabled()) {
+    system::RegisterIndicatorRoutes(app);
+  }
   if (GetTlbmcConfig().fru_collector_module().own_assemblies_in_redfish()) {
     assembly::RegisterRoutes(app);
   }
diff --git a/tlbmc/redfish/routes/chassis.cc b/tlbmc/redfish/routes/chassis.cc
index d2a4ddc..b3597ac 100644
--- a/tlbmc/redfish/routes/chassis.cc
+++ b/tlbmc/redfish/routes/chassis.cc
@@ -17,7 +17,9 @@
 #include <nlohmann/json_fwd.hpp>
 #include "json_utils.h"
 #include "tlbmc/central_config/config.h"
+#include "tlbmc/collector/led_collector.h"
 #include "tlbmc/collector/power_control_collector.h"
+#include "led_config.pb.h"
 #include "power_control.pb.h"
 #include "topology_config.pb.h"
 #include "tlbmc/redfish/app.h"
@@ -34,6 +36,9 @@
 
 namespace milotic_tlbmc::chassis {
 
+using ::milotic::authz::GetValueAsBool;
+using ::milotic::authz::GetValueAsString;
+
 namespace {
 
 void HandleChassisCollection(const RedfishApp& app, const RedfishRequest& req,
@@ -170,6 +175,45 @@
   }
   return child_resources;
 }
+
+// Injects the IndicatorLED and LocationIndicatorActive properties into the
+// chassis Redfish response if an indicator LED is configured.
+void InjectIndicatorLed(const Store& store,
+                        const nlohmann::json::json_pointer& chassis_pointer,
+                        RedfishResponse& resp) {
+  LedCollector* led_collector = store.GetLedCollector();
+  if (led_collector == nullptr) {
+    return;
+  }
+  if (!led_collector->HasIndicatorConfig()) {
+    return;
+  }
+  if (absl::StatusOr<LedState> state = led_collector->GetIndicatorState();
+      state.ok()) {
+    switch (*state) {
+      case LED_STATE_ON:
+        resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Lit");
+        resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive",
+                              true);
+        break;
+      case LED_STATE_BLINK:
+        resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Blinking");
+        resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive",
+                              true);
+        break;
+      case LED_STATE_OFF:
+        resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Off");
+        resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive",
+                              false);
+        break;
+      case LED_STATE_UNSPECIFIED:
+      default:
+        resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Unknown");
+        break;
+    }
+  }
+}
+
 }  // namespace
 
 void FillResponseWithFruData(
@@ -532,6 +576,8 @@
                           systems);
   }
 
+  InjectIndicatorLed(store, chassis_pointer, resp);
+
   resp.SetKeyInJsonBody(chassis_pointer / "Status" / "Health", "OK");
   resp.SetKeyInJsonBody(chassis_pointer / "Status" / "HealthRollup", "OK");
   resp.SetKeyInJsonBody(chassis_pointer / "Status" / "State", "Enabled");
@@ -705,10 +751,78 @@
   resp.SetKeyInJsonBody("/Parameters", parameters);
 }
 
+// Handles PATCH requests to update the indicator LED state on a Chassis
+// resource.
+void HandleChassisPatch(const RedfishApp& app, const RedfishRequest& req,
+                        RedfishResponse& resp,
+                        const std::string& /*chassis_id*/) {
+  const Store* store = app.GetStore();
+  if (store == nullptr) {
+    resp.SetToInternalError("Store is null");
+    return;
+  }
+
+  LedCollector* led_collector = store->GetLedCollector();
+  if (led_collector == nullptr) {
+    resp.SetToNoContent();
+    return;
+  }
+  if (!led_collector->HasIndicatorConfig()) {
+    resp.SetToNoContent();
+    return;
+  }
+
+  nlohmann::json req_json = nlohmann::json::parse(req.Body(), nullptr, false);
+  if (req_json.is_discarded()) {
+    resp.SetToBadRequest("Failed to parse request body");
+    return;
+  }
+
+  const std::string* indicator_led = GetValueAsString(req_json, "IndicatorLED");
+  const bool* location_indicator_active =
+      GetValueAsBool(req_json, "LocationIndicatorActive");
+
+  if (indicator_led == nullptr && location_indicator_active == nullptr) {
+    resp.SetToNoContent();
+    return;
+  }
+
+  absl::Status status = absl::OkStatus();
+  if (indicator_led != nullptr) {
+    if (*indicator_led == "Lit") {
+      status = led_collector->SetIndicatorState(LED_STATE_ON);
+    } else if (*indicator_led == "Blinking") {
+      status = led_collector->SetIndicatorState(LED_STATE_BLINK);
+    } else if (*indicator_led == "Off") {
+      status = led_collector->SetIndicatorState(LED_STATE_OFF);
+    } else {
+      resp.SetToBadRequest("Invalid IndicatorLED value");
+      return;
+    }
+  } else if (location_indicator_active != nullptr) {
+    if (*location_indicator_active) {
+      status = led_collector->SetIndicatorState(LED_STATE_BLINK);
+    } else {
+      status = led_collector->SetIndicatorState(LED_STATE_OFF);
+    }
+  }
+
+  if (!status.ok()) {
+    LOG(ERROR) << "Failed to set indicator LED: " << status;
+    resp.SetToInternalError("Failed to set indicator LED");
+    return;
+  }
+
+  resp.SetToNoContent();
+}
+
 void RegisterRoutes(RedfishApp& app) {
   TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/")
       .methods(boost::beast::http::verb::get)(
           absl::bind_front(HandleChassis, std::cref(app)));
+  TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/")
+      .methods(boost::beast::http::verb::patch)(
+          absl::bind_front(HandleChassisPatch, std::cref(app)));
   if (GetTlbmcConfig().fru_collector_module().enabled() &&
       GetTlbmcConfig()
           .fru_collector_module()
diff --git a/tlbmc/redfish/routes/system.cc b/tlbmc/redfish/routes/system.cc
index 3ac43d4..5cc12db 100644
--- a/tlbmc/redfish/routes/system.cc
+++ b/tlbmc/redfish/routes/system.cc
@@ -19,7 +19,9 @@
 #include "async_resp.hpp"
 #include <nlohmann/json.hpp>
 #include "json_utils.h"
+#include "tlbmc/collector/led_collector.h"
 #include "tlbmc/collector/power_control_collector.h"
+#include "led_config.pb.h"
 #include "power_control.pb.h"
 #include "tlbmc/host_state/power_control.h"
 #include "tlbmc/redfish/app.h"
@@ -33,11 +35,10 @@
 
 namespace milotic_tlbmc::system {
 
+using ::milotic::authz::GetValueAsBool;
 using ::milotic::authz::GetValueAsString;
 using ::milotic::authz::GetValueAsUintFromStringOrInteger;
 
-
-
 void HandleSystemReset(const RedfishApp& app, const RedfishRequest& req,
                        RedfishResponse& resp, const std::string& system_id) {
   LOG(INFO) << "HandleSystemReset: " << system_id;
@@ -155,6 +156,105 @@
   InjectBiosKeyMetrics(*store, json);
 }
 
+// Appends indicator LED properties (IndicatorLED, LocationIndicatorActive)
+// to the GET response of a System resource if an indicator LED is configured.
+void HandleSystemIndicatorGetAfterGbmcweb(
+    const RedfishApp& app, const ::crow::Request& /*request*/,
+    const std::shared_ptr<bmcweb::AsyncResp>& async_resp,
+    const std::string& system_id) {
+  const Store* store = app.GetStore();
+  LedCollector* led_collector = store->GetLedCollector();
+  if (led_collector == nullptr) {
+    return;
+  }
+  if (!led_collector->HasIndicatorConfig(system_id)) {
+    return;
+  }
+  if (absl::StatusOr<LedState> state =
+          led_collector->GetIndicatorState(system_id);
+      state.ok()) {
+    nlohmann::json& json = async_resp->res.jsonValue;
+    switch (*state) {
+      case LED_STATE_ON:
+        json["IndicatorLED"] = "Lit";
+        json["LocationIndicatorActive"] = true;
+        break;
+      case LED_STATE_BLINK:
+        json["IndicatorLED"] = "Blinking";
+        json["LocationIndicatorActive"] = true;
+        break;
+      case LED_STATE_OFF:
+        json["IndicatorLED"] = "Off";
+        json["LocationIndicatorActive"] = false;
+        break;
+      case LED_STATE_UNSPECIFIED:
+      default:
+        json["IndicatorLED"] = "Unknown";
+        break;
+    }
+  }
+}
+
+// Handles append-mode PATCH requests to update the indicator LED state on a
+// System resource.
+void HandleSystemIndicatorPatchAfterGbmcweb(
+    const RedfishApp& app, const ::crow::Request& request,
+    const std::shared_ptr<bmcweb::AsyncResp>& async_resp,
+    const std::string& system_id) {
+  const Store* store = app.GetStore();
+  LedCollector* led_collector = store->GetLedCollector();
+  if (led_collector == nullptr) {
+    return;
+  }
+  if (!led_collector->HasIndicatorConfig(system_id)) {
+    return;
+  }
+
+  nlohmann::json req_json =
+      nlohmann::json::parse(request.body(), nullptr, false);
+  if (req_json.is_discarded()) {
+    return;
+  }
+
+  const std::string* indicator_led = GetValueAsString(req_json, "IndicatorLED");
+  const bool* location_indicator_active =
+      GetValueAsBool(req_json, "LocationIndicatorActive");
+
+  if (indicator_led == nullptr && location_indicator_active == nullptr) {
+    return;
+  }
+
+  absl::Status status = absl::OkStatus();
+  if (indicator_led != nullptr) {
+    if (*indicator_led == "Lit") {
+      status = led_collector->SetIndicatorState(system_id, LED_STATE_ON);
+    } else if (*indicator_led == "Blinking") {
+      status = led_collector->SetIndicatorState(system_id, LED_STATE_BLINK);
+    } else if (*indicator_led == "Off") {
+      status = led_collector->SetIndicatorState(system_id, LED_STATE_OFF);
+    } else {
+      async_resp->res.result(boost::beast::http::status::bad_request);
+      return;
+    }
+  } else if (location_indicator_active != nullptr) {
+    if (*location_indicator_active) {
+      status = led_collector->SetIndicatorState(system_id, LED_STATE_BLINK);
+    } else {
+      status = led_collector->SetIndicatorState(system_id, LED_STATE_OFF);
+    }
+  }
+
+  if (!status.ok()) {
+    LOG(ERROR) << "Failed to set indicator LED: " << status;
+    async_resp->res.result(boost::beast::http::status::internal_server_error);
+    return;
+  }
+
+  if (async_resp->res.result() == boost::beast::http::status::ok) {
+    async_resp->res.result(boost::beast::http::status::no_content);
+  }
+}
+
 void RegisterResetRoutes(RedfishApp& app) {
   TLBMC_ROUTE(app, "/redfish/v1/Systems/<str>/Actions/ComputerSystem.Reset/")
       .methods(boost::beast::http::verb::post)(
@@ -172,4 +272,15 @@
       absl::bind_front(HandleSystemGetAfterGbmcweb, std::cref(app)));
 }
 
+void RegisterIndicatorRoutes(RedfishApp& app) {
+  TLBMC_APPEND_GBMCWEB(
+      app.GetSmartRouter(), "/redfish/v1/Systems/<str>/",
+      boost::beast::http::verb::get,
+      absl::bind_front(HandleSystemIndicatorGetAfterGbmcweb, std::cref(app)));
+  TLBMC_APPEND_GBMCWEB(
+      app.GetSmartRouter(), "/redfish/v1/Systems/<str>/",
+      boost::beast::http::verb::patch,
+      absl::bind_front(HandleSystemIndicatorPatchAfterGbmcweb, std::cref(app)));
+}
+
 }  // namespace milotic_tlbmc::system
diff --git a/tlbmc/redfish/routes/system.h b/tlbmc/redfish/routes/system.h
index dc53b3f..43d7aab 100644
--- a/tlbmc/redfish/routes/system.h
+++ b/tlbmc/redfish/routes/system.h
@@ -10,6 +10,10 @@
 
 void RegisterAppendModeRoutes(RedfishApp& app);
 
+// Registers append-mode routes for system indicator LEDs
+// (GET and PATCH handlers on /redfish/v1/Systems/<str>/).
+void RegisterIndicatorRoutes(RedfishApp& app);
+
 }  // namespace milotic_tlbmc::system
 
 #endif  // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTES_SYSTEM_H_