Refactor children_fru_keys into separate arrays

To implement the sanity check in cl/744092037, we must remove any objects from the FruTable that will not be traversed in the topology and are not real scanned Frus.

It is inaccurate to consider Processors and Storage as Fru objects since these are not detected by Fru scanning today. We keep their Id as an array in the associated Fru object. We will use this to construct the link to the object.

As part of this refactor, we separate the children_fru_keys by distinct resource types, the Fru proto will keep a separate array for each type.

#tlbmc

PiperOrigin-RevId: 744892988
Change-Id: Ic1c415dc5e8a01a33d7bde90832701ba5dd5a69e
diff --git a/tlbmc/configs/entity_config_json_impl.cc b/tlbmc/configs/entity_config_json_impl.cc
index cfb351a..159319c 100644
--- a/tlbmc/configs/entity_config_json_impl.cc
+++ b/tlbmc/configs/entity_config_json_impl.cc
@@ -176,10 +176,13 @@
       }
       // Downstream node key is always present in the FRU table so will not
       // check for presence.
-      fru_table.mutable_key_to_fru()
-          ->at(downstream_node.fru_info().fru_key())
-          .set_parent_fru_key(current_fru.attributes().key());
-      current_fru.add_children_fru_keys(downstream_node.fru_info().fru_key());
+      Fru& downstream_fru = fru_table.mutable_key_to_fru()->at(
+          downstream_node.fru_info().fru_key());
+      downstream_fru.set_parent_fru_key(current_fru.attributes().key());
+      if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) {
+        current_fru.add_children_chassis_keys(
+            downstream_node.fru_info().fru_key());
+      }
       node_queue.push(&downstream_node);
     }
 
@@ -210,11 +213,11 @@
             absl::StrCat(current_node->devpath(), "/", "DOWNLINK"));
         node_queue.push(&downstream_node);
         cable_fru.set_parent_fru_key(current_fru.attributes().key());
-        current_fru.add_children_fru_keys(cable_fru.attributes().key());
+        current_fru.add_children_cable_ids(cable_fru.attributes().key());
       } else {
         fru_table.mutable_key_to_fru()
             ->at(downstream_node.fru_info().fru_key())
-            .add_children_fru_keys(cable_fru.attributes().key());
+            .add_children_cable_ids(cable_fru.attributes().key());
       }
     }
   }
@@ -234,20 +237,13 @@
     return absl::OkStatus();
   }
 
-  const std::string* processor_key = GetValueAsString(config, "Name");
-  if (processor_key == nullptr) {
+  const std::string* processor_id = GetValueAsString(config, "Name");
+  if (processor_id == nullptr) {
     return absl::InvalidArgumentError(
         "Invalid config: Name field is not found for processor config");
   }
 
-  Fru processor;
-  processor.mutable_attributes()->set_key(*processor_key);
-  processor.mutable_attributes()->set_resource_type(RESOURCE_TYPE_PROCESSOR);
-  processor.set_parent_fru_key(fru.attributes().key());
-  fru.add_children_fru_keys(*processor_key);
-  fru_table.mutable_key_to_fru()->insert({*processor_key, processor});
-  topology_config.mutable_fru_configs()->insert(
-      {*processor_key, *processor_key});
+  fru.add_children_processor_ids(*processor_id);
   return absl::OkStatus();
 }
 
@@ -264,18 +260,10 @@
     return absl::InvalidArgumentError(
         "Invalid config: Id field is not found for storage config");
   }
-
-  Fru storage;
   std::string storage_id_with_index = *storage_id;
   absl::StrReplaceAll({{"$index", absl::StrCat(index + 1)}},
                       &storage_id_with_index);
-  storage.mutable_attributes()->set_key(storage_id_with_index);
-  storage.mutable_attributes()->set_resource_type(RESOURCE_TYPE_STORAGE);
-  storage.set_parent_fru_key(fru.attributes().key());
-  fru.add_children_fru_keys(storage_id_with_index);
-  fru_table.mutable_key_to_fru()->insert({storage_id_with_index, storage});
-  topology_config.mutable_fru_configs()->insert(
-      {storage_id_with_index, storage_id_with_index});
+  fru.add_children_storage_ids(storage_id_with_index);
 
   return absl::OkStatus();
 }
@@ -669,6 +657,26 @@
   return probed_config_map;
 }
 
+void SortChildrenResourceIds(FruTable& fru_table) {
+  for (auto& [_, fru] : *fru_table.mutable_key_to_fru()) {
+    std::sort(fru.mutable_children_cable_ids()->pointer_begin(),
+              fru.mutable_children_cable_ids()->pointer_end(),
+              [](const std::string* id_1, const std::string* id_2) {
+                return *id_1 < *id_2;
+              });
+    std::sort(fru.mutable_children_processor_ids()->pointer_begin(),
+              fru.mutable_children_processor_ids()->pointer_end(),
+              [](const std::string* id_1, const std::string* id_2) {
+                return *id_1 < *id_2;
+              });
+    std::sort(fru.mutable_children_storage_ids()->pointer_begin(),
+              fru.mutable_children_storage_ids()->pointer_end(),
+              [](const std::string* id_1, const std::string* id_2) {
+                return *id_1 < *id_2;
+              });
+  }
+}
+
 }  // namespace
 
 template <class TypeName>
@@ -1191,6 +1199,9 @@
         .max_updates_to_mutable_data = ad_hoc_fru_count};
   }
 
+  // Sort the children of each Fru
+  SortChildrenResourceIds(mutable_data.fru_table);
+
   // Sanity check that all Raw Frus exist in EM config.
   std::vector<std::string> missing_raw_fru_keys;
   for (const auto& [raw_fru_key, raw_fru] : fru_table.key_to_raw_fru()) {
diff --git a/tlbmc/fru.proto b/tlbmc/fru.proto
index 851338f..0f42b7e 100644
--- a/tlbmc/fru.proto
+++ b/tlbmc/fru.proto
@@ -36,8 +36,12 @@
   FruData data = 3;
   // Parent FRU object.
   string parent_fru_key = 4;
-  // Child FRU objects.
-  repeated string children_fru_keys = 5;
+  // Child resource objects.
+  // TODO(b/409103531): Generalize associations in tlBMC data model.
+  repeated string children_chassis_keys = 5;
+  repeated string children_cable_ids = 6;
+  repeated string children_processor_ids = 7;
+  repeated string children_storage_ids = 8;
 }
 
 // I2C information.
diff --git a/tlbmc/redfish/routes/chassis.cc b/tlbmc/redfish/routes/chassis.cc
index 4c5f0a8..5e24ed4 100644
--- a/tlbmc/redfish/routes/chassis.cc
+++ b/tlbmc/redfish/routes/chassis.cc
@@ -22,6 +22,7 @@
 #include "fru.pb.h"
 #include "resource.pb.h"
 #include "tlbmc/store/store.h"
+#include "google/protobuf/repeated_ptr_field.h"
 
 namespace milotic_tlbmc::chassis {
 
@@ -62,6 +63,47 @@
 
 inline std::string GetSystemId() { return "system"; }
 
+nlohmann::json::array_t CreateChildResourceLinksArray(
+    const ::google::protobuf::RepeatedPtrField<std::string>& child_resource_ids,
+    ResourceType resource_type) {
+  nlohmann::json::array_t child_resources;
+  nlohmann::json::object_t child_object;
+  for (const auto& id : child_resource_ids) {
+    if (resource_type == RESOURCE_TYPE_PROCESSOR) {
+      child_object["@odata.id"] = CreateUrl(
+          {"redfish", "v1", "Systems", GetSystemId(), "Processors", id});
+    } else if (resource_type == RESOURCE_TYPE_STORAGE) {
+      child_object["@odata.id"] =
+          CreateUrl({"redfish", "v1", "Systems", GetSystemId(), "Storage", id});
+    }
+    child_resources.emplace_back(child_object);
+  }
+  return child_resources;
+}
+
+absl::StatusOr<nlohmann::json::array_t> CreateChildResourceLinksArrayFromFru(
+    const ::google::protobuf::RepeatedPtrField<std::string>& child_resource_keys,
+    ResourceType resource_type, const Store& store) {
+  nlohmann::json::array_t child_resources;
+  nlohmann::json::object_t child_object;
+  for (const auto& key : child_resource_keys) {
+    absl::StatusOr<std::string> child_fru_name =
+        store.GetConfigNameByFruKey(key);
+    if (!child_fru_name.ok()) {
+      LOG(ERROR) << "Failed to get config name for child FRU key: " << key;
+      return child_fru_name.status();
+    }
+    if (resource_type == RESOURCE_TYPE_BOARD) {
+      child_object["@odata.id"] =
+          CreateUrl({"redfish", "v1", "Chassis", *child_fru_name});
+    } else if (resource_type == RESOURCE_TYPE_CABLE) {
+      child_object["@odata.id"] =
+          CreateUrl({"redfish", "v1", "Cables", *child_fru_name});
+    }
+    child_resources.emplace_back(child_object);
+  }
+  return child_resources;
+}
 }  // namespace
 
 void FillResponseWithFruData(
@@ -216,86 +258,52 @@
     }
   }
 
-  nlohmann::json::array_t child_cables;
-  nlohmann::json::array_t child_chassis;
-  nlohmann::json::array_t child_processors;
-  nlohmann::json::array_t child_drives;
-  for (const auto& key : fru_ptr->children_fru_keys()) {
-    absl::StatusOr<const Fru*> child_fru = store.GetFru(key);
-
-    if (!child_fru.ok()) {
-      LOG(ERROR) << "Failed to get child FRU for key: " << key;
-      resp.SetToAbslStatus(child_fru.status());
-      return;
-    }
-
-    const Fru* child_fru_ptr = *child_fru;
-
-    nlohmann::json::object_t child_object;
-    absl::StatusOr<std::string> child_fru_name =
-        store.GetConfigNameByFruKey(key);
-    if (!child_fru_name.ok()) {
-      LOG(ERROR) << "Failed to get config name for child FRU key: " << key;
-      resp.SetToAbslStatus(child_fru_name.status());
-      return;
-    }
-
-    switch (child_fru_ptr->attributes().resource_type()) {
-      case RESOURCE_TYPE_BOARD:
-        child_object["@odata.id"] =
-            CreateUrl({"redfish", "v1", "Chassis", *child_fru_name});
-        child_chassis.emplace_back(child_object);
-        break;
-      case RESOURCE_TYPE_CABLE:
-        child_object["@odata.id"] =
-            CreateUrl({"redfish", "v1", "Cables", *child_fru_name});
-        child_cables.emplace_back(child_object);
-        break;
-      case RESOURCE_TYPE_PROCESSOR:
-        child_object["@odata.id"] =
-            CreateUrl({"redfish", "v1", "Systems", GetSystemId(), "Processors",
-                       *child_fru_name});
-        child_processors.emplace_back(child_object);
-        break;
-      case RESOURCE_TYPE_STORAGE:
-        child_object["@odata.id"] =
-            CreateUrl({"redfish", "v1", "Systems", GetSystemId(), "Storage",
-                       *child_fru_name});
-        child_drives.emplace_back(child_object);
-        break;
-      default:
-        continue;
-    }
+  absl::StatusOr<nlohmann::json::array_t> child_chassis =
+      CreateChildResourceLinksArrayFromFru(fru_ptr->children_chassis_keys(),
+                                           RESOURCE_TYPE_BOARD, store);
+  if (!child_chassis.ok()) {
+    resp.SetToAbslStatus(child_chassis.status());
+    return;
   }
-
-  if (!child_chassis.empty()) {
-    std::sort(child_chassis.begin(), child_chassis.end());
+  if (!child_chassis->empty()) {
+    // We cannot avoid sorting child chassis here since order is by config_name
+    // which is not known from the key until this point.
+    std::sort(child_chassis->begin(), child_chassis->end());
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Contains",
-                          child_chassis);
+                          *child_chassis);
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Contains@odata.count",
-                          child_chassis.size());
+                          child_chassis->size());
   }
 
-  if (!child_cables.empty()) {
-    std::sort(child_cables.begin(), child_cables.end());
-    resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables", child_cables);
+  absl::StatusOr<nlohmann::json::array_t> child_cables =
+      CreateChildResourceLinksArrayFromFru(fru_ptr->children_cable_ids(),
+                                           RESOURCE_TYPE_CABLE, store);
+  if (!child_cables.ok()) {
+    resp.SetToAbslStatus(child_cables.status());
+    return;
+  }
+  if (!child_cables->empty()) {
+    resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables", *child_cables);
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables@odata.count",
-                          child_cables.size());
+                          child_cables->size());
   }
 
+  nlohmann::json::array_t child_processors = CreateChildResourceLinksArray(
+      fru_ptr->children_processor_ids(), RESOURCE_TYPE_PROCESSOR);
   if (!child_processors.empty()) {
-    std::sort(child_processors.begin(), child_processors.end());
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Processors",
                           child_processors);
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Processors@odata.count",
                           child_processors.size());
   }
 
-  if (!child_drives.empty()) {
-    std::sort(child_drives.begin(), child_drives.end());
-    resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Storage", child_drives);
+  nlohmann::json::array_t child_storages = CreateChildResourceLinksArray(
+      fru_ptr->children_storage_ids(), RESOURCE_TYPE_STORAGE);
+  if (!child_storages.empty()) {
+    resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Storage",
+                          child_storages);
     resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Storage@odata.count",
-                          child_drives.size());
+                          child_storages.size());
   }
 
   if (fru_ptr->has_parent_fru_key()) {