Refactor Fast-Sanity GetAllFrus and support state extraction.

This CL:
    1. Update the logic of FRU extraction from hashmap-based to vector-based to improve the scalability, as we can build FRUs during the processing, and avoid out-sourcing GTL.
    2. Support enabling state extraction for GetAllFrus RPC.

#tlbmc-fast-sanity

PiperOrigin-RevId: 783114704
Change-Id: I73c535696ba04fd324b0c6df56f76782a4e54207
diff --git a/tlbmc/service/fru_service.cc b/tlbmc/service/fru_service.cc
index 1e7c75d..f80ce21 100644
--- a/tlbmc/service/fru_service.cc
+++ b/tlbmc/service/fru_service.cc
@@ -8,7 +8,6 @@
 #include <vector>
 
 #include "absl/base/no_destructor.h"
-#include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/log/log.h"
 #include "absl/status/status.h"
@@ -43,6 +42,8 @@
 using ::milotic::authz::GetValueAsJson;
 using ::milotic::authz::GetValueAsString;
 
+using FruComponentStatus = FruComponent::Status;
+
 constexpr absl::string_view kRootChassisUrl = "/redfish/v1/Chassis?$expand=.";
 // TODO(b/415893570): Make this config-based.
 // Be ware: BB has `"/redfish/v1/Systems/system1/Memory"` as the memory
@@ -129,7 +130,28 @@
   return supported_location_types->contains(std::string(location_type_json));
 }
 
-absl::StatusOr<Devpath> ExtractDevpathFromJson(
+FruComponentStatus ExtractFruStatus(const nlohmann::json &fru) {
+  if (!fru.contains("Status") || !fru["Status"].contains("State") ||
+      fru["Status"]["State"] == "Enabled") {
+    return FruComponent::STATUS_OK;
+  }
+  if (fru["Status"]["State"] == "Absent") {
+    return FruComponent::STATUS_NOT_FOUND;
+  }
+  return FruComponent::STATUS_OK;
+}
+
+FruComponent BuildFruComponent(absl::string_view devpath,
+                               FruComponentStatus status) {
+  FruComponent fru;
+  fru.mutable_primary_identifier()->set_value(devpath);
+  fru.mutable_primary_identifier()->set_type(
+      UniqueIdentifierType::UNIQUE_IDENTIFIER_TYPE_LOCAL_DEVPATH);
+  fru.set_status(status);
+  return fru;
+}
+
+absl::StatusOr<FruComponent> ExtractFruComponentFromJson(
     const nlohmann::json &json, absl::string_view root_chassis_location_code) {
   ECCLESIA_RETURN_IF_ERROR(IsLocationInfoValid(json));
 
@@ -138,19 +160,23 @@
       json["Location"]["PartLocation"].find("LocationType");
   if (location_type_it == json["Location"]["PartLocation"].end() ||
       !IsLocationTypeSupported(*location_type_it)) {
-    return Devpath();
+    return absl::InvalidArgumentError(
+        "Invalid location info in the Redfish response.");
   }
 
-  // This is the root chassis. Return empty devpath.
+  FruComponentStatus status = ExtractFruStatus(json);
+
   const auto &res_member_location = json["Location"];
   std::string service_label =
       res_member_location["PartLocation"]["ServiceLabel"];
-  if (service_label == root_chassis_location_code) {
-    return Devpath();
+  // This is the root chassis.
+  if (service_label == root_chassis_location_code ||
+      service_label == kLocalRootChassisLocationCode) {
+    return absl::InternalError("Root chassis FRU will be built separately.");
   }
 
-  // PartLocationContext is optional, and the prefix should be removed if it is
-  // equal to the root chassis location code.
+  // PartLocationContext is optional, and the prefix should be removed if it
+  // is equal to the root chassis location code.
   std::string part_location_context;
   const auto part_location_context_it =
       res_member_location.find("PartLocationContext");
@@ -173,87 +199,52 @@
                   kLocalRootChassisLocationCode.length());
   }
 
-  return Devpath(part_location_context, service_label);
+  return BuildFruComponent(
+      absl::StrFormat(
+          "/%s/%s%s", kLocalRootChassisLocationCode,
+          (!part_location_context.empty() ? part_location_context + "/" : ""),
+          service_label),
+      status);
 }
 
-void InsertDevpath(const nlohmann::json &json,
-                   absl::string_view root_chassis_location_code,
-                   std::shared_ptr<PartLocationContextToServiceLabelSet>
-                       &part_location_context_to_service_label_set) {
-  absl::StatusOr<Devpath> devpath_status =
-      ExtractDevpathFromJson(json, root_chassis_location_code);
+void GenerateFruComponentsFromRedfishObject(
+    const nlohmann::json &json, absl::string_view root_chassis_location_code,
+    const std::shared_ptr<FruComponents> &fru_components) {
+  absl::StatusOr<FruComponent> fru_info_status =
+      ExtractFruComponentFromJson(json, root_chassis_location_code);
   // TODO(b/422818770): Utilize the error status better.
   //  E.g., provide logs.
-  if (!devpath_status.ok() || devpath_status.value().service_label.empty()) {
-    return;
+  if (fru_info_status.ok()) {
+    fru_components->AddFruComponent(std::move(fru_info_status.value()));
   }
-
-  absl::MutexLock lock(&part_location_context_to_service_label_set->mutex);
-  part_location_context_to_service_label_set
-      ->part_location_context_to_service_label_set[devpath_status.value()
-                                                       .part_location_context]
-      .insert(devpath_status.value().service_label);
-}
-
-void GenerateDevpathFromRedfishObject(
-    const nlohmann::json &json, absl::string_view root_chassis_location_code,
-    std::shared_ptr<PartLocationContextToServiceLabelSet>
-        part_location_context_to_service_label_set) {
-  InsertDevpath(json, root_chassis_location_code,
-                part_location_context_to_service_label_set);
   const auto assembly_it = json.find("Assembly");
   if (assembly_it != json.end() &&
       assembly_it->find("Assemblies") != json["Assembly"].end()) {
     for (const auto &fru : (*assembly_it)["Assemblies"]) {
-      GenerateDevpathFromRedfishObject(
-          fru, root_chassis_location_code,
-          part_location_context_to_service_label_set);
+      GenerateFruComponentsFromRedfishObject(fru, root_chassis_location_code,
+                                             fru_components);
     }
   }
   if (json.contains("Members")) {
     for (const auto &fru : json["Members"]) {
-      GenerateDevpathFromRedfishObject(
-          fru, root_chassis_location_code,
-          part_location_context_to_service_label_set);
+      GenerateFruComponentsFromRedfishObject(fru, root_chassis_location_code,
+                                             fru_components);
     }
   }
 }
 
 void SendGetAllFruInfoResponse(
     ServerUnaryReactor &reactor, GetAllFruInfoResponse &response,
-    const std::shared_ptr<PartLocationContextToServiceLabelSet>
-        &part_location_context_to_service_label_set) {
-  // Build the devpath list
-  // Insert the default local root chassis devpath first.
-  std::vector<std::string> devpaths({"/phys"});
-
-  {
-    absl::MutexLock lock(&part_location_context_to_service_label_set->mutex);
-    for (const auto &[part_location_context, service_label_set] :
-         part_location_context_to_service_label_set
-             ->part_location_context_to_service_label_set) {
-      for (const auto &service_label : service_label_set) {
-        devpaths.push_back(absl::StrCat(
-            "/", kLocalRootChassisLocationCode, "/",
-            (!part_location_context.empty() ? part_location_context + "/" : ""),
-            service_label));
-      }
-    }
-  }
-
-  if (devpaths.empty()) {
+    const std::shared_ptr<FruComponents> &fru_components) {
+  absl::MutexLock lock(&fru_components->mutex);
+  if (fru_components->fru_components.empty()) {
     reactor.Finish(::grpc::Status(::grpc::StatusCode::INTERNAL,
                                   "No matched devpath found."));
   }
-
-  for (const std::string &devpath : devpaths) {
-    if (devpath.empty()) {
+  for (FruComponent &fru : fru_components->fru_components) {
+    if (fru.primary_identifier().value().empty()) {
       continue;
     }
-    FruComponent fru;
-    fru.mutable_primary_identifier()->set_value(devpath);
-    fru.mutable_primary_identifier()->set_type(
-        UniqueIdentifierType::UNIQUE_IDENTIFIER_TYPE_LOCAL_DEVPATH);
     response.mutable_fru_components()->Add(std::move(fru));
   }
   reactor.Finish(::grpc::Status(::grpc::StatusCode::OK, "Devpath list sent."));
@@ -364,9 +355,15 @@
     ServerUnaryReactor &reactor, const GetAllFruInfoRequest &request,
     GetAllFruInfoResponse &response,
     absl::string_view root_chassis_location_code) {
-  std::shared_ptr<PartLocationContextToServiceLabelSet>
-      part_location_context_to_service_label_set =
-          std::make_shared<PartLocationContextToServiceLabelSet>();
+  auto fru_components = std::make_shared<FruComponents>();
+
+  // The root chassis is successfully extracted. Need to explicitly add its
+  // corresponding FRU into the list, as some platforms do not have a root
+  // chassis location code to be identified as a devpath.
+  FruComponent root_fru_component =
+      BuildFruComponent(absl::StrCat("/", kLocalRootChassisLocationCode),
+                        FruComponent::STATUS_OK);
+  fru_components->AddFruComponent(std::move(root_fru_component));
 
   std::shared_ptr<OngoingDevpathExtractionRequestCount>
       ongoing_devpath_extraction_request_count =
@@ -388,13 +385,12 @@
 
     // Extract devpaths from the rest of the requests.
     async_devpath_resp->res.setCompleteRequestHandler(
-        [&reactor, &response, url, part_location_context_to_service_label_set,
+        [&reactor, &response, url, fru_components,
          root_chassis_location_code = std::string(root_chassis_location_code),
          ongoing_devpath_extraction_request_count](crow::Response &res) {
           if (res.stringResponse.has_value()) {
-            GenerateDevpathFromRedfishObject(
-                res.jsonValue, root_chassis_location_code,
-                part_location_context_to_service_label_set);
+            GenerateFruComponentsFromRedfishObject(
+                res.jsonValue, root_chassis_location_code, fru_components);
           } else {
             LOG(WARNING) << "No valid response from redfish for url: `" << url
                          << "`";
@@ -407,8 +403,7 @@
               &ongoing_devpath_extraction_request_count->mutex);
           if (ongoing_devpath_extraction_request_count
                   ->ongoing_devpath_extraction_request_count == 0) {
-            SendGetAllFruInfoResponse(
-                reactor, response, part_location_context_to_service_label_set);
+            SendGetAllFruInfoResponse(reactor, response, fru_components);
           }
         });
     app_->handle(*devpath_request_status, async_devpath_resp);
diff --git a/tlbmc/service/fru_service.h b/tlbmc/service/fru_service.h
index 91a589e..4a2bdc5 100644
--- a/tlbmc/service/fru_service.h
+++ b/tlbmc/service/fru_service.h
@@ -2,17 +2,17 @@
 #define THIRD_PARTY_MILOTIC_EXTERNAL_CC_FAST_SANITY_SERVICE_FRU_SERVICE_H_
 
 #include <cstdint>
-#include <string>
+#include <utility>
+#include <vector>
 
 #include "absl/base/thread_annotations.h"
-#include "absl/container/flat_hash_map.h"
-#include "absl/container/flat_hash_set.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/mutex.h"
 #include "grpcpp/security/auth_context.h"
 #include "grpcpp/server_context.h"
 #include "grpcpp/support/server_callback.h"
 #include "grpcpp/support/status.h"
+#include "fru_component_model.pb.h"
 #include "fru_service.grpc.pb.h"
 #include "app.hpp"
 #include "dbus_utility.hpp" // NOLINT
@@ -28,22 +28,14 @@
 using FruService = ::milotic_fast_sanity::FruService;
 #endif
 
-struct Devpath {
-  std::string part_location_context;
-  std::string service_label;
-
-  Devpath() = default;
-
-  Devpath(absl::string_view part_location_context,
-          absl::string_view service_label)
-      : part_location_context(part_location_context),
-        service_label(service_label) {}
-};
-
-struct PartLocationContextToServiceLabelSet {
+struct FruComponents {
   absl::Mutex mutex;
-  absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
-      part_location_context_to_service_label_set ABSL_GUARDED_BY(mutex);
+  std::vector<FruComponent> fru_components ABSL_GUARDED_BY(mutex);
+
+  void AddFruComponent(FruComponent&& fru_component) {
+    absl::MutexLock lock(&mutex);
+    fru_components.push_back(std::move(fru_component));
+  }
 };
 
 struct FruServiceOptions {