Repository: aristanetworks/avd
Pull request #5123: Refactor(eos_designs): Refactor eos_designs structured_config code for flows init
Author: login: MaheshGSLAB
Labels: name: state: CI Updated
description: CI scenario have been updated in the PR
name: rn: Refactor(eos_designs)
Created: 2025-03-03T14:55:20Z | Merged: 2025-03-12T09:21:03Z


## Change Summary

Refactor eos_designs structured_config code for flow init

## Related Issue(s)

Fixes #<ISSUE ID>

## Component(s) name

`arista.avd.eos_designs`

## Proposed changes
Refactor eos_designs structured_config code for flow init

## How to test
Run molecule

## Checklist

### User Checklist

<!-- Add your own checklist using MD syntax and by replacing N/A -->
- N/A

### Repository Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code has been rebased from devel before I start
- [x] I have read the [**CONTRIBUTING**](https://avd.arista.com/stable/docs/contribution/overview.html) document.
- [ ] My change requires a change to the documentation and documentation have been updated accordingly.
- [x] I have updated [molecule CI](https://github.com/aristanetworks/avd/tree/devel/ansible_collections/arista/avd/molecule) testing accordingly. (check the box if not applicable)



Diff:
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/flow-tracking-missing-tracker.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/flow-tracking-missing-tracker.yml
new file mode 100644
index 00000000000..264659871ea
--- /dev/null
+++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/flow-tracking-missing-tracker.yml
@@ -0,0 +1,24 @@
+type: l2leaf
+l2leaf:
+  nodes:
+    - name: flow-tracking-missing-tracker
+      id: 1
+      # Could as well be hardware
+      flow_tracker_type: sampled
+
+fabric_flow_tracking:
+  endpoints:
+    enabled: true
+    name: I-DO-NOT-EXIST
+
+servers:
+  - name: test
+    adapters:
+      - switches: [flow-tracking-missing-tracker]
+        switch_ports: [Ethernet1]
+        flow_tracking:
+          enabled: true
+
+expected_error_message: >-
+  The flow tracker 'I-DO-NOT-EXIST' is being used for at least one interface,
+  but is not configured in 'self.inputs.flow_tracking_settings'.
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml
index cc9caef7648..822ff3dff15 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml
@@ -115,6 +115,7 @@ all:
             failure-adapter-ptp-profile-does-not-exist:
             failure-svi-grandparent-profile-does-not-exist:
             failure-svi-parent-profile-does-not-exist:
+            flow-tracking-missing-tracker:
             ipv4-acl-in-missing-on-l3-port-channel-wan-interface:
             ipv4-acl-in-missing-on-wan-interface:
             ipv4-acls:
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/flow-tracking-tests-spine2.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/flow-tracking-tests-spine2.cfg
index c251510ed06..dd54da2add1 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/flow-tracking-tests-spine2.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/flow-tracking-tests-spine2.cfg
@@ -5,6 +5,14 @@ no aaa root
 vlan internal order ascending range 1006 1199
 !
 flow tracking hardware
+   tracker FLOW-TRACKER
+      record export on inactive timeout 70000
+      record export on interval 300000
+      exporter CV-TELEMETRY
+         collector 127.0.0.1
+         local interface Loopback0
+         template interval 3600000
+   !
    tracker FLOW-TRACKER-1
       record export on inactive timeout 50000
       record export on interval 300331
@@ -45,7 +53,7 @@ interface Ethernet2
    no shutdown
    mtu 9214
    no switchport
-   flow tracker hardware FLOW-TRACKER-2
+   flow tracker hardware FLOW-TRACKER
    ip address 10.254.2.2/31
 !
 interface Ethernet4
@@ -53,7 +61,7 @@ interface Ethernet4
    no shutdown
    mtu 9214
    no switchport
-   flow tracker hardware FLOW-TRACKER-2
+   flow tracker hardware FLOW-TRACKER
    ip address 10.254.2.6/31
 !
 interface Ethernet6
@@ -61,7 +69,7 @@ interface Ethernet6
    no shutdown
    mtu 9214
    no switchport
-   flow tracker hardware FLOW-TRACKER-2
+   flow tracker hardware FLOW-TRACKER
    ip address 10.254.2.18/31
 !
 interface Ethernet8
@@ -69,7 +77,7 @@ interface Ethernet8
    no shutdown
    mtu 9214
    no switchport
-   flow tracker hardware FLOW-TRACKER-2
+   flow tracker hardware FLOW-TRACKER
    ip address 10.254.2.22/31
 !
 interface Ethernet9
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/flow-tracking-tests-spine2.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/flow-tracking-tests-spine2.yml
index ddae51f61ce..3899a252493 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/flow-tracking-tests-spine2.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/flow-tracking-tests-spine2.yml
@@ -9,7 +9,7 @@ ethernet_interfaces:
   shutdown: false
   mtu: 9214
   flow_tracker:
-    hardware: FLOW-TRACKER-2
+    hardware: FLOW-TRACKER
   ip_address: 10.254.2.2/31
   peer: flow-tracking-tests-leaf1
   peer_interface: Ethernet2
@@ -21,7 +21,7 @@ ethernet_interfaces:
   shutdown: false
   mtu: 9214
   flow_tracker:
-    hardware: FLOW-TRACKER-2
+    hardware: FLOW-TRACKER
   ip_address: 10.254.2.6/31
   peer: flow-tracking-tests-leaf2
   peer_interface: Ethernet2
@@ -33,7 +33,7 @@ ethernet_interfaces:
   shutdown: false
   mtu: 9214
   flow_tracker:
-    hardware: FLOW-TRACKER-2
+    hardware: FLOW-TRACKER
   ip_address: 10.254.2.18/31
   peer: flow-tracking-tests-leaf3
   peer_interface: Ethernet2
@@ -45,7 +45,7 @@ ethernet_interfaces:
   shutdown: false
   mtu: 9214
   flow_tracker:
-    hardware: FLOW-TRACKER-2
+    hardware: FLOW-TRACKER
   ip_address: 10.254.2.22/31
   peer: flow-tracking-tests-leaf4
   peer_interface: Ethernet2
@@ -137,6 +137,16 @@ flow_tracking:
     record:
       format_ipfix_standard_timestamps_counters: true
     trackers:
+    - name: FLOW-TRACKER
+      record_export:
+        on_inactive_timeout: 70000
+        on_interval: 300000
+      exporters:
+      - name: CV-TELEMETRY
+        collector:
+          host: 127.0.0.1
+        local_interface: Loopback0
+        template_interval: 3600000
     - name: FLOW-TRACKER-1
       record_export:
         on_inactive_timeout: 50000
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
index 5977a293451..43eb7e9cdae 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
@@ -20,41 +20,35 @@ flow_tracking_settings:
     record:
       format_ipfix_standard_timestamps_counters: true
   trackers:
-  - name: FLOW-TRACKER-2
-    record_export:
-      on_inactive_timeout: 50020
-      on_interval: 300321
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.2.1
-      local_interface: Loopback0
-      template_interval: 40020
-  - name: FLOW-TRACKER-1
-    record_export:
-      on_inactive_timeout: 50000
-      on_interval: 300331
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.0.1
-      local_interface: Loopback0
-      template_interval: 40000
+    - name: FLOW-TRACKER-2
+      record_export:
+        on_inactive_timeout: 50020
+        on_interval: 300321
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.2.1
+          local_interface: Loopback0
+          template_interval: 40020
+    - name: FLOW-TRACKER-1
+      record_export:
+        on_inactive_timeout: 50000
+        on_interval: 300331
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.0.1
+          local_interface: Loopback0
+          template_interval: 40000
 
 # False by default
 fabric_flow_tracking:
-  uplinks:
-    enabled: true
   downlinks:
     enabled: true
-    name: FLOW-TRACKER-2
-  endpoints:
-    enabled: true
+    # using default
   l3_edge:
     enabled: false
     name: FLOW-TRACKER-2
   core_interfaces:
     enabled: false
     name: FLOW-TRACKER-2
-  mlag_interfaces:
-    enabled: true
diff --git a/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
index 6b845ca26c9..7e96219b744 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
@@ -6,10 +6,10 @@
 from functools import cached_property
 from itertools import chain
 
+from pyavd._eos_cli_config_gen.schema import EosCliConfigGen
 from pyavd._eos_designs.schema import EosDesigns
-from pyavd._eos_designs.structured_config.structured_config_generator import StructuredConfigGenerator
+from pyavd._eos_designs.structured_config.structured_config_generator import StructuredConfigGenerator, structured_config_contributor
 from pyavd._errors import AristaAvdInvalidInputsError
-from pyavd._utils import strip_null_from_data
 from pyavd.j2filters import natural_sort
 
 
@@ -25,8 +25,8 @@ class AvdStructuredConfigFlows(StructuredConfigGenerator):
 
     ignore_avd_eos_designs_enforce_duplication_checks_across_all_models = True
 
-    @cached_property
-    def sflow(self) -> dict | None:
+    @structured_config_contributor
+    def sflow(self) -> None:
         """
         Structured config for sFlow.
 
@@ -37,7 +37,7 @@ def sflow(self) -> dict | None:
         - source-interfaces based on source_interfaces.sflow
         """
         if not self._enable_sflow:
-            return None
+            return
 
         if not (destinations := self.inputs.sflow_settings.destinations):
             msg = "`sflow_settings.destinations` is required to configure `sflow`."
@@ -47,73 +47,58 @@ def sflow(self) -> dict | None:
 
         # At this point we have at least one interface with sFlow enabled
         # and at least one destination.
-        sflow = {"run": True, "polling_interval": self.inputs.sflow_settings.polling_interval, "sample": self.inputs.sflow_settings.sample.rate}
-
-        # Using a temporary dict for VRFs
-        sflow_vrfs = {}
+        self.structured_config.sflow._update(
+            run=True, polling_interval=self.inputs.sflow_settings.polling_interval, sample=self.inputs.sflow_settings.sample.rate
+        )
 
         for destination in natural_sort(destinations, "destination"):
             destination: EosDesigns.SflowSettings.DestinationsItem
-            vrf = destination.vrf
-            if vrf is None:
-                vrf = self.shared_utils.default_mgmt_protocol_vrf
+            vrf_name = destination.vrf
+            if vrf_name is None:
+                vrf_name = self.shared_utils.default_mgmt_protocol_vrf
                 source_interface = self.shared_utils.default_mgmt_protocol_interface
 
-            elif vrf == "use_mgmt_interface_vrf":
+            elif vrf_name == "use_mgmt_interface_vrf":
                 if (self.shared_utils.node_config.mgmt_ip is None) and (self.shared_utils.node_config.ipv6_mgmt_ip is None):
                     msg = "Unable to configure sFlow source-interface with 'use_mgmt_interface_vrf' since 'mgmt_ip' or 'ipv6_mgmt_ip' are not set."
                     raise AristaAvdInvalidInputsError(msg)
 
-                vrf = self.inputs.mgmt_interface_vrf
-                if vrf in sflow_settings_vrfs and sflow_settings_vrfs[vrf].source_interface:
-                    source_interface = sflow_settings_vrfs[vrf].source_interface
+                vrf_name = self.inputs.mgmt_interface_vrf
+                if vrf_name in sflow_settings_vrfs and sflow_settings_vrfs[vrf_name].source_interface:
+                    source_interface = sflow_settings_vrfs[vrf_name].source_interface
                 else:
                     source_interface = self.shared_utils.mgmt_interface
 
-            elif vrf == "use_inband_mgmt_vrf":
+            elif vrf_name == "use_inband_mgmt_vrf":
                 # Check for missing interface
                 if self.shared_utils.inband_mgmt_interface is None:
                     msg = "Unable to configure sFlow source-interface with 'use_inband_mgmt_vrf' since 'inband_mgmt_interface' is not set."
                     raise AristaAvdInvalidInputsError(msg)
 
                 # self.shared_utils.inband_mgmt_vrf returns None for the default VRF, but here we need "default" to avoid duplicates.
-                vrf = self.shared_utils.inband_mgmt_vrf or "default"
-                if vrf in sflow_settings_vrfs and sflow_settings_vrfs[vrf].source_interface:
-                    source_interface = sflow_settings_vrfs[vrf].source_interface
+                vrf_name = self.shared_utils.inband_mgmt_vrf or "default"
+                if vrf_name in sflow_settings_vrfs and sflow_settings_vrfs[vrf_name].source_interface:
+                    source_interface = sflow_settings_vrfs[vrf_name].source_interface
                 else:
                     source_interface = self.shared_utils.inband_mgmt_interface
 
             # Default is none, meaning we will not configure a source interface for this VRF.
-            elif vrf in sflow_settings_vrfs and sflow_settings_vrfs[vrf].source_interface:
-                source_interface = sflow_settings_vrfs[vrf].source_interface
+            elif vrf_name in sflow_settings_vrfs and sflow_settings_vrfs[vrf_name].source_interface:
+                source_interface = sflow_settings_vrfs[vrf_name].source_interface
             else:
                 source_interface = None
 
-            if vrf in [None, "default"]:
+            if vrf_name in [None, "default"]:
                 # Add destination without VRF field
-                sflow.setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow["source_interface"] = source_interface
+                self.structured_config.sflow.destinations.append_new(destination=destination.destination, port=destination.port)
+                self.structured_config.sflow.source_interface = source_interface
 
             else:
                 # Add destination with VRF field.
-                sflow_vrfs.setdefault(vrf, {}).setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow_vrfs[vrf]["source_interface"] = source_interface
-
-        # convert sflow_vrfs dict into list and insert into sflow
-        if sflow_vrfs:
-            sflow["vrfs"] = [{"name": vrf_name, **vrf} for vrf_name, vrf in sflow_vrfs.items()]
-
-        return strip_null_from_data(sflow)
+                vrf_item = self.structured_config.sflow.vrfs.obtain(vrf_name)
+                vrf_item.destinations.append_new(destination=destination.destination, port=destination.port)
+                vrf_item.source_interface = source_interface
+                self.structured_config.sflow.vrfs.append(vrf_item)
 
     @cached_property
     def _enable_sflow(self) -> bool:
@@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
+            record_export.mpls = config.sampled.record_export.mpls
 
-        return flow_tracking
+            self.structured_config.flow_tracking.sampled.trackers.append_new(
+                name=config.name,
+                record_export=record_export,
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=config.sampled.table_size,
+            )
 
-    def _get_enabled_flow_trackers(self) -> set[str]:
+    def _get_tracker_input_config(self, tracker_name: str) -> EosDesigns.FlowTrackingSettings.TrackersItem:
         """
-        Enable flow-tracking if any interface is enabled for flow-tracking.
+        Retrieves inputs for the given tracker_name.
 
-        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        If the name matches the default tracker_name, return the default tracker.
+
+        We allow overriding the default flow tracker name, so if user has configured a tracker
+        with the default tracker name, then we just use that, if not, we create a default config
         """
-        all_interfaces = chain(
-            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
-        )
-        if self.shared_utils.flow_tracking_type == "hardware":
-            return {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if tracker_name in self.inputs.flow_tracking_settings.trackers:
+            return self.inputs.flow_tracking_settings.trackers[tracker_name]
+
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        if tracker_name == default_tracker.name:
+            return default_tracker
 
-        return {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        msg = f"The flow tracker '{tracker_name}' is being used for at least one interface, but is not configured in 'self.inputs.flow_tracking_settings'."
+        raise AristaAvdInvalidInputsError(msg)


Reviews:
user: login: Vibhu-gslab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:32:43Z
user: login: Vibhu-gslab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:59:58Z
user: login: Vibhu-gslab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T10:04:04Z
user: login: laxmikantchintakindi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:08:41Z
user: login: laxmikantchintakindi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:08:51Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T05:52:24Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T05:54:12Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T05:54:42Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T05:56:10Z
user: login: laxmikantchintakindi
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:10:05Z
user: login: Vibhu-gslab
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T09:06:30Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T09:36:35Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:50:59Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:51:03Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:51:06Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T07:51:12Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T08:16:10Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T07:25:23Z
user: login: ClausHolbechArista
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:27:41Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:30:17Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:38:18Z
user: login: ClausHolbechArista
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T07:02:35Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T07:54:16Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:00:29Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:41:35Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:41:59Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:42:20Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T09:08:54Z
user: login: ClausHolbechArista
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T09:20:50Z

Review comments:
diff_hunk: @@ -91,29 +91,16 @@ def sflow(self) -> dict | None:
 
             if vrf in [None, "default"]:
                 # Add destination without VRF field
-                sflow.setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow["source_interface"] = source_interface
+                self.structured_config.sflow.destinations.append_new(destination=destination.destination, port=destination.port)
+                self.structured_config.sflow.source_interface = source_interface
 
             else:
                 # Add destination with VRF field.
-                sflow_vrfs.setdefault(vrf, {}).setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow_vrfs[vrf]["source_interface"] = source_interface
-
-        # convert sflow_vrfs dict into list and insert into sflow
-        if sflow_vrfs:
-            sflow["vrfs"] = [{"name": vrf_name, **vrf} for vrf_name, vrf in sflow_vrfs.items()]
+                vrf_item = sflow_vrfs.obtain(vrf)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: Vibhu-gslab
body: I think it would be good if we specify `.obtain(name=vrf)` here, to make it easy to read
created_at: 2025-03-05T06:06:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,29 +91,16 @@ def sflow(self) -> dict | None:
 
             if vrf in [None, "default"]:
                 # Add destination without VRF field
-                sflow.setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow["source_interface"] = source_interface
+                self.structured_config.sflow.destinations.append_new(destination=destination.destination, port=destination.port)
+                self.structured_config.sflow.source_interface = source_interface
 
             else:
                 # Add destination with VRF field.
-                sflow_vrfs.setdefault(vrf, {}).setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow_vrfs[vrf]["source_interface"] = source_interface
-
-        # convert sflow_vrfs dict into list and insert into sflow
-        if sflow_vrfs:
-            sflow["vrfs"] = [{"name": vrf_name, **vrf} for vrf_name, vrf in sflow_vrfs.items()]
+                vrf_item = sflow_vrfs.obtain(vrf)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: Vibhu-gslab
body: Or we can rename `vrf` to `vrf_name`
created_at: 2025-03-05T06:59:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: Vibhu-gslab
body: since we are using it only once may be we can check the condition directly.
created_at: 2025-03-05T10:00:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
         tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
+
+        if tracker_type == "hardware":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: Vibhu-gslab
body: Same for this one as well.
created_at: 2025-03-05T10:03:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
         tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
+
+        if tracker_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled.sample = flow_tracking_settings.sampled.sample
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: laxmikantchintakindi
body: ```suggestion
           structured_config.sampled._update(sample = flow_tracking_settings.sampled.sample, shutdown=False)
```
created_at: 2025-03-05T12:08:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
         tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
+
+        if tracker_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled.sample = flow_tracking_settings.sampled.sample
+            structured_config.sampled.encapsulation._update(
+                ipv4_ipv6=flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=flow_tracking_settings.sampled.encapsulation.mpls,
+            )
+            structured_config.sampled.hardware_offload._update(
+                ipv4=flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            )
+            structured_config.sampled.shutdown = False
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: laxmikantchintakindi
body: ```suggestion
```
created_at: 2025-03-05T12:08:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,29 +91,16 @@ def sflow(self) -> dict | None:
 
             if vrf in [None, "default"]:
                 # Add destination without VRF field
-                sflow.setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow["source_interface"] = source_interface
+                self.structured_config.sflow.destinations.append_new(destination=destination.destination, port=destination.port)
+                self.structured_config.sflow.source_interface = source_interface
 
             else:
                 # Add destination with VRF field.
-                sflow_vrfs.setdefault(vrf, {}).setdefault("destinations", []).append(
-                    {
-                        "destination": destination.destination,
-                        "port": destination.port,
-                    },
-                )
-                sflow_vrfs[vrf]["source_interface"] = source_interface
-
-        # convert sflow_vrfs dict into list and insert into sflow
-        if sflow_vrfs:
-            sflow["vrfs"] = [{"name": vrf_name, **vrf} for vrf_name, vrf in sflow_vrfs.items()]
+                vrf_item = sflow_vrfs.obtain(vrf)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: changed to vrf_name
created_at: 2025-03-06T05:52:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: updated
created_at: 2025-03-06T05:54:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
         tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
+
+        if tracker_type == "hardware":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: updated
created_at: 2025-03-06T05:54:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,60 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
+        tracker_type = self.shared_utils.flow_tracking_type
+        if tracker_type == "sampled":
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
         tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
+
+        if tracker_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled.sample = flow_tracking_settings.sampled.sample
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: updated
created_at: 2025-03-06T05:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,73 +47,60 @@ def sflow(self) -> dict | None:
 
         # At this point we have at least one interface with sFlow enabled
         # and at least one destination.
-        sflow = {"run": True, "polling_interval": self.inputs.sflow_settings.polling_interval, "sample": self.inputs.sflow_settings.sample.rate}
-
-        # Using a temporary dict for VRFs
-        sflow_vrfs = {}
+        self.structured_config.sflow._update(
+            run=True, polling_interval=self.inputs.sflow_settings.polling_interval, sample=self.inputs.sflow_settings.sample.rate
+        )
+        sflow_vrfs = EosCliConfigGen.Sflow.Vrfs()
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: @MaheshGSLAB looking at the code I am wondering if you even need the intermediate `sflow_vrfs` - the only thing you do is doing obtain in it to add some destinations which can be done on the `self.structured_config.sflow.vrfs` directly I believe
created_at: 2025-03-06T09:18:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled._update(sample=flow_tracking_settings.sampled.sample, shutdown=False)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: any reason to not use _case_as for the sampled case?
created_at: 2025-03-06T09:27:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled._update(sample=flow_tracking_settings.sampled.sample, shutdown=False)
+            structured_config.sampled.encapsulation._update(
+                ipv4_ipv6=flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=flow_tracking_settings.sampled.encapsulation.mpls,
+            )
+            structured_config.sampled.hardware_offload._update(
+                ipv4=flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            )
+            tracker_list = structured_config.sampled.trackers
+
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
         for tracker_name in natural_sort(configured_trackers):
             """
             We allow overriding the default flow tracker name, so if user has configured a tracker
             with the default tracker name, then we just use that, if not, we create a default config
             """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
             if tracker_name not in self.inputs.flow_tracking_settings.trackers:
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: here we are using a mix of `self.inputs.flow_tracking_settigns` and your shorter version of `flow_tracking_settings`. Wondering if we should not align this.
created_at: 2025-03-06T09:31:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: looking at this if /else here and in the tracking resolution, it almost feel like it would be easier to follow if we had one sub-function that does all the hardware and one that does all the sampled config and we just call the appropriate ones. The only inconvenient would be the repeat of the error message if the tracker name is part of the configured one but I feel like we can make this check early on and then carry on.

Thoughts? 
created_at: 2025-03-06T09:36:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: spilt the methods, let me know if this looks fine.
created_at: 2025-03-07T07:50:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled._update(sample=flow_tracking_settings.sampled.sample, shutdown=False)
+            structured_config.sampled.encapsulation._update(
+                ipv4_ipv6=flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=flow_tracking_settings.sampled.encapsulation.mpls,
+            )
+            structured_config.sampled.hardware_offload._update(
+                ipv4=flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            )
+            tracker_list = structured_config.sampled.trackers
+
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
         for tracker_name in natural_sort(configured_trackers):
             """
             We allow overriding the default flow tracker name, so if user has configured a tracker
             with the default tracker name, then we just use that, if not, we create a default config
             """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
             if tracker_name not in self.inputs.flow_tracking_settings.trackers:
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: updated
created_at: 2025-03-07T07:51:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled._update(sample=flow_tracking_settings.sampled.sample, shutdown=False)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: Yes there is some default values and we decided to not use _cast_as for such cases.
```
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: Unable to cast '<class 'pyavd._eos_designs.schema.EosDesigns.FlowTrackingSettings.Sampled'>' as type '<class 'pyavd._eos_cli_config_gen.schema.EosCliConfigGen.FlowTracking.Sampled'>' since the field 'sample' is incompatible. Value 50000
```
created_at: 2025-03-07T07:51:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,73 +47,60 @@ def sflow(self) -> dict | None:
 
         # At this point we have at least one interface with sFlow enabled
         # and at least one destination.
-        sflow = {"run": True, "polling_interval": self.inputs.sflow_settings.polling_interval, "sample": self.inputs.sflow_settings.sample.rate}
-
-        # Using a temporary dict for VRFs
-        sflow_vrfs = {}
+        self.structured_config.sflow._update(
+            run=True, polling_interval=self.inputs.sflow_settings.polling_interval, sample=self.inputs.sflow_settings.sample.rate
+        )
+        sflow_vrfs = EosCliConfigGen.Sflow.Vrfs()
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: Yes you are right, updated the code
created_at: 2025-03-07T07:51:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
+            structured_config.hardware = flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+            structured_config.hardware.shutdown = False
+            tracker_list = structured_config.hardware.trackers
+        else:
+            structured_config.sampled._update(sample=flow_tracking_settings.sampled.sample, shutdown=False)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: ah yeah .. the comparison in cast_as. thanks!
created_at: 2025-03-07T08:16:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,44 +111,57 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
+    def resolve_flow_tracker_by_type(
+        self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem
+    ) -> EosCliConfigGen.FlowTracking.Hardware.TrackersItem | EosCliConfigGen.FlowTracking.Sampled.TrackersItem:
         if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
-
-        return tracker
+            tracker = EosCliConfigGen.FlowTracking.Sampled.TrackersItem(
+                name=tracker_settings.name,
+                record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport),
+                exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=tracker_settings.sampled.table_size,
+            )
+            tracker.record_export.mpls = tracker_settings.sampled.record_export.mpls
+            return tracker
+        return EosCliConfigGen.FlowTracking.Hardware.TrackersItem(
+            name=tracker_settings.name,
+            record_export=tracker_settings.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+            exporters=tracker_settings.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+        )
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking."""
         configured_trackers = self._get_enabled_flow_trackers()
         if not configured_trackers:
-            return None
-
-        flow_tracking = {}
+            return
 
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
+        structured_config = self.structured_config.flow_tracking
+        flow_tracking_settings = self.inputs.flow_tracking_settings
 
-        filtered_trackers = []
+        if self.shared_utils.flow_tracking_type == "hardware":
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: MaheshGSLAB
body: @gmuloc Revert the changes as discussed
created_at: 2025-03-11T07:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: Can we try to change the schema to use a $ref when we also use cast_as? Otherwise a drift in schemas might lead to bad issues.
created_at: 2025-03-11T14:17:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: Actually it seems like we only support `hardware.record.format_ipfix_standard_timestamps_counters` so that should be faster to just set directly.
created_at: 2025-03-11T14:19:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
 
-        return flow_tracking
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
 
-    def _get_enabled_flow_trackers(self) -> set[str]:
-        """
-        Enable flow-tracking if any interface is enabled for flow-tracking.
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
+            record_export.mpls = config.sampled.record_export.mpls
 
-        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+            self.structured_config.flow_tracking.sampled.trackers.append_new(
+                name=config.name,
+                record_export=record_export,
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=config.sampled.table_size,
+            )
+
+    def _get_tracker_input_config(
+        self, default_tracker: EosDesigns.FlowTrackingSettings.TrackersItem, tracker_name: str
+    ) -> EosDesigns.FlowTrackingSettings.TrackersItem:
+        """Retrieves inputs for the given tracker_name.
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: ```suggestion
        """
        Retrieves inputs for the given tracker_name.
```
created_at: 2025-03-11T14:20:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: ```suggestion
        """
        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
```
created_at: 2025-03-11T14:20:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
 
-        return flow_tracking
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
 
-    def _get_enabled_flow_trackers(self) -> set[str]:
-        """
-        Enable flow-tracking if any interface is enabled for flow-tracking.
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
+            record_export.mpls = config.sampled.record_export.mpls
 
-        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+            self.structured_config.flow_tracking.sampled.trackers.append_new(
+                name=config.name,
+                record_export=record_export,
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
+                table_size=config.sampled.table_size,
+            )
+
+    def _get_tracker_input_config(
+        self, default_tracker: EosDesigns.FlowTrackingSettings.TrackersItem, tracker_name: str
+    ) -> EosDesigns.FlowTrackingSettings.TrackersItem:
+        """Retrieves inputs for the given tracker_name.
+
+        If the name matches the default tracker_name, return the default tracker.
+
+        We allow overriding the default flow tracker name, so if user has configured a tracker
+        with the default tracker name, then we just use that, if not, we create a default config
         """
-        all_interfaces = chain(
-            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
-        )
-        if self.shared_utils.flow_tracking_type == "hardware":
-            return {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if (tracker_settings := self.inputs.flow_tracking_settings.trackers.get(tracker_name)) != Undefined:
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: IMO a `if tracker_name in self.inputs.flow_tracking_settings.trackers` is simpler. and then return the item with `[tracker_name]`. Two cheap operations and simpler to read.
created_at: 2025-03-11T14:22:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
 
-        return flow_tracking
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: I think it would make sense to do this in the helper function after seing the tracker was not there
created_at: 2025-03-11T14:24:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
 
-        return flow_tracking
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: it means potentially many next/iter
created_at: 2025-03-11T14:30:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +110,95 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.hardware = self.inputs.flow_tracking_settings.hardware._cast_as(EosCliConfigGen.FlowTracking.Hardware)
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(default_tracker, tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
 
-        return flow_tracking
+        # Validate and configure trackers
+        default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: changed
created_at: 2025-03-11T14:38:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+type: l2leaf
+l2leaf:
+  nodes:
+    - name: flow-tracking-missing-tracker
+      id: 1
+      # Could as well be hardware
+      flow_tracker_type: sampled
+
+fabric_flow_tracking:
+  endpoints:
+    enabled: true
+    name: I-DO-NOT-EXIST
+
+servers:
+  - name: test
+    adapters:
+      - switches: [flow-tracking-missing-tracker]
+        switch_ports: [Ethernet1]
+        flow_tracking:
+          enabled: true
+
+expected_error_message: >-
+  'I-DO-NOT-EXIST' is being used for one of the interfaces, but is not configured in 'self.inputs.flow_tracking_settings'.
path: ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/flow-tracking-missing-tracker.yml
user: login: ClausHolbechArista
body: ```suggestion
  The flow tracker 'I-DO-NOT-EXIST' is being used for at least one interface, but is not configured in 'self.inputs.flow_tracking_settings'.
```
created_at: 2025-03-12T06:54:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,35 @@ flow_tracking_settings:
     record:
       format_ipfix_standard_timestamps_counters: true
   trackers:
-  - name: FLOW-TRACKER-2
-    record_export:
-      on_inactive_timeout: 50020
-      on_interval: 300321
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.2.1
-      local_interface: Loopback0
-      template_interval: 40020
-  - name: FLOW-TRACKER-1
-    record_export:
-      on_inactive_timeout: 50000
-      on_interval: 300331
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.0.1
-      local_interface: Loopback0
-      template_interval: 40000
+    - name: FLOW-TRACKER-2
+      record_export:
+        on_inactive_timeout: 50020
+        on_interval: 300321
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.2.1
+          local_interface: Loopback0
+          template_interval: 40020
+    - name: FLOW-TRACKER-1
+      record_export:
+        on_inactive_timeout: 50000
+        on_interval: 300331
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.0.1
+          local_interface: Loopback0
+          template_interval: 40000
 
 # False by default
 fabric_flow_tracking:
path: ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
user: login: ClausHolbechArista
body: why all these changes to tests? It makes it much harder to verify the refactor is correct.
created_at: 2025-03-12T06:55:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: This is one boolean, so let us avoid cast_as
created_at: 2025-03-12T06:59:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
+            record_export.mpls = config.sampled.record_export.mpls
 
-        return flow_tracking
+            self.structured_config.flow_tracking.sampled.trackers.append_new(
+                name=config.name,
+                record_export=record_export,
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: Could we update the exporters schema to use a pure $ref instead of repeating it from eos_cli_config_gen? Effectively it should be the same, and then the classes would be cli gen classes already.
created_at: 2025-03-12T07:01:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: ClausHolbechArista
body: Please check if we can modify the schemas to be $ref to cli gen so we don't need this
created_at: 2025-03-12T07:02:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,35 @@ flow_tracking_settings:
     record:
       format_ipfix_standard_timestamps_counters: true
   trackers:
-  - name: FLOW-TRACKER-2
-    record_export:
-      on_inactive_timeout: 50020
-      on_interval: 300321
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.2.1
-      local_interface: Loopback0
-      template_interval: 40020
-  - name: FLOW-TRACKER-1
-    record_export:
-      on_inactive_timeout: 50000
-      on_interval: 300331
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.0.1
-      local_interface: Loopback0
-      template_interval: 40000
+    - name: FLOW-TRACKER-2
+      record_export:
+        on_inactive_timeout: 50020
+        on_interval: 300321
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.2.1
+          local_interface: Loopback0
+          template_interval: 40020
+    - name: FLOW-TRACKER-1
+      record_export:
+        on_inactive_timeout: 50000
+        on_interval: 300331
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.0.1
+          local_interface: Loopback0
+          template_interval: 40000
 
 # False by default
 fabric_flow_tracking:
path: ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
user: login: gmuloc
body: we are not testing this today so we don't know if the refactor is correct
created_at: 2025-03-12T07:54:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: this is not only one boolean:


```
flow_tracking:
  sampled:
    sample: 10000
    trackers:
    - record_export:
        on_inactive_timeout: 50001
        on_interval: 300332
      name: FLOW-TRACKER
      exporters:
      - name: ayush_exporter
        collector:
          host: 127.0.0.2
        local_interface: Loopback0
        template_interval: 40002
    - table_size: 4331
      record_export:
        mpls: true. <<<<<<<<<<<<<< this is added in the schema on top of a def
        on_inactive_timeout: 50000
        on_interval: 300331
 ```
created_at: 2025-03-12T08:00:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
+            record_export.mpls = config.sampled.record_export.mpls
 
-        return flow_tracking
+            self.structured_config.flow_tracking.sampled.trackers.append_new(
+                name=config.name,
+                record_export=record_export,
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.Exporters),
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: discussed on call we can't because we would need to ref a defs which we can't today
created_at: 2025-03-12T08:41:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: same as above..
created_at: 2025-03-12T08:41:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,70 +109,96 @@ def _enable_sflow(self) -> bool:
         """
         return any(interface.sflow.enable for interface in chain(self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces))
 
-    def resolve_flow_tracker_by_type(self, tracker_settings: EosDesigns.FlowTrackingSettings.TrackersItem) -> dict:
-        tracker = {
-            "name": tracker_settings.name,
-            "record_export": tracker_settings.record_export._as_dict(),
-            "exporters": tracker_settings.exporters._as_list(),
-        }
-        if self.shared_utils.flow_tracking_type == "sampled":
-            if (table_size := tracker_settings.sampled.table_size) is not None:
-                tracker["table_size"] = table_size
-            if (mpls := tracker_settings.sampled.record_export.mpls) is not None:
-                tracker["record_export"]["mpls"] = mpls
+    @structured_config_contributor
+    def flow_tracking(self) -> None:
+        """
+        Set the structured config for flow_tracking if any interface is enabled for flow-tracking.
 
-        return tracker
+        This relies on flow-tracking being rendered after all other eos_designs modules (except structured config).
+        """
+        if self.shared_utils.flow_tracking_type == "hardware":
+            self._set_hardware_flow_tracking()
+        elif self.shared_utils.flow_tracking_type == "sampled":
+            self._set_sampled_flow_tracking()
 
-    @cached_property
-    def flow_tracking(self) -> dict | None:
-        """Return structured config for flow_tracking."""
-        configured_trackers = self._get_enabled_flow_trackers()
-        if not configured_trackers:
-            return None
-
-        flow_tracking = {}
-
-        tracker_type = self.shared_utils.flow_tracking_type
-        global_settings = self.inputs.flow_tracking_settings.hardware if tracker_type == "hardware" else self.inputs.flow_tracking_settings.sampled
-        flow_tracking[tracker_type] = global_settings._as_dict()
-        if tracker_type == "sampled":
-            # asdict does not contain default values so we need to insert the default sample.
-            # TODO: consider if asdict should include defaults.
-            flow_tracking[tracker_type]["sample"] = global_settings.sample
-
-        filtered_trackers = []
-        for tracker_name in natural_sort(configured_trackers):
-            """
-            We allow overriding the default flow tracker name, so if user has configured a tracker
-            with the default tracker name, then we just use that, if not, we create a default config
-            """
-            default_tracker = next(iter(EosDesigns.FlowTrackingSettings().trackers))
-            if tracker_name not in self.inputs.flow_tracking_settings.trackers:
-                if tracker_name == default_tracker.name:
-                    tracker = default_tracker
-                else:
-                    msg = f"{tracker_name} is being used for one of the interfaces, but is not configured in flow_tracking_settings"
-                    raise AristaAvdInvalidInputsError(msg)
-            else:
-                tracker = self.inputs.flow_tracking_settings.trackers[tracker_name]
+    def _set_hardware_flow_tracking(self) -> None:
+        """Set the structured configuration for hardware flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
 
-            filtered_trackers.append(self.resolve_flow_tracker_by_type(tracker))
+        trackers: set[str] = {interface.flow_tracker.hardware for interface in all_interfaces if interface.flow_tracker.hardware}
+        if not trackers:
+            return
 
-        flow_tracking[tracker_type]["trackers"] = filtered_trackers
-        flow_tracking[tracker_type]["shutdown"] = False
+        self.structured_config.flow_tracking.hardware.record.format_ipfix_standard_timestamps_counters = (
+            self.inputs.flow_tracking_settings.hardware.record.format_ipfix_standard_timestamps_counters
+        )
+        self.structured_config.flow_tracking.hardware.shutdown = False
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+            self.structured_config.flow_tracking.hardware.trackers.append_new(
+                name=config.name,
+                record_export=config.record_export._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.RecordExport),
+                exporters=config.exporters._cast_as(EosCliConfigGen.FlowTracking.Hardware.TrackersItem.Exporters),
+            )
+
+    def _set_sampled_flow_tracking(self) -> None:
+        """Set the structured configuration for sampled flow tracking if any interface is configured."""
+        all_interfaces = chain(
+            self.structured_config.ethernet_interfaces, self.structured_config.port_channel_interfaces, self.structured_config.dps_interfaces
+        )
+
+        trackers: set[str] = {interface.flow_tracker.sampled for interface in all_interfaces if interface.flow_tracker.sampled}
+        if not trackers:
+            return
+
+        self.structured_config.flow_tracking.sampled._update(
+            sample=self.inputs.flow_tracking_settings.sampled.sample,
+            shutdown=False,
+            encapsulation=EosCliConfigGen.FlowTracking.Sampled.Encapsulation(
+                ipv4_ipv6=self.inputs.flow_tracking_settings.sampled.encapsulation.ipv4_ipv6,
+                mpls=self.inputs.flow_tracking_settings.sampled.encapsulation.mpls,
+            ),
+            hardware_offload=EosCliConfigGen.FlowTracking.Sampled.HardwareOffload(
+                ipv4=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv4,
+                ipv6=self.inputs.flow_tracking_settings.sampled.hardware_offload.ipv6,
+                threshold_minimum=self.inputs.flow_tracking_settings.sampled.hardware_offload.threshold_minimum,
+            ),
+        )
+
+        # Validate and configure trackers
+        for tracker_name in natural_sort(trackers):
+            config = self._get_tracker_input_config(tracker_name)
+
+            # Need to handle mpls specifically
+            record_export = config.record_export._cast_as(EosCliConfigGen.FlowTracking.Sampled.TrackersItem.RecordExport)
path: python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py
user: login: gmuloc
body: so discussed we cna't do that
created_at: 2025-03-12T08:42:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,35 @@ flow_tracking_settings:
     record:
       format_ipfix_standard_timestamps_counters: true
   trackers:
-  - name: FLOW-TRACKER-2
-    record_export:
-      on_inactive_timeout: 50020
-      on_interval: 300321
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.2.1
-      local_interface: Loopback0
-      template_interval: 40020
-  - name: FLOW-TRACKER-1
-    record_export:
-      on_inactive_timeout: 50000
-      on_interval: 300331
-    exporters:
-    - name: ayush_exporter
-      collector:
-        host: 127.0.0.1
-      local_interface: Loopback0
-      template_interval: 40000
+    - name: FLOW-TRACKER-2
+      record_export:
+        on_inactive_timeout: 50020
+        on_interval: 300321
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.2.1
+          local_interface: Loopback0
+          template_interval: 40020
+    - name: FLOW-TRACKER-1
+      record_export:
+        on_inactive_timeout: 50000
+        on_interval: 300331
+      exporters:
+        - name: ayush_exporter
+          collector:
+            host: 127.0.0.1
+          local_interface: Loopback0
+          template_interval: 40000
 
 # False by default
 fabric_flow_tracking:
path: ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/FLOW_TRACKING_TESTS_SPINES_2.yml
user: login: gmuloc
body: so as discussed base on Code Coverage report this changes is to be able to use the default tracker without redefining it.
The removal of uplinks / endpoints and mlag is because this group_vars file is only for spine 2 which does not have any uplink, endpoin and mlag so this is just noise that makes understandting what we test harder
created_at: 2025-03-12T09:08:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-03T14:55:31Z
body: Review docs on [Read the Docs](https://ansible-avd--5123.org.readthedocs.build/en/5123/)

To test this pull request:

```sh
# Create virtual environment for this testing below the current directory
python -m venv test-avd-pr-5123
# Activate the virtual environment
source test-avd-pr-5123/bin/activate
# Install all requirements including PyAVD
pip install "pyavd[ansible] @ git+https://github.com/MaheshGSLAB/ansible-avd.git@refactor-sflow#subdirectory=python-avd" --force
# Point Ansible collections path to the Python virtual environment
export ANSIBLE_COLLECTIONS_PATH=$VIRTUAL_ENV/ansible_collections
# Install Ansible collection
ansible-galaxy collection install git+https://github.com/MaheshGSLAB/ansible-avd.git#/ansible_collections/arista/avd/,refactor-sflow --force
# Optional: Install AVD examples
cd test-avd-pr-5123
ansible-playbook arista.avd.install_examples
```

author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-12T09:18:49Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_avd&pullRequest=5123) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=aristanetworks_avd&pullRequest=5123&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_avd&pullRequest=5123&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_avd&pullRequest=5123&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [92.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_avd&pullRequest=5123&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_avd&pullRequest=5123&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_avd&pullRequest=5123)


author_association: NONE