Propagate Assembly sensors to parent board

Implement logic to propagate all sensors configured in the EM config of an Assembly to the parent board. This involves:

1. Creation of a Assembly to Parent Config Board map to perform lookup at sensor creation time to correctly assign ownership of the Assembly sensors to the parent config board. This map is populated at topology creation time. If a sensor's assigned board_config_name is in this map, it indicates that this is an Assembly, and therefore we must override the set board_config_name.

    Note: Since Assemblies are a concept specific to Redfish, this is implemented in the RedfishIndexes field of the EntityConfig. As future protocols are supported, similar fields may be added to support concepts specific to different protocols.

2. Logic for sensor board_config_name override at sensor creation time. Since sensor configs are immutable outside of entity_config_json_impl.cc, we need to provide an override field when sensors are created to propagate sensors correctly to parent board and avoid the board_config_name set when the sensor was originally parsed.

3. Default RelatedItem assigning for Assemblies. Since we have now propagated the parent Board to the created Sensor's EntityCommonConfig, if the RelatedItem for a sensor is still an Assembly type, indicating that the RelatedItem was not overridden in EM config, we assign the parent board as the RelatedItem.

#tlbmc

Tested: unit tests and e2e test in follow up cl/759162080
PiperOrigin-RevId: 761737715
Change-Id: I65ba2638aeeb0f376f4a7ac03ad5f054e30464a7
diff --git a/tlbmc/collector/sensor_collector.cc b/tlbmc/collector/sensor_collector.cc
index 67f0f3d..0de23eb 100644
--- a/tlbmc/collector/sensor_collector.cc
+++ b/tlbmc/collector/sensor_collector.cc
@@ -4,6 +4,7 @@
 #include <cstddef>
 #include <functional>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -72,28 +73,28 @@
     LOG(INFO) << "Scheduling Sensor Read! Sensor key is " << sensor->GetKey()
               << " and interval is " << interval;
 
-    thread_manager.sensor_key_to_task_id
-        [sensor->GetKey()] = thread_manager.task_scheduler->RunAndScheduleAsync(
-        [sensor = std::weak_ptr<Sensor>(sensor),
-         refresh_notification =
-             params.refresh_notification](absl::AnyInvocable<void()> on_done) {
-          std::shared_ptr<Sensor> sensor_locked = sensor.lock();
-          if (!sensor_locked) {
-            return;
-          }
+    thread_manager.sensor_key_to_task_id[sensor->GetKey()] =
+        thread_manager.task_scheduler->RunAndScheduleAsync(
+            [sensor = std::weak_ptr<Sensor>(sensor),
+             refresh_notification = params.refresh_notification](
+                absl::AnyInvocable<void()> on_done) {
+              std::shared_ptr<Sensor> sensor_locked = sensor.lock();
+              if (!sensor_locked) {
+                return;
+              }
 
-          sensor_locked->RefreshOnceAsync(
-              [refresh_notification = refresh_notification,
-               on_done =
-                   std::move(on_done)](const std::shared_ptr<const SensorValue>&
-                                           sensor_data) mutable {
-                if (refresh_notification != nullptr) {
-                  refresh_notification->NotifyWithData(sensor_data);
-                }
-                on_done();
-              });
-        },
-        interval);
+              sensor_locked->RefreshOnceAsync(
+                  [refresh_notification = refresh_notification,
+                   on_done = std::move(on_done)](
+                      const std::shared_ptr<const SensorValue>&
+                          sensor_data) mutable {
+                    if (refresh_notification != nullptr) {
+                      refresh_notification->NotifyWithData(sensor_data);
+                    }
+                    on_done();
+                  });
+            },
+            interval);
   }
   LOG(INFO) << "Schedule done!";
   return absl::OkStatus();
diff --git a/tlbmc/configs/entity_config_json_impl.cc b/tlbmc/configs/entity_config_json_impl.cc
index dbdee5f..b33e762 100644
--- a/tlbmc/configs/entity_config_json_impl.cc
+++ b/tlbmc/configs/entity_config_json_impl.cc
@@ -600,6 +600,83 @@
   }
 }
 
+void SetDefaultRelatedItem(EntityCommonConfig& entity_common_config) {
+    // By default, if no related item is specified, the sensor related item
+    // will be the parent board of the config it is defined in.
+    RelatedItem default_related_board;
+    default_related_board.set_id(entity_common_config.board_config_name());
+    default_related_board.set_type(RESOURCE_TYPE_BOARD);
+    *entity_common_config.mutable_related_item() = default_related_board;
+}
+
+// For each type of sensor config within our EntityConfig, we need to check if
+// the sensor is associated with an assembly. To do so, we perform a lookup in
+// the TopologyConfig and FruTable to check the resource type of the assigned
+// board_config_name, if an Assembly, we propagate the sensor config to the
+// parent.
+// Assigned board_config_name are guaranteed to be valid in the TopologyConfig,
+// and fru_key in the topology config will be valid in the FruTable since these
+// are where these values are set from.
+void PropagateSensorsFromAssemblyToBoard(
+    EntityConfigJsonImplImmutableData& data,
+    const TopologyConfig& topology_config, const FruTable& fru_table) {
+  for (auto& config : data.hwmon_temp_sensor_configs) {
+    TopologyConfigNode node = topology_config.topology_config_nodes().at(
+        config.entity_common_config().board_config_name());
+    const Fru& fru = fru_table.key_to_fru().at(node.fru_info().fru_key());
+    if (fru.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) {
+      *config.mutable_entity_common_config()->mutable_board_config_name() =
+          node.parent_resource_id();
+    }
+    if (!config.entity_common_config().has_related_item()) {
+      SetDefaultRelatedItem(*config.mutable_entity_common_config());
+    }
+  }
+
+  for (auto& config : data.psu_sensor_configs) {
+    TopologyConfigNode node = topology_config.topology_config_nodes().at(
+        config.entity_common_config().board_config_name());
+    const Fru& fru = fru_table.key_to_fru().at(node.fru_info().fru_key());
+    if (fru.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) {
+      *config.mutable_entity_common_config()->mutable_board_config_name() =
+          node.parent_resource_id();
+    }
+    if (!config.entity_common_config().has_related_item()) {
+      SetDefaultRelatedItem(*config.mutable_entity_common_config());
+    }
+  }
+
+  for (auto& config : data.fan_pwm_configs) {
+    TopologyConfigNode node = topology_config.topology_config_nodes().at(
+        config.entity_common_config().board_config_name());
+    const Fru& fru = fru_table.key_to_fru().at(node.fru_info().fru_key());
+    if (fru.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) {
+      *config.mutable_entity_common_config()->mutable_board_config_name() =
+          node.parent_resource_id();
+    }
+  }
+
+  for (auto& config : data.fan_tach_configs) {
+    TopologyConfigNode node = topology_config.topology_config_nodes().at(
+        config.entity_common_config().board_config_name());
+    const Fru& fru = fru_table.key_to_fru().at(node.fru_info().fru_key());
+    if (fru.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) {
+      *config.mutable_entity_common_config()->mutable_board_config_name() =
+          node.parent_resource_id();
+    }
+  }
+
+  for (auto& config : data.shared_mem_sensor_configs) {
+    TopologyConfigNode node = topology_config.topology_config_nodes().at(
+        config.entity_common_config().board_config_name());
+    const Fru& fru = fru_table.key_to_fru().at(node.fru_info().fru_key());
+    if (fru.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) {
+      *config.mutable_entity_common_config()->mutable_board_config_name() =
+          node.parent_resource_id();
+    }
+  }
+}
+
 }  // namespace
 
 template <class TypeName>
@@ -738,7 +815,7 @@
 
 absl::Status EntityConfigJsonImpl::ParseAndPopulateConfig(
     EntityConfigJsonImplImmutableData& data, const nlohmann::json& element,
-    absl::string_view config_name_with_index) {
+    absl::string_view config_name_with_index, bool is_subfru) {
   const bool* tlbmc_owned = GetValueAsBool(element, "TlbmcOwned");
   if (tlbmc_owned == nullptr || !*tlbmc_owned) {
     return absl::OkStatus();
@@ -760,16 +837,6 @@
     }
     *hwmon_temp_sensor_config->mutable_entity_common_config()
          ->mutable_board_config_name() = config_name_with_index;
-    if (!hwmon_temp_sensor_config->entity_common_config().has_related_item()) {
-      // By default, if no related item is specified, the sensor related item
-      // will be its chassis
-      RelatedItem default_related_chassis;
-      default_related_chassis.set_id(
-          hwmon_temp_sensor_config->entity_common_config().board_config_name());
-      default_related_chassis.set_type(RESOURCE_TYPE_BOARD);
-      *hwmon_temp_sensor_config->mutable_entity_common_config()
-           ->mutable_related_item() = default_related_chassis;
-    }
     data.hwmon_temp_sensor_configs.push_back(*hwmon_temp_sensor_config);
   }
 
@@ -784,16 +851,6 @@
     }
     *psu_sensor_config->mutable_entity_common_config()
          ->mutable_board_config_name() = config_name_with_index;
-    if (!psu_sensor_config->entity_common_config().has_related_item()) {
-      // By default, if no related item is specified, the sensor related item
-      // will be its chassis
-      RelatedItem default_related_chassis;
-      default_related_chassis.set_id(
-          psu_sensor_config->entity_common_config().board_config_name());
-      default_related_chassis.set_type(RESOURCE_TYPE_BOARD);
-      *psu_sensor_config->mutable_entity_common_config()
-           ->mutable_related_item() = default_related_chassis;
-    }
     data.psu_sensor_configs.push_back(*psu_sensor_config);
   }
 
@@ -959,7 +1016,8 @@
 
       // In the case of a sub FRU, we create an entry in the FruTable keyed by
       // the config name to have separate Fru data from the parent Fru.
-      if (*resource_type == RESOURCE_TYPE_ASSEMBLY) {
+      bool is_subfru = *resource_type == RESOURCE_TYPE_ASSEMBLY;
+      if (is_subfru) {
         fru_key = config_name_with_index;
         Fru sub_fru_info;
         Attributes* attributes = sub_fru_info.mutable_attributes();
@@ -1087,7 +1145,7 @@
 
         if (reload_type == ReloadType::kReloadAll) {
           absl::Status config_parse_status = ParseAndPopulateConfig(
-              immutable_data, element, config_name_with_index);
+              immutable_data, element, config_name_with_index, is_subfru);
           if (!config_parse_status.ok()) {
             mutable_data.parsed_status = config_parse_status;
             return EntityConfigJsonImplData{
@@ -1142,7 +1200,7 @@
 
       mutable_data.topology_config.mutable_fru_configs()->insert(
           {fru_key, config_name_with_index});
-      if (*resource_type == RESOURCE_TYPE_ASSEMBLY) {
+      if (is_subfru) {
         topology_config_node.mutable_fru_info()->set_is_sub_fru(true);
         fru_key_to_sub_fru_config[config_data.fru_keys[i].ToString()].insert(
             config_name_with_index);
@@ -1228,6 +1286,11 @@
   // Sort the children of each Fru
   SortChildrenResourceIds(mutable_data.topology_config);
 
+  // Propagate sensors defined in Assembly to their parent boards
+  PropagateSensorsFromAssemblyToBoard(immutable_data,
+                                      mutable_data.topology_config,
+                                      mutable_data.fru_table);
+
   return EntityConfigJsonImplData{
       .immutable_data = std::move(immutable_data),
       .mutable_data = std::move(mutable_data),
diff --git a/tlbmc/configs/entity_config_json_impl.h b/tlbmc/configs/entity_config_json_impl.h
index 6186652..e78bb56 100644
--- a/tlbmc/configs/entity_config_json_impl.h
+++ b/tlbmc/configs/entity_config_json_impl.h
@@ -215,7 +215,7 @@
 
   static absl::Status ParseAndPopulateConfig(
       EntityConfigJsonImplImmutableData& data, const nlohmann::json& element,
-      absl::string_view config_name_with_index);
+      absl::string_view config_name_with_index, bool is_subfru);
 
   // Returns the type of the hardware monitor temperature sensor. If the type is
   // not supported, returns HwmonTempSensorType::kUnknown.