Repository: antrea-io/antrea
Pull request #6853: Reconcile secondary network OVS ports after Agent restart
Author: login: KMAnju-2021
Created: 2024-12-11T19:08:11Z | Merged: 2025-03-28T17:54:08Z

Closes: #6578 #5623 

Store the SecondaryNetwork interface in interfacestore after Agent restart and delete secondaryNetwork OVS ports correctly.

e2e case: -->
1. restart the antrea agent
2. check for the ovs ports restored or not
3. delete the one testpod p1
4. check for the stale ovs ports
5. check for the pod ip (released or not)


Diff:
diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go
index ce8c46e6370..6034fcb92b0 100644
--- a/cmd/antrea-agent/agent.go
+++ b/cmd/antrea-agent/agent.go
@@ -647,6 +647,19 @@ func run(o *Options) error {
 		}
 	}
 
+	// Secondary network controller should be created before CNIServer.Run() to make sure no Pod CNI updates will be missed.
+	var secondaryNetworkController *secondarynetwork.Controller
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		secondaryNetworkController, err = secondarynetwork.NewController(
+			o.config.ClientConnection, o.config.KubeAPIServerOverride,
+			k8sClient, localPodInformer.Get(),
+			podUpdateChannel, ifaceStore,
+			&o.config.SecondaryNetwork, ovsdbConnection)
+		if err != nil {
+			return fmt.Errorf("failed to create secondary network controller: %w", err)
+		}
+	}
+
 	var traceflowController *traceflow.Controller
 	if features.DefaultFeatureGate.Enabled(features.Traceflow) {
 		traceflowController = traceflow.NewTraceflowController(
@@ -761,18 +774,6 @@ func run(o *Options) error {
 		go ipamController.Run(stopCh)
 	}
 
-	var secondaryNetworkController *secondarynetwork.Controller
-	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
-		secondaryNetworkController, err = secondarynetwork.NewController(
-			o.config.ClientConnection, o.config.KubeAPIServerOverride,
-			k8sClient, localPodInformer.Get(),
-			podUpdateChannel,
-			&o.config.SecondaryNetwork, ovsdbConnection)
-		if err != nil {
-			return fmt.Errorf("failed to create secondary network controller: %w", err)
-		}
-	}
-
 	var bgpController *bgp.Controller
 	if features.DefaultFeatureGate.Enabled(features.BGPPolicy) {
 		bgpPolicyInformer := crdInformerFactory.Crd().V1alpha1().BGPPolicies()
diff --git a/pkg/agent/secondarynetwork/init.go b/pkg/agent/secondarynetwork/init.go
index 721027f1a13..bb1c72224ee 100644
--- a/pkg/agent/secondarynetwork/init.go
+++ b/pkg/agent/secondarynetwork/init.go
@@ -24,6 +24,7 @@ import (
 	componentbaseconfig "k8s.io/component-base/config"
 	"k8s.io/klog/v2"
 
+	"antrea.io/antrea/pkg/agent/interfacestore"
 	"antrea.io/antrea/pkg/agent/secondarynetwork/podwatch"
 	agentconfig "antrea.io/antrea/pkg/config/agent"
 	"antrea.io/antrea/pkg/ovs/ovsconfig"
@@ -47,6 +48,7 @@ func NewController(
 	k8sClient clientset.Interface,
 	podInformer cache.SharedIndexInformer,
 	podUpdateSubscriber channel.Subscriber,
+	primaryInterfaceStore interfacestore.InterfaceStore,
 	secNetConfig *agentconfig.SecondaryNetworkConfig, ovsdb *ovsdb.OVSDB,
 ) (*Controller, error) {
 	ovsBridgeClient, err := createOVSBridge(secNetConfig.OVSBridges, ovsdb)
@@ -65,7 +67,7 @@ func NewController(
 	// k8s.v1.cni.cncf.io/networks Annotation defined.
 	podWatchController, err := podwatch.NewPodController(
 		k8sClient, netAttachDefClient, podInformer,
-		podUpdateSubscriber, ovsBridgeClient)
+		podUpdateSubscriber, primaryInterfaceStore, ovsBridgeClient)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/agent/secondarynetwork/podwatch/controller.go b/pkg/agent/secondarynetwork/podwatch/controller.go
index 1dcbfd449d1..3ccc0b39793 100644
--- a/pkg/agent/secondarynetwork/podwatch/controller.go
+++ b/pkg/agent/secondarynetwork/podwatch/controller.go
@@ -103,6 +103,7 @@ func NewPodController(
 	netAttachDefClient netdefclient.K8sCniCncfIoV1Interface,
 	podInformer cache.SharedIndexInformer,
 	podUpdateSubscriber channel.Subscriber,
+	primaryInterfaceStore interfacestore.InterfaceStore,
 	ovsBridgeClient ovsconfig.OVSBridgeClient,
 ) (*PodController, error) {
 	ifaceStore := interfacestore.NewInterfaceStore()
@@ -134,6 +135,19 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	// This is the case when secondary bridge is not configured and no VLAN interfaces at all. In this case,
+	// we should skip both initializeSecondaryInterfaceStore and reconcileSecondaryInterfaces.
+	if ovsBridgeClient != nil {
+		if err := pc.initializeSecondaryInterfaceStore(); err != nil {
+			return nil, fmt.Errorf("failed to initialize secondary interface store: %w", err)
+		}
+
+		if err := pc.reconcileSecondaryInterfaces(primaryInterfaceStore); err != nil {
+			return nil, fmt.Errorf("failed to restore CNI cache and reconcile secondary interfaces: %w", err)
+		}
+	}
+
 	// podUpdateSubscriber can be nil with test code.
 	if podUpdateSubscriber != nil {
 		// Subscribe Pod CNI add/del events.
@@ -521,3 +535,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %w", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+			continue
+		}
+
+		ifaceList = append(ifaceList, intf)
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(primaryInterfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := primaryInterfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+	}
+
+	var staleInterfaces []*interfacestore.InterfaceConfig
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		_, exists := primaryInterfaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists || containerConfig.OFPort == -1 {
+			// Deletes ports not in the CNI cache.
+			staleInterfaces = append(staleInterfaces, containerConfig)
+		}
+	}
+
+	// If there are any stale interfaces, pass them to removeInterfaces()
+	if len(staleInterfaces) > 0 {
+		if err := pc.removeInterfaces(staleInterfaces); err != nil {
+			klog.ErrorS(err, "Failed to remove stale secondary interfaces", "staleInterfaces", staleInterfaces)
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/agent/secondarynetwork/podwatch/controller_test.go b/pkg/agent/secondarynetwork/podwatch/controller_test.go
index 08bbffaf79d..ee8f1c0a5d6 100644
--- a/pkg/agent/secondarynetwork/podwatch/controller_test.go
+++ b/pkg/agent/secondarynetwork/podwatch/controller_test.go
@@ -32,6 +32,7 @@ import (
 	"time"
 
 	current "github.com/containernetworking/cni/pkg/types/100"
+	"github.com/google/uuid"
 	netdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
 	netdefclientfake "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/fake"
 	"github.com/stretchr/testify/assert"
@@ -43,12 +44,15 @@ import (
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/util/workqueue"
 
+	"antrea.io/antrea/pkg/agent/cniserver"
 	"antrea.io/antrea/pkg/agent/cniserver/ipam"
 	cnitypes "antrea.io/antrea/pkg/agent/cniserver/types"
 	"antrea.io/antrea/pkg/agent/interfacestore"
 	podwatchtesting "antrea.io/antrea/pkg/agent/secondarynetwork/podwatch/testing"
 	"antrea.io/antrea/pkg/agent/types"
 	crdv1beta1 "antrea.io/antrea/pkg/apis/crd/v1beta1"
+	"antrea.io/antrea/pkg/ovs/ovsconfig"
+	ovsconfigtest "antrea.io/antrea/pkg/ovs/ovsconfig/testing"
 )
 
 const (
@@ -213,6 +217,9 @@ func TestPodControllerRun(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	client := fake.NewSimpleClientset()
 	netdefclient := netdefclientfake.NewSimpleClientset().K8sCniCncfIoV1()
+	mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(ctrl)
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, nil).AnyTimes()
+	primaryInterfaceStore := interfacestore.NewInterfaceStore()
 	informerFactory := informers.NewSharedInformerFactory(client, resyncPeriod)
 	interfaceConfigurator := podwatchtesting.NewMockInterfaceConfigurator(ctrl)
 	mockIPAM := podwatchtesting.NewMockIPAMAllocator(ctrl)
@@ -220,7 +227,7 @@ func TestPodControllerRun(t *testing.T) {
 		client,
 		netdefclient,
 		informerFactory.Core().V1().Pods().Informer(),
-		nil, nil)
+		nil, primaryInterfaceStore, mockOVSBridgeClient)
 	podController.interfaceConfigurator = interfaceConfigurator
 	podController.ipamAllocator = mockIPAM
 	cniCache := &podController.cniCache
@@ -968,7 +975,7 @@ func TestPodControllerAddPod(t *testing.T) {
 
 	t.Run("updating deviceID cache per Pod", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
-		podController, _, _ := testPodController(ctrl)
+		podController, _, _, _ := testPodController(ctrl)
 		_, err := podController.assignUnusedSriovVFDeviceID(podName, testNamespace, sriovResourceName1, interfaceName)
 		_, exists := podController.vfDeviceIDUsageMap.Load(podKey)
 		assert.True(t, exists)
@@ -984,16 +991,18 @@ func TestPodControllerAddPod(t *testing.T) {
 
 func testPodController(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
-	*podwatchtesting.MockInterfaceConfigurator) {
+	*podwatchtesting.MockInterfaceConfigurator, *ovsconfigtest.MockOVSBridgeClient) {
 	client := fake.NewSimpleClientset()
 	netdefclient := netdefclientfake.NewSimpleClientset().K8sCniCncfIoV1()
 	informerFactory := informers.NewSharedInformerFactory(client, resyncPeriod)
 	interfaceConfigurator := podwatchtesting.NewMockInterfaceConfigurator(ctrl)
 	mockIPAM := podwatchtesting.NewMockIPAMAllocator(ctrl)
+	mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(ctrl)
 
 	// PodController without event handlers.
 	return &PodController{
 		kubeClient:         client,
+		ovsBridgeClient:    mockOVSBridgeClient,
 		netAttachDefClient: netdefclient,
 		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
 			workqueue.NewTypedItemExponentialFailureRateLimiter[string](minRetryDelay, maxRetryDelay),
@@ -1005,14 +1014,14 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
@@ -1020,3 +1029,124 @@ func testPodControllerStart(ctrl *gomock.Controller) (
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+	uuid4 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	// Create InterfaceConfig objects directly
+	containerConfig1 := interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "nsA", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)
+	containerConfig1.OVSPortConfig = &interfacestore.OVSPortConfig{
+		OFPort: 11,
+	}
+	containerConfig2 := interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "nsA", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)
+	containerConfig2.OVSPortConfig = &interfacestore.OVSPortConfig{
+		OFPort: 12,
+	}
+	containerConfig3 := interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "nsA", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)
+	containerConfig3.OVSPortConfig = &interfacestore.OVSPortConfig{
+		OFPort: -1,
+	}
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig1))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig2))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig3))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid4,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3, "uuid4": uuid4}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{containerConfig1, containerConfig2, containerConfig3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+	uuids, ovsPorts, _ := createTestInterfaces()
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(ovsPorts, nil)
+
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "OVS ports list successfully")
+
+	// Validate stored interfaces
+	require.Equal(t, 3, pc.interfaceStore.Len(), "Only valid interfaces should be stored")
+
+	_, found1 := pc.interfaceStore.GetContainerInterface(uuids["uuid1"])
+	assert.True(t, found1, "Interface 1 should be stored")
+
+	_, found2 := pc.interfaceStore.GetContainerInterface(uuids["uuid2"])
+	assert.True(t, found2, "Interface 2 should be stored")
+
+	_, found3 := pc.interfaceStore.GetContainerInterface(uuids["uuid3"])
+	assert.True(t, found3, "Interface 3 should be stored")
+
+	_, found4 := pc.interfaceStore.GetContainerInterface(uuids["uuid4"])
+	assert.False(t, found4, "Unknown interface type should not be stored")
+}
+
+func TestReconcileSecondaryInterfaces(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	pc, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
+	primaryStore := interfacestore.NewInterfaceStore()
+	_, _, containerConfigs := createTestInterfaces()
+
+	// Add interfaces to primary store
+	primaryStore.AddInterface(containerConfigs[0])
+	primaryStore.AddInterface(containerConfigs[1])
+
+	// Add interfaces to controller secondaryInterfaceStore
+	pc.interfaceStore.AddInterface(containerConfigs[0])
+	pc.interfaceStore.AddInterface(containerConfigs[1])
+	// Case when OFPort == -1
+	pc.interfaceStore.AddInterface(containerConfigs[2])
+
+	interfaceConfigurator.EXPECT().DeleteVLANSecondaryInterface(gomock.Any()).Return(nil).Times(1)
+	mockIPAM.EXPECT().SecondaryNetworkRelease(gomock.Any()).Return(nil).Times(1)
+
+	err := pc.reconcileSecondaryInterfaces(primaryStore)
+
+	require.NoError(t, err)
+
+	// Check CNI Cache
+	_, foundPod1 := pc.cniCache.Load("nsA/Pod1")
+	assert.True(t, foundPod1, "CNI Cache should contain nsA/Pod1")
+
+	_, foundPod2 := pc.cniCache.Load("nsA/Pod2")
+	assert.True(t, foundPod2, "CNI Cache should contain nsA/Pod2")
+
+	// Ensure stale interfaces are removed
+	_, foundPod3 := pc.cniCache.Load("nsA/Pod3")
+	assert.False(t, foundPod3, "Stale interface should have been removed")
+}
diff --git a/test/e2e-secondary-network/secondary_network_test.go b/test/e2e-secondary-network/secondary_network_test.go
index b79d967e29a..8d20dcd4459 100644
--- a/test/e2e-secondary-network/secondary_network_test.go
+++ b/test/e2e-secondary-network/secondary_network_test.go
@@ -16,16 +16,21 @@ package e2esecondary
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"net"
 	"strings"
 	"testing"
 	"time"
 
+	netattdef "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned"
 	logs "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
 
 	antreae2e "antrea.io/antrea/test/e2e"
@@ -51,6 +56,8 @@ const (
 
 	// Namespace of NetworkAttachmentDefinition CRs.
 	attachDefNamespace = "default"
+	ipPoolNamespace    = "default"
+	secondaryOVSBridge = "br-secondary"
 
 	containerName  = "toolbox"
 	podApp         = "secondaryTest"
@@ -223,6 +230,146 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames maps each Pod's interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	beforeAgentRestartOvsPorts := make(map[string][]string)
+	beforeAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports before agent restart")
+		beforeAgentRestartOvsPorts[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP before agent restart")
+		beforeAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	require.NoError(t, data.e2eTestData.RestartAntreaAgentPods(30*time.Second), "Failed to restart Antrea agent Pods")
+
+	afterAgentRestartOvsPorts := make(map[string][]string)
+	afterAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports after agent restart")
+		afterAgentRestartOvsPorts[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP after agent restart")
+		afterAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	// Compare OVS ports before and after restart
+	for nodeName, portsBefore := range beforeAgentRestartOvsPorts {
+		assert.ElementsMatch(t, portsBefore, afterAgentRestartOvsPorts[nodeName],
+			"Secondary bridge OVS Ports mismatch after agent restart on node %s", nodeName)
+	}
+
+	// Compare IPs and interfaces before and after restart
+	for podName, ipsBefore := range beforeAgentRestartIPsAndIfaces {
+		assert.Equal(t, ipsBefore, afterAgentRestartIPsAndIfaces[podName],
+			"Pod: %s, Interfaces and IPs mismatch after agent restart", podName)
+	}
+
+	// Remove Pod and check IP released or not.
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	require.NoError(t, err, "Failed to get IPs of Interfaces")
+
+	beforeDeletionOvsPorts, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports Before Pod deletion")
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, ipPoolNamespace)
+	require.NoError(t, err, "Failed to get IPPool")
+
+	require.NoError(t, data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()), "Failed to delete Pod")
+
+	afterDeletionOvsPorts, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports After Pod deletion")
+
+	assert.NotEqual(t, beforeDeletionOvsPorts, afterDeletionOvsPorts, "OVS Ports for VLAN Pod still exist")
+
+	data.checkIPReleased(ipPools, ifacesIPs, ifaces)
+}
+
 func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo) {
 	e2eTestData, err := antreae2e.SetupTest(t)
 	if err != nil {
@@ -232,14 +379,41 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 
 	testData := &testData{e2eTestData: e2eTestData, networkType: networkType, pods: pods}
 
-	err = testData.createPods(t, e2eTestData.GetTestNamespace())
-	if err != nil {
-		t.Fatalf("Error when create test Pods: %v", err)
-	}
-	err = testData.pingBetweenInterfaces(t)
-	if err != nil {
-		t.Fatalf("Error when pinging between interfaces: %v", err)
+	t.Run("testCreateTestPodOnNode", func(t *testing.T) {
+		err := testData.createPods(t, e2eTestData.GetTestNamespace())
+		require.NoError(t, err, "Error when create test Pods")
+	})
+	t.Run("testPingBetweenInterfaces", func(t *testing.T) {
+		err := testData.pingBetweenInterfaces(t)
+		require.NoError(t, err, "Error when pinging between interfaces")
+	})
+	t.Run("testReconcilationAfterAgentRestart", func(t *testing.T) {
+		testData.reconcilationAfterAgentRestart(t)
+	})
+}
+
+func TestSriovNetwork(t *testing.T) {
+	// Create Pods on the control plane Node, assuming a single Node cluster for the SR-IOV
+	// test.
+	nodeName := antreae2e.NodeName(0)
+	pods := []*testPodInfo{
+		{
+			podName:           "sriov-pod1",
+			nodeName:          nodeName,
+			interfaceNetworks: map[string]string{"eth1": "sriov-net1", "eth2": "sriov-net2"},
+		},
+		{
+			podName:           "sriov-pod2",
+			nodeName:          nodeName,
+			interfaceNetworks: map[string]string{"eth2": "sriov-net1", "eth3": "sriov-net3"},
+		},
+		{
+			podName:           "sriov-pod3",
+			nodeName:          nodeName,
+			interfaceNetworks: map[string]string{"eth4": "sriov-net1"},
+		},
 	}
+	testSecondaryNetwork(t, networkTypeSriov, pods)
 }
 
 func TestVLANNetwork(t *testing.T) {
diff --git a/test/e2e/antreaipam_test.go b/test/e2e/antreaipam_test.go
index 0f1810b571b..bad09b7b456 100644
--- a/test/e2e/antreaipam_test.go
+++ b/test/e2e/antreaipam_test.go
@@ -309,10 +309,10 @@ func TestAntreaIPAM(t *testing.T) {
 }
 
 func testIPPoolConversion(t *testing.T, data *TestData) {
-	_, err := data.crdClient.CrdV1alpha2().IPPools().Create(context.TODO(), &v1a1Pool, metav1.CreateOptions{})
+	_, err := data.CRDClient.CrdV1alpha2().IPPools().Create(context.TODO(), &v1a1Pool, metav1.CreateOptions{})
 	assert.NoError(t, err, "failed to create v1alpha2 IPPool")
 	defer deleteIPPoolWrapper(t, data, v1a1Pool.Name)
-	v1beta1Pool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), v1a1Pool.Name, metav1.GetOptions{})
+	v1beta1Pool, err := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), v1a1Pool.Name, metav1.GetOptions{})
 	assert.NoError(t, err, "failed to get v1beta1 IPPool")
 	assert.Equal(t, v1a1Pool.Name, v1beta1Pool.Name)
 	assert.Equal(t, v1a1Pool.Spec.IPRanges[0].Start, v1beta1Pool.Spec.IPRanges[0].Start)
@@ -321,10 +321,10 @@ func testIPPoolConversion(t *testing.T, data *TestData) {
 	assert.Equal(t, v1a1Pool.Spec.IPRanges[0].PrefixLength, v1beta1Pool.Spec.SubnetInfo.PrefixLength)
 	assert.Equal(t, int32(v1a1Pool.Spec.IPRanges[0].VLAN), v1beta1Pool.Spec.SubnetInfo.VLAN)
 
-	_, err = data.crdClient.CrdV1beta1().IPPools().Create(context.TODO(), &v1b1Pool, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().IPPools().Create(context.TODO(), &v1b1Pool, metav1.CreateOptions{})
 	defer deleteIPPoolWrapper(t, data, v1b1Pool.Name)
 	assert.NoError(t, err, "failed to create v1beta1 IPPool")
-	v1alpha2Pool, err := data.crdClient.CrdV1alpha2().IPPools().Get(context.TODO(), v1b1Pool.Name, metav1.GetOptions{})
+	v1alpha2Pool, err := data.CRDClient.CrdV1alpha2().IPPools().Get(context.TODO(), v1b1Pool.Name, metav1.GetOptions{})
 	assert.NoError(t, err, "failed to get v1alpha2 IPPool")
 	assert.Equal(t, v1b1Pool.Name, v1alpha2Pool.Name)
 	assert.Equal(t, v1b1Pool.Spec.IPRanges[0].CIDR, v1alpha2Pool.Spec.IPRanges[0].CIDR)
@@ -509,7 +509,7 @@ func testAntreaIPAMStatefulSet(t *testing.T, data *TestData, dedicatedIPPoolKey
 }
 
 func checkStatefulSetIPPoolAllocation(tb testing.TB, data *TestData, name string, namespace string, ipPoolName string, ipOffsets, reservedIPOffsets []int32) {
-	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	ipPool, err := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
 	if err != nil {
 		tb.Fatalf("Failed to get IPPool %s, err: %+v", ipPoolName, err)
 	}
@@ -553,7 +553,7 @@ func checkStatefulSetIPPoolAllocation(tb testing.TB, data *TestData, name string
 	tb.Logf("expectedIPAddressMap: %s", expectedIPAddressJson)
 
 	err = wait.PollUntilContextTimeout(context.Background(), time.Second*3, time.Second*15, false, func(ctx context.Context) (bool, error) {
-		ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+		ipPool, err := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
 		if err != nil {
 			tb.Fatalf("Failed to get IPPool %s, err: %+v", ipPoolName, err)
 		}
@@ -598,11 +598,11 @@ func deleteAntreaIPAMNamespace(tb testing.TB, data *TestData, namespace string)
 func createIPPool(tb testing.TB, data *TestData, key string) (*crdv1beta1.IPPool, error) {
 	ipv4IPPool := subnetIPv4RangesMap[key]
 	tb.Logf("Creating IPPool '%s'", ipv4IPPool.Name)
-	return data.crdClient.CrdV1beta1().IPPools().Create(context.TODO(), &ipv4IPPool, metav1.CreateOptions{})
+	return data.CRDClient.CrdV1beta1().IPPools().Create(context.TODO(), &ipv4IPPool, metav1.CreateOptions{})
 }
 
 func checkIPPoolAllocation(tb testing.TB, data *TestData, ipPoolName, podIPString string) (isBelongTo bool, ipAddressState *crdv1beta1.IPAddressState, err error) {
-	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	ipPool, err := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
 	if err != nil {
 		return
 	}
@@ -636,8 +636,8 @@ func checkIPPoolAllocation(tb testing.TB, data *TestData, ipPoolName, podIPStrin
 func deleteIPPoolWrapper(tb testing.TB, data *TestData, name string) {
 	tb.Logf("Deleting IPPool '%s'", name)
 	for i := 0; i < 10; i++ {
-		if err := data.crdClient.CrdV1beta1().IPPools().Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil {
-			ipPool, _ := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), name, metav1.GetOptions{})
+		if err := data.CRDClient.CrdV1beta1().IPPools().Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil {
+			ipPool, _ := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), name, metav1.GetOptions{})
 			ipPoolJson, _ := json.Marshal(ipPool)
 			tb.Logf("Error when deleting IPPool, err: %v, data: %s", err, ipPoolJson)
 			time.Sleep(defaultInterval)
@@ -651,7 +651,7 @@ func checkIPPoolsEmpty(tb testing.TB, data *TestData, names []string) {
 	count := 0
 	err := wait.PollUntilContextTimeout(context.Background(), 3*time.Second, defaultTimeout, true, func(ctx context.Context) (bool, error) {
 		for _, name := range names {
-			ipPool, _ := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), name, metav1.GetOptions{})
+			ipPool, _ := data.CRDClient.CrdV1beta1().IPPools().Get(context.TODO(), name, metav1.GetOptions{})
 			if len(ipPool.Status.IPAddresses) > 0 {
 				ipPoolJson, _ := json.Marshal(ipPool)
 				if count > 20 {
diff --git a/test/e2e/antreapolicy_test.go b/test/e2e/antreapolicy_test.go
index 50347e9d174..8b3d265bfb8 100644
--- a/test/e2e/antreapolicy_test.go
+++ b/test/e2e/antreapolicy_test.go
@@ -4133,7 +4133,7 @@ func testACNPIGMPQuery(t *testing.T, data *TestData, acnpName, caseName, groupAd
 		nil, nil, nil, nil, nil, action, "", "", nil)
 	acnp := builder.Get()
 	_, err = k8sUtils.CreateOrUpdateACNP(acnp)
-	defer data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
 	if err != nil {
 		t.Fatalf("failed to create acnp %v: %v", acnpName, err)
 	}
@@ -4218,7 +4218,7 @@ func testACNPMulticastEgress(t *testing.T, data *TestData, acnpName, caseName, g
 	if err != nil {
 		t.Fatalf("failed to create acnp %v: %v", acnpName, err)
 	}
-	defer data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
 
 	captured, err := checkPacketCaptureResult(t, data, tcpdumpName, cmd)
 	if action == crdv1beta1.RuleActionAllow {
@@ -4769,9 +4769,9 @@ func TestAntreaPolicyStatus(t *testing.T) {
 		nil, nil, nil, nil, crdv1beta1.RuleActionAllow, "", "")
 	annp := annpBuilder.Get()
 	log.Debugf("creating ANNP %v", annp.Name)
-	_, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
 	assert.NoError(t, err)
-	defer data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Delete(context.TODO(), annp.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Delete(context.TODO(), annp.Name, metav1.DeleteOptions{})
 
 	acnpBuilder := &ClusterNetworkPolicySpecBuilder{}
 	acnpBuilder = acnpBuilder.SetName("acnp-applied-to-two-nodes").
@@ -4781,9 +4781,9 @@ func TestAntreaPolicyStatus(t *testing.T) {
 		nil, nil, nil, nil, nil, crdv1beta1.RuleActionAllow, "", "", nil)
 	acnp := acnpBuilder.Get()
 	log.Debugf("creating ACNP %v", acnp.Name)
-	_, err = data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Create(context.TODO(), acnp, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Create(context.TODO(), acnp, metav1.CreateOptions{})
 	assert.NoError(t, err)
-	defer data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), acnp.Name, metav1.DeleteOptions{})
 
 	expectedStatus := crdv1beta1.NetworkPolicyStatus{
 		Phase:                crdv1beta1.NetworkPolicyRealized,
@@ -4820,9 +4820,9 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) {
 		nil, nil, nil, []ANNPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": server1Name}}}, crdv1beta1.RuleActionAllow, "", "")
 	annp := annpBuilder.Get()
 	log.Debugf("creating ANNP %v", annp.Name)
-	annp, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
+	annp, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
 	assert.NoError(t, err)
-	defer data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Delete(context.TODO(), annp.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Delete(context.TODO(), annp.Name, metav1.DeleteOptions{})
 
 	annp = checkANNPStatus(t, data, annp, crdv1beta1.NetworkPolicyStatus{
 		Phase:                crdv1beta1.NetworkPolicyRealized,
@@ -4834,7 +4834,7 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) {
 
 	// Remove the second ingress rule.
 	annp.Spec.Ingress = annp.Spec.Ingress[0:1]
-	_, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
 	assert.NoError(t, err)
 	annp = checkANNPStatus(t, data, annp, crdv1beta1.NetworkPolicyStatus{
 		Phase:                crdv1beta1.NetworkPolicyRealized,
@@ -4847,7 +4847,7 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) {
 	// Add a non-existing group.
 	// Although nothing will be changed in datapath, the policy's status should be realized with the latest generation.
 	annp.Spec.Ingress[0].AppliedTo = append(annp.Spec.Ingress[0].AppliedTo, crdv1beta1.AppliedTo{Group: "foo"})
-	_, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
 	assert.NoError(t, err)
 	annp = checkANNPStatus(t, data, annp, crdv1beta1.NetworkPolicyStatus{
 		Phase:                crdv1beta1.NetworkPolicyRealized,
@@ -4860,7 +4860,7 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) {
 	// Delete the non-existing group.
 	// Although nothing will be changed in datapath, the policy's status should be realized with the latest generation.
 	annp.Spec.Ingress[0].AppliedTo = annp.Spec.Ingress[0].AppliedTo[0:1]
-	_, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), annp, metav1.UpdateOptions{})
 	assert.NoError(t, err)
 	checkANNPStatus(t, data, annp, crdv1beta1.NetworkPolicyStatus{
 		Phase:                crdv1beta1.NetworkPolicyRealized,
@@ -4943,7 +4943,7 @@ func TestAntreaPolicyStatusWithAppliedToUnsupportedGroup(t *testing.T) {
 func checkANNPStatus(t *testing.T, data *TestData, annp *crdv1beta1.NetworkPolicy, expectedStatus crdv1beta1.NetworkPolicyStatus) *crdv1beta1.NetworkPolicy {
 	err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, policyRealizedTimeout, false, func(ctx context.Context) (bool, error) {
 		var err error
-		annp, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Get(context.TODO(), annp.Name, metav1.GetOptions{})
+		annp, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Get(context.TODO(), annp.Name, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -4956,7 +4956,7 @@ func checkANNPStatus(t *testing.T, data *TestData, annp *crdv1beta1.NetworkPolic
 func checkACNPStatus(t *testing.T, data *TestData, acnp *crdv1beta1.ClusterNetworkPolicy, expectedStatus crdv1beta1.NetworkPolicyStatus) *crdv1beta1.ClusterNetworkPolicy {
 	err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, policyRealizedTimeout, false, func(ctx context.Context) (bool, error) {
 		var err error
-		acnp, err = data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), acnp.Name, metav1.GetOptions{})
+		acnp, err = data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), acnp.Name, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -4972,7 +4972,7 @@ func checkACNPStatus(t *testing.T, data *TestData, acnp *crdv1beta1.ClusterNetwo
 func (data *TestData) waitForANNPRealized(t *testing.T, namespace string, name string, timeout time.Duration) error {
 	t.Logf("Waiting for ANNP '%s/%s' to be realized", namespace, name)
 	if err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, timeout, false, func(ctx context.Context) (bool, error) {
-		annp, err := data.crdClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		annp, err := data.CRDClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -4989,7 +4989,7 @@ func (data *TestData) waitForANNPRealized(t *testing.T, namespace string, name s
 func (data *TestData) waitForACNPRealized(t *testing.T, name string, timeout time.Duration) error {
 	t.Logf("Waiting for ACNP '%s' to be realized", name)
 	if err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, timeout, false, func(ctx context.Context) (bool, error) {
-		acnp, err := data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
+		acnp, err := data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -5131,7 +5131,7 @@ func testANNPNetworkPolicyStatsWithDropAction(t *testing.T, data *TestData) {
 	}
 
 	if err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, defaultTimeout, false, func(ctx context.Context) (bool, error) {
-		stats, err := data.crdClient.StatsV1alpha1().AntreaNetworkPolicyStats(data.testNamespace).Get(context.TODO(), "np1", metav1.GetOptions{})
+		stats, err := data.CRDClient.StatsV1alpha1().AntreaNetworkPolicyStats(data.testNamespace).Get(context.TODO(), "np1", metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -5266,7 +5266,7 @@ func testAntreaClusterNetworkPolicyStats(t *testing.T, data *TestData) {
 	}
 
 	if err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, defaultTimeout, false, func(ctx context.Context) (bool, error) {
-		stats, err := data.crdClient.StatsV1alpha1().AntreaClusterNetworkPolicyStats().Get(context.TODO(), "cnp1", metav1.GetOptions{})
+		stats, err := data.CRDClient.StatsV1alpha1().AntreaClusterNetworkPolicyStats().Get(context.TODO(), "cnp1", metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
diff --git a/test/e2e/bgppolicy_test.go b/test/e2e/bgppolicy_test.go
index f2e9ccb19c7..0bc3eb6f779 100644
--- a/test/e2e/bgppolicy_test.go
+++ b/test/e2e/bgppolicy_test.go
@@ -152,8 +152,8 @@ func TestBGPPolicy(t *testing.T) {
 			},
 		},
 	}
-	bgpPolicy, err = data.crdClient.CrdV1alpha1().BGPPolicies().Create(context.TODO(), bgpPolicy, metav1.CreateOptions{})
-	defer data.crdClient.CrdV1alpha1().BGPPolicies().Delete(context.TODO(), bgpPolicyName, metav1.DeleteOptions{})
+	bgpPolicy, err = data.CRDClient.CrdV1alpha1().BGPPolicies().Create(context.TODO(), bgpPolicy, metav1.CreateOptions{})
+	defer data.CRDClient.CrdV1alpha1().BGPPolicies().Delete(context.TODO(), bgpPolicyName, metav1.DeleteOptions{})
 	require.NoError(t, err)
 
 	t.Log("Get the routes installed on remote FRR router and verify them")
@@ -184,7 +184,7 @@ func TestBGPPolicy(t *testing.T) {
 	updatedBGPPolicy := bgpPolicy.DeepCopy()
 	updatedBGPPolicy.Spec.LocalASN = updatedNodeASN
 	updatedBGPPolicy.Spec.Advertisements.Pod = nil
-	_, err = data.crdClient.CrdV1alpha1().BGPPolicies().Update(context.TODO(), updatedBGPPolicy, metav1.UpdateOptions{})
+	_, err = data.CRDClient.CrdV1alpha1().BGPPolicies().Update(context.TODO(), updatedBGPPolicy, metav1.UpdateOptions{})
 	require.NoError(t, err)
 
 	t.Log("Get routes installed on remote FRR router and verify them")
diff --git a/test/e2e/egress_test.go b/test/e2e/egress_test.go
index 41bd72a6451..edf94d2d9c5 100644
--- a/test/e2e/egress_test.go
+++ b/test/e2e/egress_test.go
@@ -81,7 +81,7 @@ func testCreateExternalIPPool(t *testing.T, data *TestData) {
 		Spec:       v1beta1.ExternalIPPoolSpec{NodeSelector: metav1.LabelSelector{MatchLabels: map[string]string{"env": "pro-"}}},
 	}
 
-	_, err := data.crdClient.CrdV1beta1().ExternalIPPools().Create(context.TODO(), &eip, metav1.CreateOptions{})
+	_, err := data.CRDClient.CrdV1beta1().ExternalIPPools().Create(context.TODO(), &eip, metav1.CreateOptions{})
 	assert.Error(t, err, "Should fail to create ExternalIPPool")
 }
 
@@ -162,14 +162,14 @@ func testEgressClientIP(t *testing.T, data *TestData) {
 				},
 			}
 			egress := data.createEgress(t, "egress-", matchExpressions, nil, "", egressNodeIP, nil)
-			defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			assertClientIP(data, t, localPod, toolboxContainerName, tt.serverIP, egressNodeIP)
 			assertClientIP(data, t, remotePod, toolboxContainerName, tt.serverIP, egressNodeIP)
 
 			var err error
 			err = wait.PollUntilContextTimeout(context.Background(), time.Millisecond*100, time.Second, false,
 				func(ctx context.Context) (bool, error) {
-					egress, err = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+					egress, err = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 					if err != nil {
 						return false, err
 					}
@@ -203,7 +203,7 @@ func testEgressClientIP(t *testing.T, data *TestData) {
 					MatchLabels: map[string]string{"antrea-e2e": remotePod},
 				},
 			}
-			egress, err = data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
+			egress, err = data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
 			if err != nil {
 				t.Fatalf("Failed to update Egress %v: %v", egress, err)
 			}
@@ -216,7 +216,7 @@ func testEgressClientIP(t *testing.T, data *TestData) {
 					MatchLabels: map[string]string{"antrea-e2e": localPod},
 				},
 			}
-			egress, err = data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
+			egress, err = data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
 			if err != nil {
 				t.Fatalf("Failed to update Egress %v: %v", egress, err)
 			}
@@ -225,7 +225,7 @@ func testEgressClientIP(t *testing.T, data *TestData) {
 
 			t.Logf("Updating the Egress's EgressIP to %s", tt.localIP1)
 			egress.Spec.EgressIP = tt.localIP1
-			egress, err = data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
+			egress, err = data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
 			if err != nil {
 				t.Fatalf("Failed to update Egress %v: %v", egress, err)
 			}
@@ -233,7 +233,7 @@ func testEgressClientIP(t *testing.T, data *TestData) {
 			assertConnError(data, t, remotePod, toolboxContainerName, tt.serverIP)
 
 			t.Log("Deleting the Egress")
-			err = data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			err = data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			if err != nil {
 				t.Fatalf("Failed to delete Egress %v: %v", egress, err)
 			}
@@ -310,12 +310,12 @@ func testEgressClientIPFromVLANSubnet(t *testing.T, data *TestData) {
 				VLAN:         int32(tt.vlanID),
 			}
 			pool := data.createExternalIPPool(t, "pool-vlan", ipRange, &subnet, nil, nil)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), pool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), pool.Name, metav1.DeleteOptions{})
 
 			egress := data.createEgress(t, "egress-vlan", nil, map[string]string{"antrea-e2e": clientPod1}, pool.Name, "", nil)
-			defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			err := wait.PollUntilContextTimeout(context.Background(), 500*time.Millisecond, 3*time.Second, true, func(ctx context.Context) (done bool, err error) {
-				egress, err = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+				egress, err = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 				if err != nil {
 					return false, err
 				}
@@ -344,7 +344,7 @@ func testEgressClientIPFromVLANSubnet(t *testing.T, data *TestData) {
 					MatchLabels: map[string]string{"antrea-e2e": clientPod2},
 				},
 			}
-			egress, err = data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
+			egress, err = data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), egress, metav1.UpdateOptions{})
 			if err != nil {
 				t.Fatalf("Failed to update Egress %v: %v", egress, err)
 			}
@@ -352,7 +352,7 @@ func testEgressClientIPFromVLANSubnet(t *testing.T, data *TestData) {
 			assertClientIP(data, t, clientPod2, toolboxContainerName, tt.serverIP, egress.Spec.EgressIP)
 
 			t.Log("Deleting the Egress")
-			err = data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			err = data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			if err != nil {
 				t.Fatalf("Failed to delete Egress %v: %v", egress, err)
 			}
@@ -449,14 +449,14 @@ func testEgressCRUD(t *testing.T, data *TestData) {
 				skipIfNotIPv4Cluster(t)
 			}
 			pool := data.createExternalIPPool(t, "crud-pool-", tt.ipRange, nil, tt.nodeSelector.MatchExpressions, tt.nodeSelector.MatchLabels)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), pool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), pool.Name, metav1.DeleteOptions{})
 
 			egress := data.createEgress(t, "crud-egress-", nil, map[string]string{"foo": "bar"}, pool.Name, "", nil)
-			defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			// Use Poll to wait the interval before the first run to detect the case that the IP is assigned to any Node
 			// when it's not supposed to.
 			err := wait.PollUntilContextTimeout(context.Background(), 500*time.Millisecond, 3*time.Second, false, func(ctx context.Context) (done bool, err error) {
-				egress, err = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+				egress, err = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 				if err != nil {
 					return false, err
 				}
@@ -496,7 +496,7 @@ func testEgressCRUD(t *testing.T, data *TestData) {
 				var gotUsed, gotTotal int
 				err := wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 2*time.Second, true,
 					func(ctx context.Context) (done bool, err error) {
-						pool, err := data.crdClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), pool.Name, metav1.GetOptions{})
+						pool, err := data.CRDClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), pool.Name, metav1.GetOptions{})
 						if err != nil {
 							return false, fmt.Errorf("failed to get ExternalIPPool: %v", err)
 						}
@@ -513,7 +513,7 @@ func testEgressCRUD(t *testing.T, data *TestData) {
 			}
 			checkEIPStatus(1)
 
-			err = data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			err = data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			require.NoError(t, err, "Failed to delete Egress")
 			if egress.Status.EgressNode != "" {
 				err := wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, timeout, true,
@@ -577,12 +577,12 @@ func testEgressUpdateEgressIP(t *testing.T, data *TestData) {
 				skipIfNotIPv4Cluster(t)
 			}
 			originalPool := data.createExternalIPPool(t, "originalpool-", tt.originalIPRange, nil, nil, map[string]string{v1.LabelHostname: tt.originalNode})
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), originalPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), originalPool.Name, metav1.DeleteOptions{})
 			newPool := data.createExternalIPPool(t, "newpool-", tt.newIPRange, nil, nil, map[string]string{v1.LabelHostname: tt.newNode})
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), newPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), newPool.Name, metav1.DeleteOptions{})
 
 			egress := data.createEgress(t, "egress-", nil, map[string]string{"foo": "bar"}, originalPool.Name, "", nil)
-			defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 			egress, err := data.checkEgressState(egress.Name, tt.originalEgressIP, tt.originalNode, "", time.Second)
 			require.NoError(t, err)
 
@@ -591,9 +591,9 @@ func testEgressUpdateEgressIP(t *testing.T, data *TestData) {
 			err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
 				toUpdate.Spec.ExternalIPPool = newPool.Name
 				toUpdate.Spec.EgressIP = tt.newEgressIP
-				_, err = data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), toUpdate, metav1.UpdateOptions{})
+				_, err = data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), toUpdate, metav1.UpdateOptions{})
 				if err != nil && errors.IsConflict(err) {
-					toUpdate, _ = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+					toUpdate, _ = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 				}
 				return err
 			})
@@ -657,7 +657,7 @@ func testEgressUpdateNodeSelector(t *testing.T, data *TestData) {
 				skipIfNotIPv6Cluster(t)
 			}
 			updateNodeSelector := func(poolName, evictNode string, ensureExists bool) {
-				pool, err := data.crdClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), poolName, metav1.GetOptions{})
+				pool, err := data.CRDClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), poolName, metav1.GetOptions{})
 				require.NoError(t, err, "Failed to get ExternalIPPool %v", pool)
 				newNodes := sets.New[string](pool.Spec.NodeSelector.MatchExpressions[0].Values...)
 				if ensureExists {
@@ -666,7 +666,7 @@ func testEgressUpdateNodeSelector(t *testing.T, data *TestData) {
 					newNodes.Delete(evictNode)
 				}
 				pool.Spec.NodeSelector.MatchExpressions[0].Values = sets.List(newNodes)
-				_, err = data.crdClient.CrdV1beta1().ExternalIPPools().Update(context.TODO(), pool, metav1.UpdateOptions{})
+				_, err = data.CRDClient.CrdV1beta1().ExternalIPPools().Update(context.TODO(), pool, metav1.UpdateOptions{})
 				require.NoError(t, err, "Failed to update ExternalIPPool %v", pool)
 			}
 			shrinkEgressNodes := func(poolName, evictNode string) {
@@ -745,10 +745,10 @@ func testEgressMigration(t *testing.T, data *TestData, triggerFunc, revertFunc f
 		},
 	}
 	externalIPPoolTwoNodes := data.createExternalIPPool(t, "pool-with-two-nodes-", *ipRange, nil, matchExpressions, nil)
-	defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), externalIPPoolTwoNodes.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), externalIPPoolTwoNodes.Name, metav1.DeleteOptions{})
 
 	egress := data.createEgress(t, "migration-egress-", nil, map[string]string{"foo": "bar"}, externalIPPoolTwoNodes.Name, "", nil)
-	defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 
 	var err error
 	egress, err = data.waitForEgressRealized(egress)
@@ -828,7 +828,7 @@ func testEgressUpdateBandwidth(t *testing.T, data *TestData) {
 	egress := data.createEgress(t, "egress-qos-", nil, map[string]string{"antrea-e2e": clientPodName}, "", egressNodeIP, bandwidth)
 	_, err = data.waitForEgressRealized(egress)
 	require.NoError(t, err, "Error when waiting for Egress to be realized")
-	defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 
 	// expectedBandwidth is Mbps
 	runIperf := func(cmd []string, expectedBandwidth int) {
@@ -852,7 +852,7 @@ func (data *TestData) checkEgressState(egressName, expectedIP, expectedNode, oth
 	var expectedNodeHasIP, otherNodeHasIP bool
 	pollErr := wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) {
 		var err error
-		egress, err = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egressName, metav1.GetOptions{})
+		egress, err = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egressName, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -969,7 +969,7 @@ func (data *TestData) createExternalIPPool(t *testing.T, generateName string, ip
 			},
 		},
 	}
-	pool, err := data.crdClient.CrdV1beta1().ExternalIPPools().Create(context.TODO(), pool, metav1.CreateOptions{})
+	pool, err := data.CRDClient.CrdV1beta1().ExternalIPPools().Create(context.TODO(), pool, metav1.CreateOptions{})
 	require.NoError(t, err, "Failed to create ExternalIPPool")
 	return pool
 }
@@ -989,7 +989,7 @@ func (data *TestData) createEgress(t *testing.T, generateName string, matchExpre
 			Bandwidth:      bandwidth,
 		},
 	}
-	egress, err := data.crdClient.CrdV1beta1().Egresses().Create(context.TODO(), egress, metav1.CreateOptions{})
+	egress, err := data.CRDClient.CrdV1beta1().Egresses().Create(context.TODO(), egress, metav1.CreateOptions{})
 	require.NoError(t, err, "Failed to create Egress")
 	return egress
 }
@@ -997,7 +997,7 @@ func (data *TestData) createEgress(t *testing.T, generateName string, matchExpre
 func (data *TestData) waitForEgressRealized(egress *v1beta1.Egress) (*v1beta1.Egress, error) {
 	err := wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, waitEgressRealizedTimeout, true,
 		func(ctx context.Context) (done bool, err error) {
-			egress, err = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+			egress, err = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 			if err != nil {
 				return false, err
 			}
diff --git a/test/e2e/flowaggregator_test.go b/test/e2e/flowaggregator_test.go
index e01e586f9ce..003dda6dd27 100644
--- a/test/e2e/flowaggregator_test.go
+++ b/test/e2e/flowaggregator_test.go
@@ -866,7 +866,7 @@ func testHelper(t *testing.T, data *TestData, isIPv6 bool) {
 			t.Fatalf("Error when waiting for Egress to be realized: %v", err)
 		}
 		t.Logf("Egress %s is realized with Egress IP %s", egress.Name, egressNodeIP)
-		defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+		defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 		if !isIPv6 {
 			if clientIPs.IPv4 != nil && serverIPs.IPv4 != nil {
 				checkRecordsForToExternalFlows(t, data, nodeName(0), clientName, clientIPs.IPv4.String(), serverIPs.IPv4.String(), serverPodPort, isIPv6, egress.Name, egressNodeIP, egressNodeName, label)
@@ -908,7 +908,7 @@ func testHelper(t *testing.T, data *TestData, isIPv6 bool) {
 			t.Fatalf("Error when waiting for Egress to be realized: %v", err)
 		}
 		t.Logf("Egress %s is realized with Egress IP %s", egress.Name, egressNodeIP)
-		defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+		defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 		if !isIPv6 {
 			if clientIPs.IPv4 != nil && serverIPs.IPv4 != nil {
 				checkRecordsForToExternalFlows(t, data, nodeName(0), clientName, clientIPs.IPv4.String(), serverIPs.IPv4.String(), serverPodPort, isIPv6, egress.Name, egressNodeIP, egressNodeName, label)
diff --git a/test/e2e/framework.go b/test/e2e/framework.go
index 370f70a03e8..c199cbb642a 100644
--- a/test/e2e/framework.go
+++ b/test/e2e/framework.go
@@ -255,7 +255,7 @@ type TestData struct {
 	KubeConfig         *restclient.Config
 	clientset          kubernetes.Interface
 	aggregatorClient   aggregatorclientset.Interface
-	crdClient          crdclientset.Interface
+	CRDClient          crdclientset.Interface
 	logsDirForTestCase string
 	testNamespace      string
 }
@@ -1320,7 +1320,7 @@ func (data *TestData) CreateClient(kubeconfigPath string) error {
 	data.KubeConfig = kubeConfig
 	data.clientset = clientset
 	data.aggregatorClient = aggregatorClient
-	data.crdClient = crdClient
+	data.CRDClient = crdClient
 	return nil
 }
 
@@ -3187,7 +3187,7 @@ func retryOnConnectionLostError(backoff wait.Backoff, fn func() error) error {
 
 func (data *TestData) checkAntreaAgentInfo(interval time.Duration, timeout time.Duration, name string) error {
 	err := wait.PollUntilContextTimeout(context.TODO(), interval, timeout, true, func(ctx context.Context) (bool, error) {
-		aai, err := data.crdClient.CrdV1beta1().AntreaAgentInfos().Get(context.TODO(), name, metav1.GetOptions{})
+		aai, err := data.CRDClient.CrdV1beta1().AntreaAgentInfos().Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil {
 			if errors.IsNotFound(err) {
 				return false, nil
diff --git a/test/e2e/k8s_util.go b/test/e2e/k8s_util.go
index 6fe96c27801..3b9dbd81ba7 100644
--- a/test/e2e/k8s_util.go
+++ b/test/e2e/k8s_util.go
@@ -795,18 +795,18 @@ func (data *TestData) CreateTier(name string, tierPriority int32) (*crdv1beta1.T
 		ObjectMeta: metav1.ObjectMeta{Name: name},
 		Spec:       crdv1beta1.TierSpec{Priority: tierPriority},
 	}
-	return data.crdClient.CrdV1beta1().Tiers().Create(context.TODO(), tr, metav1.CreateOptions{})
+	return data.CRDClient.CrdV1beta1().Tiers().Create(context.TODO(), tr, metav1.CreateOptions{})
 }
 
 // GetTier is a convenience function for getting Tier.
 func (data *TestData) GetTier(name string) (*crdv1beta1.Tier, error) {
-	return data.crdClient.CrdV1beta1().Tiers().Get(context.TODO(), name, metav1.GetOptions{})
+	return data.CRDClient.CrdV1beta1().Tiers().Get(context.TODO(), name, metav1.GetOptions{})
 }
 
 // UpdateTier is a convenience function for updating an Antrea Policy Tier.
 func (data *TestData) UpdateTier(tier *crdv1beta1.Tier) (*crdv1beta1.Tier, error) {
 	log.Infof("Updating tier %s", tier.Name)
-	return data.crdClient.CrdV1beta1().Tiers().Update(context.TODO(), tier, metav1.UpdateOptions{})
+	return data.CRDClient.CrdV1beta1().Tiers().Update(context.TODO(), tier, metav1.UpdateOptions{})
 }
 
 func isReferencedError(err error) bool {
@@ -823,7 +823,7 @@ func isReferencedError(err error) bool {
 func (data *TestData) DeleteTier(name string) error {
 	log.Infof("Deleting tier %s", name)
 	if err := retry.OnError(retry.DefaultRetry, isReferencedError, func() error {
-		return data.crdClient.CrdV1beta1().Tiers().Delete(context.TODO(), name, metav1.DeleteOptions{})
+		return data.CRDClient.CrdV1beta1().Tiers().Delete(context.TODO(), name, metav1.DeleteOptions{})
 	}); err != nil {
 		return fmt.Errorf("unable to delete tier %s: %w", name, err)
 	}
@@ -833,9 +833,9 @@ func (data *TestData) DeleteTier(name string) error {
 // CreateOrUpdateCG is a convenience function for idempotent setup of crd/v1beta1 ClusterGroups
 func (data *TestData) CreateOrUpdateCG(cg *crdv1beta1.ClusterGroup) (*crdv1beta1.ClusterGroup, error) {
 	log.Infof("Creating/updating ClusterGroup %s", cg.Name)
-	cgReturned, err := data.crdClient.CrdV1beta1().ClusterGroups().Get(context.TODO(), cg.Name, metav1.GetOptions{})
+	cgReturned, err := data.CRDClient.CrdV1beta1().ClusterGroups().Get(context.TODO(), cg.Name, metav1.GetOptions{})
 	if err != nil {
-		cgr, err := data.crdClient.CrdV1beta1().ClusterGroups().Create(context.TODO(), cg, metav1.CreateOptions{})
+		cgr, err := data.CRDClient.CrdV1beta1().ClusterGroups().Create(context.TODO(), cg, metav1.CreateOptions{})
 		if err != nil {
 			log.Infof("Unable to create cluster group %s: %v", cg.Name, err)
 			return nil, err
@@ -844,7 +844,7 @@ func (data *TestData) CreateOrUpdateCG(cg *crdv1beta1.ClusterGroup) (*crdv1beta1
 	} else if cgReturned.Name != "" {
 		log.Debugf("ClusterGroup with name %s already exists, updating", cg.Name)
 		cgReturned.Spec = cg.Spec
-		cgr, err := data.crdClient.CrdV1beta1().ClusterGroups().Update(context.TODO(), cgReturned, metav1.UpdateOptions{})
+		cgr, err := data.CRDClient.CrdV1beta1().ClusterGroups().Update(context.TODO(), cgReturned, metav1.UpdateOptions{})
 		return cgr, err
 	}
 	return nil, fmt.Errorf("error occurred in creating/updating ClusterGroup %s", cg.Name)
@@ -853,9 +853,9 @@ func (data *TestData) CreateOrUpdateCG(cg *crdv1beta1.ClusterGroup) (*crdv1beta1
 // CreateOrUpdateGroup is a convenience function for idempotent setup of crd/v1beta1 Groups
 func (k *KubernetesUtils) CreateOrUpdateGroup(g *crdv1beta1.Group) (*crdv1beta1.Group, error) {
 	log.Infof("Creating/updating Group %s/%s", g.Namespace, g.Name)
-	gReturned, err := k.crdClient.CrdV1beta1().Groups(g.Namespace).Get(context.TODO(), g.Name, metav1.GetOptions{})
+	gReturned, err := k.CRDClient.CrdV1beta1().Groups(g.Namespace).Get(context.TODO(), g.Name, metav1.GetOptions{})
 	if err != nil {
-		gr, err := k.crdClient.CrdV1beta1().Groups(g.Namespace).Create(context.TODO(), g, metav1.CreateOptions{})
+		gr, err := k.CRDClient.CrdV1beta1().Groups(g.Namespace).Create(context.TODO(), g, metav1.CreateOptions{})
 		if err != nil {
 			log.Infof("Unable to create group %s/%s: %v", g.Namespace, g.Name, err)
 			return nil, err
@@ -864,7 +864,7 @@ func (k *KubernetesUtils) CreateOrUpdateGroup(g *crdv1beta1.Group) (*crdv1beta1.
 	} else if gReturned.Name != "" {
 		log.Debugf("Group %s/%s already exists, updating", g.Namespace, g.Name)
 		gReturned.Spec = g.Spec
-		gr, err := k.crdClient.CrdV1beta1().Groups(g.Namespace).Update(context.TODO(), gReturned, metav1.UpdateOptions{})
+		gr, err := k.CRDClient.CrdV1beta1().Groups(g.Namespace).Update(context.TODO(), gReturned, metav1.UpdateOptions{})
 		return gr, err
 	}
 	return nil, fmt.Errorf("error occurred in creating/updating Group %s/%s", g.Namespace, g.Name)
@@ -872,43 +872,43 @@ func (k *KubernetesUtils) CreateOrUpdateGroup(g *crdv1beta1.Group) (*crdv1beta1.
 
 // GetCG is a convenience function for getting ClusterGroups
 func (k *KubernetesUtils) GetCG(name string) (*crdv1beta1.ClusterGroup, error) {
-	return k.crdClient.CrdV1beta1().ClusterGroups().Get(context.TODO(), name, metav1.GetOptions{})
+	return k.CRDClient.CrdV1beta1().ClusterGroups().Get(context.TODO(), name, metav1.GetOptions{})
 }
 
 // GetGroup is a convenience function for getting Groups
 func (k *KubernetesUtils) GetGroup(namespace, name string) (*crdv1beta1.Group, error) {
-	return k.crdClient.CrdV1beta1().Groups(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	return k.CRDClient.CrdV1beta1().Groups(namespace).Get(context.TODO(), name, metav1.GetOptions{})
 }
 
 // DeleteCG is a convenience function for deleting core/v1beta1 ClusterGroup by name.
 func (data *TestData) DeleteCG(name string) error {
 	log.Infof("Deleting ClusterGroup %s", name)
-	return data.crdClient.CrdV1beta1().ClusterGroups().Delete(context.TODO(), name, metav1.DeleteOptions{})
+	return data.CRDClient.CrdV1beta1().ClusterGroups().Delete(context.TODO(), name, metav1.DeleteOptions{})
 }
 
 // DeleteGroup is a convenience function for deleting core/v1beta1 Group by namespace and name.
 func (k *KubernetesUtils) DeleteGroup(namespace, name string) error {
 	log.Infof("Deleting Group %s/%s", namespace, name)
-	return k.crdClient.CrdV1beta1().Groups(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
+	return k.CRDClient.CrdV1beta1().Groups(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
 }
 
 // CleanCGs is a convenience function for deleting all ClusterGroups in the cluster.
 func (data *TestData) CleanCGs() error {
-	return data.crdClient.CrdV1beta1().ClusterGroups().DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
+	return data.CRDClient.CrdV1beta1().ClusterGroups().DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
 }
 
 // CleanGroups is a convenience function for deleting all Groups in the namespace.
 func (k *KubernetesUtils) CleanGroups(namespace string) error {
-	return k.crdClient.CrdV1beta1().Groups(namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
+	return k.CRDClient.CrdV1beta1().Groups(namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
 }
 
 // CreateOrUpdateACNP is a convenience function for updating/creating AntreaClusterNetworkPolicies.
 func (data *TestData) CreateOrUpdateACNP(cnp *crdv1beta1.ClusterNetworkPolicy) (*crdv1beta1.ClusterNetworkPolicy, error) {
 	log.Infof("Creating/updating ClusterNetworkPolicy %s", cnp.Name)
-	cnpReturned, err := data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), cnp.Name, metav1.GetOptions{})
+	cnpReturned, err := data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), cnp.Name, metav1.GetOptions{})
 	if err != nil {
 		log.Debugf("Creating ClusterNetworkPolicy %s", cnp.Name)
-		cnp, err = data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Create(context.TODO(), cnp, metav1.CreateOptions{})
+		cnp, err = data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Create(context.TODO(), cnp, metav1.CreateOptions{})
 		if err != nil {
 			log.Debugf("Unable to create ClusterNetworkPolicy: %s", err)
 		}
@@ -916,7 +916,7 @@ func (data *TestData) CreateOrUpdateACNP(cnp *crdv1beta1.ClusterNetworkPolicy) (
 	} else if cnpReturned.Name != "" {
 		log.Debugf("ClusterNetworkPolicy with name %s already exists, updating", cnp.Name)
 		cnpReturned.Spec = cnp.Spec
-		cnp, err = data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Update(context.TODO(), cnpReturned, metav1.UpdateOptions{})
+		cnp, err = data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Update(context.TODO(), cnpReturned, metav1.UpdateOptions{})
 		return cnp, err
 	}
 	return nil, fmt.Errorf("error occurred in creating/updating ClusterNetworkPolicy %s", cnp.Name)
@@ -924,27 +924,27 @@ func (data *TestData) CreateOrUpdateACNP(cnp *crdv1beta1.ClusterNetworkPolicy) (
 
 // GetACNP is a convenience function for getting AntreaClusterNetworkPolicies.
 func (data *TestData) GetACNP(name string) (*crdv1beta1.ClusterNetworkPolicy, error) {
-	return data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
+	return data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
 }
 
 // DeleteACNP is a convenience function for deleting ACNP by name.
 func (data *TestData) DeleteACNP(name string) error {
 	log.Infof("Deleting AntreaClusterNetworkPolicies %s", name)
-	return data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), name, metav1.DeleteOptions{})
+	return data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Delete(context.TODO(), name, metav1.DeleteOptions{})
 }
 
 // CleanACNPs is a convenience function for deleting all Antrea ClusterNetworkPolicies in the cluster.
 func (data *TestData) CleanACNPs() error {
-	return data.crdClient.CrdV1beta1().ClusterNetworkPolicies().DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
+	return data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
 }
 
 // CreateOrUpdateANNP is a convenience function for updating/creating Antrea NetworkPolicies.
 func (data *TestData) CreateOrUpdateANNP(annp *crdv1beta1.NetworkPolicy) (*crdv1beta1.NetworkPolicy, error) {
 	log.Infof("Creating/updating Antrea NetworkPolicy %s/%s", annp.Namespace, annp.Name)
-	npReturned, err := data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Get(context.TODO(), annp.Name, metav1.GetOptions{})
+	npReturned, err := data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Get(context.TODO(), annp.Name, metav1.GetOptions{})
 	if err != nil {
 		log.Debugf("Creating Antrea NetworkPolicy %s", annp.Name)
-		annp, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
+		annp, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Create(context.TODO(), annp, metav1.CreateOptions{})
 		if err != nil {
 			log.Debugf("Unable to create Antrea NetworkPolicy: %s", err)
 		}
@@ -952,7 +952,7 @@ func (data *TestData) CreateOrUpdateANNP(annp *crdv1beta1.NetworkPolicy) (*crdv1
 	} else if npReturned.Name != "" {
 		log.Debugf("Antrea NetworkPolicy with name %s already exists, updating", annp.Name)
 		npReturned.Spec = annp.Spec
-		annp, err = data.crdClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), npReturned, metav1.UpdateOptions{})
+		annp, err = data.CRDClient.CrdV1beta1().NetworkPolicies(annp.Namespace).Update(context.TODO(), npReturned, metav1.UpdateOptions{})
 		return annp, err
 	}
 	return nil, fmt.Errorf("error occurred in creating/updating Antrea NetworkPolicy %s", annp.Name)
@@ -960,19 +960,19 @@ func (data *TestData) CreateOrUpdateANNP(annp *crdv1beta1.NetworkPolicy) (*crdv1
 
 // GetANNP is a convenience function for getting AntreaNetworkPolicies.
 func (data *TestData) GetANNP(namespace, name string) (*crdv1beta1.NetworkPolicy, error) {
-	return data.crdClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	return data.CRDClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
 }
 
 // DeleteANNP is a convenience function for deleting ANNP by name and Namespace.
 func (data *TestData) DeleteANNP(ns, name string) error {
 	log.Infof("Deleting Antrea NetworkPolicy '%s/%s'", ns, name)
-	return data.crdClient.CrdV1beta1().NetworkPolicies(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
+	return data.CRDClient.CrdV1beta1().NetworkPolicies(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
 }
 
 // CleanANNPs is a convenience function for deleting all Antrea NetworkPolicies in provided namespaces.
 func (data *TestData) CleanANNPs(namespaces []string) error {
 	for _, ns := range namespaces {
-		if err := data.crdClient.CrdV1beta1().NetworkPolicies(ns).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{}); err != nil {
+		if err := data.CRDClient.CrdV1beta1().NetworkPolicies(ns).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{}); err != nil {
 			return fmt.Errorf("unable to delete Antrea NetworkPolicies in ns %s: %w", ns, err)
 		}
 	}
@@ -982,7 +982,7 @@ func (data *TestData) CleanANNPs(namespaces []string) error {
 func (data *TestData) WaitForANNPCreationAndRealization(t *testing.T, namespace string, name string, timeout time.Duration) error {
 	t.Logf("Waiting for ANNP '%s/%s' to be realized", namespace, name)
 	if err := wait.PollUntilContextTimeout(context.TODO(), 100*time.Millisecond, timeout, false, func(ctx context.Context) (bool, error) {
-		annp, err := data.crdClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		annp, err := data.CRDClient.CrdV1beta1().NetworkPolicies(namespace).Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil {
 			return false, nil
 		}
@@ -996,7 +996,7 @@ func (data *TestData) WaitForANNPCreationAndRealization(t *testing.T, namespace
 func (data *TestData) WaitForACNPCreationAndRealization(t *testing.T, name string, timeout time.Duration) error {
 	t.Logf("Waiting for ACNP '%s' to be created and realized", name)
 	if err := wait.PollUntilContextTimeout(context.TODO(), 100*time.Millisecond, timeout, false, func(ctx context.Context) (bool, error) {
-		acnp, err := data.crdClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
+		acnp, err := data.CRDClient.CrdV1beta1().ClusterNetworkPolicies().Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil {
 			return false, nil
 		}
diff --git a/test/e2e/l7networkpolicy_test.go b/test/e2e/l7networkpolicy_test.go
index b2e20e8318b..c3addd23b16 100644
--- a/test/e2e/l7networkpolicy_test.go
+++ b/test/e2e/l7networkpolicy_test.go
@@ -134,7 +134,7 @@ func createL7NetworkPolicy(t *testing.T,
 
 	annp := annpBuilder.Get()
 	t.Logf("Creating ANNP %v", annp.Name)
-	_, err := data.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Create(context.TODO(), annp, metav1.CreateOptions{})
+	_, err := data.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Create(context.TODO(), annp, metav1.CreateOptions{})
 	assert.NoError(t, err)
 }
 
@@ -272,7 +272,7 @@ func testL7NetworkPolicyHTTP(t *testing.T, data *TestData) {
 		probeL7NetworkPolicyHTTP(t, data, serverPodName, clientPodName, serviceIPs, true, false)
 
 		// Delete the first L7 NetworkPolicy that only allows HTTP path 'hostname'.
-		data.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowPathHostname, metav1.DeleteOptions{})
+		data.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowPathHostname, metav1.DeleteOptions{})
 		time.Sleep(networkPolicyDelay)
 
 		// Since the fist L7 NetworkPolicy has been deleted, corresponding packets will be matched by the second L7 NetworkPolicy,
@@ -280,7 +280,7 @@ func testL7NetworkPolicyHTTP(t *testing.T, data *TestData) {
 		probeL7NetworkPolicyHTTP(t, data, serverPodName, clientPodName, dstPodIPs, true, true)
 		probeL7NetworkPolicyHTTP(t, data, serverPodName, clientPodName, serviceIPs, true, true)
 
-		data.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowAnyPath, metav1.DeleteOptions{})
+		data.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowAnyPath, metav1.DeleteOptions{})
 	})
 
 	time.Sleep(networkPolicyDelay)
@@ -301,7 +301,7 @@ func testL7NetworkPolicyHTTP(t *testing.T, data *TestData) {
 		probeL7NetworkPolicyHTTP(t, data, serverPodName, clientPodName, serviceIPs, true, false)
 
 		// Delete the first L7 NetworkPolicy that only allows HTTP path 'hostname'.
-		data.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowPathHostname, metav1.DeleteOptions{})
+		data.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowPathHostname, metav1.DeleteOptions{})
 		time.Sleep(networkPolicyDelay)
 
 		// Since the fist L7 NetworkPolicy has been deleted, corresponding packets will be matched by the second L7 NetworkPolicy,
@@ -424,7 +424,7 @@ func testL7NetworkPolicyTLS(t *testing.T, data *TestData) {
 	probeL7NetworkPolicyTLS(t, data, clientPodName, serverIPs, serverNameBravo, false)
 
 	// Delete the first L7 NetworkPolicy that allows server name '*.alfa.test.l7.tls'.
-	data.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowSNIAlfa, metav1.DeleteOptions{})
+	data.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policyAllowSNIAlfa, metav1.DeleteOptions{})
 	time.Sleep(networkPolicyDelay)
 
 	probeL7NetworkPolicyTLS(t, data, clientPodName, serverIPs, serverNameAlfa, false)
diff --git a/test/e2e/multicast_test.go b/test/e2e/multicast_test.go
index 8134467a4d7..054a49ed3c2 100644
--- a/test/e2e/multicast_test.go
+++ b/test/e2e/multicast_test.go
@@ -493,7 +493,7 @@ func testMulticastStatsWithSendersReceivers(t *testing.T, data *TestData, testNa
 			}
 		}
 		for _, anp := range mc.igmpANPConfigs {
-			stats, err := data.crdClient.StatsV1alpha1().AntreaNetworkPolicyStats(testNamespace).Get(context.TODO(), anp.name, metav1.GetOptions{})
+			stats, err := data.CRDClient.StatsV1alpha1().AntreaNetworkPolicyStats(testNamespace).Get(context.TODO(), anp.name, metav1.GetOptions{})
 			if err != nil {
 				return false, err
 			}
@@ -514,7 +514,7 @@ func testMulticastStatsWithSendersReceivers(t *testing.T, data *TestData, testNa
 			}
 		}
 		for _, anp := range mc.multicastANPConfigs {
-			stats, err := data.crdClient.StatsV1alpha1().AntreaNetworkPolicyStats(testNamespace).Get(context.TODO(), anp.name, metav1.GetOptions{})
+			stats, err := data.CRDClient.StatsV1alpha1().AntreaNetworkPolicyStats(testNamespace).Get(context.TODO(), anp.name, metav1.GetOptions{})
 			if err != nil {
 				return false, err
 			}
@@ -530,7 +530,7 @@ func testMulticastStatsWithSendersReceivers(t *testing.T, data *TestData, testNa
 			}
 		}
 		for _, group := range sets.List(groupAddresses) {
-			multicastGroup, err := data.crdClient.StatsV1alpha1().MulticastGroups().Get(context.TODO(), group, metav1.GetOptions{})
+			multicastGroup, err := data.CRDClient.StatsV1alpha1().MulticastGroups().Get(context.TODO(), group, metav1.GetOptions{})
 			if err != nil && !errors.IsNotFound(err) {
 				t.Logf("Got multicastGroup error %v", err)
 				return false, err
diff --git a/test/e2e/networkpolicy_test.go b/test/e2e/networkpolicy_test.go
index bef78a76cb0..519b12b5642 100644
--- a/test/e2e/networkpolicy_test.go
+++ b/test/e2e/networkpolicy_test.go
@@ -197,7 +197,7 @@ func testNetworkPolicyStats(t *testing.T, data *TestData) {
 	if err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, defaultTimeout, false, func(ctx context.Context) (bool, error) {
 		var ingressStats *v1alpha1.NetworkPolicyStats
 		for _, np := range []string{"test-networkpolicy-ingress", "test-networkpolicy-egress"} {
-			stats, err := data.crdClient.StatsV1alpha1().NetworkPolicyStats(data.testNamespace).Get(context.TODO(), np, metav1.GetOptions{})
+			stats, err := data.CRDClient.StatsV1alpha1().NetworkPolicyStats(data.testNamespace).Get(context.TODO(), np, metav1.GetOptions{})
 			if err != nil {
 				return false, err
 			}
diff --git a/test/e2e/nodelatencymonitor_test.go b/test/e2e/nodelatencymonitor_test.go
index c9ea90accc9..9619ee79c7b 100644
--- a/test/e2e/nodelatencymonitor_test.go
+++ b/test/e2e/nodelatencymonitor_test.go
@@ -47,7 +47,7 @@ func TestNodeLatencyMonitor(t *testing.T) {
 		expectedTargetIPLatencyStats = 2
 	}
 
-	_, err = data.crdClient.CrdV1alpha1().NodeLatencyMonitors().Create(context.TODO(), &crdv1alpha1.NodeLatencyMonitor{
+	_, err = data.CRDClient.CrdV1alpha1().NodeLatencyMonitors().Create(context.TODO(), &crdv1alpha1.NodeLatencyMonitor{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: "default",
 		},
@@ -59,7 +59,7 @@ func TestNodeLatencyMonitor(t *testing.T) {
 	t.Logf("NodeLatencyMonitor CR created successfully.")
 
 	defer func() {
-		err = data.crdClient.CrdV1alpha1().NodeLatencyMonitors().Delete(context.TODO(), "default", metav1.DeleteOptions{})
+		err = data.CRDClient.CrdV1alpha1().NodeLatencyMonitors().Delete(context.TODO(), "default", metav1.DeleteOptions{})
 		require.NoError(t, err, "Failed to delete NodeLatencyMonitor CR")
 		t.Logf("NodeLatencyMonitor CR deleted successfully.")
 	}()
@@ -109,7 +109,7 @@ func TestNodeLatencyMonitor(t *testing.T) {
 	}
 
 	err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 30*time.Second, false, func(ctx context.Context) (bool, error) {
-		statsList, err := data.crdClient.StatsV1alpha1().NodeLatencyStats().List(ctx, metav1.ListOptions{})
+		statsList, err := data.CRDClient.StatsV1alpha1().NodeLatencyStats().List(ctx, metav1.ListOptions{})
 		if err != nil {
 			return false, err
 		}
@@ -118,7 +118,7 @@ func TestNodeLatencyMonitor(t *testing.T) {
 	require.NoError(t, err, "Failed to validate initial NodeLatencyStats")
 
 	err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 30*time.Second, false, func(ctx context.Context) (bool, error) {
-		statsList, err := data.crdClient.StatsV1alpha1().NodeLatencyStats().List(ctx, metav1.ListOptions{})
+		statsList, err := data.CRDClient.StatsV1alpha1().NodeLatencyStats().List(ctx, metav1.ListOptions{})
 		if err != nil {
 			return false, err
 		}
diff --git a/test/e2e/packetcapture_test.go b/test/e2e/packetcapture_test.go
index 6ac6670f342..ef14cdd4961 100644
--- a/test/e2e/packetcapture_test.go
+++ b/test/e2e/packetcapture_test.go
@@ -472,11 +472,11 @@ func runPacketCaptureTest(t *testing.T, data *TestData, tc pcTestCase) {
 		}
 	}
 
-	if _, err := data.crdClient.CrdV1alpha1().PacketCaptures().Create(context.TODO(), tc.pc, metav1.CreateOptions{}); err != nil {
+	if _, err := data.CRDClient.CrdV1alpha1().PacketCaptures().Create(context.TODO(), tc.pc, metav1.CreateOptions{}); err != nil {
 		t.Fatalf("Error when creating PacketCapture: %v", err)
 	}
 	defer func() {
-		if err := data.crdClient.CrdV1alpha1().PacketCaptures().Delete(context.TODO(), tc.pc.Name, metav1.DeleteOptions{}); err != nil {
+		if err := data.CRDClient.CrdV1alpha1().PacketCaptures().Delete(context.TODO(), tc.pc.Name, metav1.DeleteOptions{}); err != nil {
 			t.Errorf("Error when deleting PacketCapture: %v", err)
 		}
 	}()
@@ -559,7 +559,7 @@ func (data *TestData) waitForPacketCapture(t *testing.T, name string, specTimeou
 		timeout = time.Duration(specTimeout) * time.Second
 	}
 	if err = wait.PollUntilContextTimeout(context.Background(), defaultInterval, timeout, true, func(ctx context.Context) (bool, error) {
-		c, err := data.crdClient.CrdV1alpha1().PacketCaptures().Get(ctx, name, metav1.GetOptions{})
+		c, err := data.CRDClient.CrdV1alpha1().PacketCaptures().Get(ctx, name, metav1.GetOptions{})
 		if err != nil {
 			return false, nil
 		}
diff --git a/test/e2e/proxy_test.go b/test/e2e/proxy_test.go
index 792249bf579..fd5c1a8fe81 100644
--- a/test/e2e/proxy_test.go
+++ b/test/e2e/proxy_test.go
@@ -416,7 +416,7 @@ func TestNodePortAndEgressWithTheSameBackendPod(t *testing.T) {
 	// Create an Egress whose external IP is on worker Node.
 	egressNodeIP := workerNodeIPv4(1)
 	egress := data.createEgress(t, "test-egress", nil, map[string]string{"app": "nginx"}, "", egressNodeIP, nil)
-	defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 
 	// Create the backend Pod on control plane Node.
 	backendPodName := "test-nodeport-egress-backend-pod"
diff --git a/test/e2e/secondary_network_ipam_test.go b/test/e2e/secondary_network_ipam_test.go
index 41ba3ce0292..d457a545701 100644
--- a/test/e2e/secondary_network_ipam_test.go
+++ b/test/e2e/secondary_network_ipam_test.go
@@ -254,12 +254,12 @@ func TestSecondaryNetworkIPAM(t *testing.T) {
 	skipIfProxyDisabled(t, data)
 	skipIfEncapModeIsNot(t, data, config.TrafficEncapModeEncap)
 
-	_, err = data.crdClient.CrdV1beta1().IPPools().Create(context.TODO(), testIPPoolv4, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().IPPools().Create(context.TODO(), testIPPoolv4, metav1.CreateOptions{})
 	defer deleteIPPoolWrapper(t, data, testIPPoolv4.Name)
 	if err != nil {
 		t.Fatalf("Failed to create v4 IPPool CR: %v", err)
 	}
-	_, err = data.crdClient.CrdV1beta1().IPPools().Create(context.TODO(), testIPPoolv6, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1beta1().IPPools().Create(context.TODO(), testIPPoolv6, metav1.CreateOptions{})
 	defer deleteIPPoolWrapper(t, data, testIPPoolv6.Name)
 	if err != nil {
 		t.Fatalf("Failed to create v6 IPPool CR: %v", err)
diff --git a/test/e2e/service_externalip_test.go b/test/e2e/service_externalip_test.go
index 25ff395acfb..c59bdcd4656 100644
--- a/test/e2e/service_externalip_test.go
+++ b/test/e2e/service_externalip_test.go
@@ -294,7 +294,7 @@ func testServiceExternalTrafficPolicyLocal(t *testing.T, data *TestData) {
 			var service *v1.Service
 			var eps *v1.Endpoints
 			ipPool := data.createExternalIPPool(t, "test-service-pool-", tt.ipRange, nil, tt.nodeSelector.MatchExpressions, tt.nodeSelector.MatchLabels)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
 
 			annotation := map[string]string{
 				antreaagenttypes.ServiceExternalIPPoolAnnotationKey: ipPool.Name,
@@ -411,7 +411,7 @@ func testServiceWithExternalIPCRUD(t *testing.T, data *TestData) {
 			var err error
 			var service *v1.Service
 			ipPool := data.createExternalIPPool(t, "crud-pool-", tt.ipRange, nil, tt.nodeSelector.MatchExpressions, tt.nodeSelector.MatchLabels)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
 
 			annotation := map[string]string{
 				antreaagenttypes.ServiceExternalIPPoolAnnotationKey: ipPool.Name,
@@ -432,7 +432,7 @@ func testServiceWithExternalIPCRUD(t *testing.T, data *TestData) {
 				var gotUsed, gotTotal int
 				err := wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 2*time.Second, true,
 					func(ctx context.Context) (done bool, err error) {
-						pool, err := data.crdClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), ipPool.Name, metav1.GetOptions{})
+						pool, err := data.CRDClient.CrdV1beta1().ExternalIPPools().Get(context.TODO(), ipPool.Name, metav1.GetOptions{})
 						if err != nil {
 							return false, fmt.Errorf("failed to get ExternalIPPool: %v", err)
 						}
@@ -458,7 +458,7 @@ func testServiceWithExternalIPCRUD(t *testing.T, data *TestData) {
 func testServiceSharingLoadBalancerIP(t *testing.T, data *TestData) {
 	ctx := context.Background()
 	pool := data.createExternalIPPool(t, "pool-", v1beta1.IPRange{CIDR: "172.30.0.0/28"}, nil, nil, nil)
-	defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(ctx, pool.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(ctx, pool.Name, metav1.DeleteOptions{})
 
 	annotationNormal := map[string]string{
 		antreaagenttypes.ServiceExternalIPPoolAnnotationKey: pool.Name,
@@ -565,9 +565,9 @@ func testServiceUpdateExternalIP(t *testing.T, data *TestData) {
 			}
 
 			originalPool := data.createExternalIPPool(t, "originalpool-", tt.originalIPRange, nil, nil, map[string]string{v1.LabelHostname: tt.originalNode})
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), originalPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), originalPool.Name, metav1.DeleteOptions{})
 			newPool := data.createExternalIPPool(t, "newpool-", tt.newIPRange, nil, nil, map[string]string{v1.LabelHostname: tt.newNode})
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), newPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), newPool.Name, metav1.DeleteOptions{})
 
 			annotation := map[string]string{
 				antreaagenttypes.ServiceExternalIPPoolAnnotationKey: originalPool.Name,
@@ -650,7 +650,7 @@ func testServiceNodeFailure(t *testing.T, data *TestData) {
 				},
 			}
 			externalIPPoolTwoNodes := data.createExternalIPPool(t, "pool-with-two-nodes-", tt.ipRange, nil, matchExpressions, nil)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), externalIPPoolTwoNodes.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), externalIPPoolTwoNodes.Name, metav1.DeleteOptions{})
 			annotation := map[string]string{
 				antreaagenttypes.ServiceExternalIPPoolAnnotationKey: externalIPPoolTwoNodes.Name,
 			}
@@ -724,7 +724,7 @@ func testExternalIPAccess(t *testing.T, data *TestData) {
 			nodes := []string{nodeName(0), nodeName(1)}
 			ipRange := v1beta1.IPRange{CIDR: tt.externalIPCIDR}
 			ipPool := data.createExternalIPPool(t, "ippool-", ipRange, nil, nil, nil)
-			defer data.crdClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
+			defer data.CRDClient.CrdV1beta1().ExternalIPPools().Delete(context.TODO(), ipPool.Name, metav1.DeleteOptions{})
 			agnhosts := []string{"agnhost-0", "agnhost-1"}
 			// Create agnhost Pods on each Node.
 			for idx, node := range nodes {
diff --git a/test/e2e/supportbundle_test.go b/test/e2e/supportbundle_test.go
index bf8fb9009d4..068f17c59fb 100644
--- a/test/e2e/supportbundle_test.go
+++ b/test/e2e/supportbundle_test.go
@@ -308,9 +308,9 @@ func testSupportBundleCollection(
 			},
 		},
 	}
-	_, err = data.crdClient.CrdV1alpha1().SupportBundleCollections().Create(context.TODO(), sbc, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1alpha1().SupportBundleCollections().Create(context.TODO(), sbc, metav1.CreateOptions{})
 	require.NoError(t, err)
-	defer data.crdClient.CrdV1alpha1().SupportBundleCollections().Delete(context.TODO(), bundleName, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1alpha1().SupportBundleCollections().Delete(context.TODO(), bundleName, metav1.DeleteOptions{})
 	sbc, err = data.waitForSupportBundleCollectionCompleted(t, bundleName, 30*time.Second)
 	require.NoError(t, err)
 
@@ -347,7 +347,7 @@ func (data *TestData) waitForSupportBundleCollection(
 ) (*crdv1alpha1.SupportBundleCollection, error) {
 	var sbc *crdv1alpha1.SupportBundleCollection
 	if err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, timeout, false, func(ctx context.Context) (bool, error) {
-		c, err := data.crdClient.CrdV1alpha1().SupportBundleCollections().Get(ctx, name, metav1.GetOptions{})
+		c, err := data.CRDClient.CrdV1alpha1().SupportBundleCollections().Get(ctx, name, metav1.GetOptions{})
 		if err != nil {
 			return false, err
 		}
diff --git a/test/e2e/traceflow_test.go b/test/e2e/traceflow_test.go
index 84819419755..6d8148b8ce6 100644
--- a/test/e2e/traceflow_test.go
+++ b/test/e2e/traceflow_test.go
@@ -2140,7 +2140,7 @@ func testTraceflowEgress(t *testing.T, data *TestData) {
 	}
 
 	egress := data.createEgress(t, "egress-", matchExpressions, nil, "", egressIP, nil)
-	defer data.crdClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1beta1().Egresses().Delete(context.TODO(), egress.Name, metav1.DeleteOptions{})
 
 	testcaseLocalEgress := testcase{
 		name:      "egressFromLocalNode",
@@ -2212,9 +2212,9 @@ func testTraceflowEgress(t *testing.T, data *TestData) {
 				MatchLabels: map[string]string{"antrea-e2e": remotePodNames[0]},
 			},
 		}
-		_, err := data.crdClient.CrdV1beta1().Egresses().Update(context.TODO(), toUpdate, metav1.UpdateOptions{})
+		_, err := data.CRDClient.CrdV1beta1().Egresses().Update(context.TODO(), toUpdate, metav1.UpdateOptions{})
 		if err != nil && errors.IsConflict(err) {
-			toUpdate, _ = data.crdClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
+			toUpdate, _ = data.CRDClient.CrdV1beta1().Egresses().Get(context.TODO(), egress.Name, metav1.GetOptions{})
 		}
 		return err
 	})
@@ -2361,7 +2361,7 @@ func testTraceflowValidation(t *testing.T, data *TestData) {
 				Spec: tc.spec,
 			}
 			tf.Name = randName("")
-			_, err := data.crdClient.CrdV1beta1().Traceflows().Create(context.TODO(), tf, metav1.CreateOptions{})
+			_, err := data.CRDClient.CrdV1beta1().Traceflows().Create(context.TODO(), tf, metav1.CreateOptions{})
 			if tc.allowed {
 				assert.Nil(t, err)
 			} else {
@@ -2379,7 +2379,7 @@ func (data *TestData) waitForTraceflow(t *testing.T, name string, phase v1beta1.
 	var err error
 	timeout := 15 * time.Second
 	if err = wait.PollUntilContextTimeout(context.Background(), defaultInterval, timeout, true, func(ctx context.Context) (bool, error) {
-		tf, err = data.crdClient.CrdV1beta1().Traceflows().Get(context.TODO(), name, metav1.GetOptions{})
+		tf, err = data.CRDClient.CrdV1beta1().Traceflows().Get(context.TODO(), name, metav1.GetOptions{})
 		if err != nil || tf.Status.Phase != phase {
 			return false, nil
 		}
@@ -2460,7 +2460,7 @@ func (data *TestData) createANNPDenyIngress(key string, value string, name strin
 			Egress: []v1beta1.Rule{},
 		},
 	}
-	annpCreated, err := k8sUtils.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Create(context.TODO(), &annp, metav1.CreateOptions{})
+	annpCreated, err := k8sUtils.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Create(context.TODO(), &annp, metav1.CreateOptions{})
 	if err != nil {
 		return nil, err
 	}
@@ -2469,7 +2469,7 @@ func (data *TestData) createANNPDenyIngress(key string, value string, name strin
 
 // deleteAntreaNetworkpolicy deletes an Antrea NetworkPolicy.
 func (data *TestData) deleteAntreaNetworkpolicy(policy *v1beta1.NetworkPolicy) error {
-	if err := k8sUtils.crdClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policy.Name, metav1.DeleteOptions{}); err != nil {
+	if err := k8sUtils.CRDClient.CrdV1beta1().NetworkPolicies(data.testNamespace).Delete(context.TODO(), policy.Name, metav1.DeleteOptions{}); err != nil {
 		return fmt.Errorf("unable to cleanup policy %v: %v", policy.Name, err)
 	}
 	return nil
@@ -2540,11 +2540,11 @@ func runTestTraceflow(t *testing.T, data *TestData, tc testcase) {
 	if tc.skipIfNeeded != nil {
 		tc.skipIfNeeded(t)
 	}
-	if _, err := data.crdClient.CrdV1beta1().Traceflows().Create(context.TODO(), tc.tf, metav1.CreateOptions{}); err != nil {
+	if _, err := data.CRDClient.CrdV1beta1().Traceflows().Create(context.TODO(), tc.tf, metav1.CreateOptions{}); err != nil {
 		t.Fatalf("Error when creating traceflow: %v", err)
 	}
 	defer func() {
-		if err := data.crdClient.CrdV1beta1().Traceflows().Delete(context.TODO(), tc.tf.Name, metav1.DeleteOptions{}); err != nil {
+		if err := data.CRDClient.CrdV1beta1().Traceflows().Delete(context.TODO(), tc.tf.Name, metav1.DeleteOptions{}); err != nil {
 			t.Errorf("Error when deleting traceflow: %v", err)
 		}
 	}()
diff --git a/test/e2e/trafficcontrol_test.go b/test/e2e/trafficcontrol_test.go
index 81a69cef8a7..1333498c597 100644
--- a/test/e2e/trafficcontrol_test.go
+++ b/test/e2e/trafficcontrol_test.go
@@ -163,7 +163,7 @@ func (data *TestData) createTrafficControl(t *testing.T,
 		tc.Spec.ReturnPort = nil
 	}
 
-	tc, err := data.crdClient.CrdV1alpha2().TrafficControls().Create(context.TODO(), tc, metav1.CreateOptions{})
+	tc, err := data.CRDClient.CrdV1alpha2().TrafficControls().Create(context.TODO(), tc, metav1.CreateOptions{})
 	require.NoError(t, err, "Failed to create TrafficControl")
 	return tc
 }
@@ -231,7 +231,7 @@ ip link set %[3]s up`, vni, dstVXLANPort, tunnelPeer)
 	targetPort := &v1alpha2.UDPTunnel{RemoteIP: tcTestConfig.collectorPodIPs[corev1.IPv4Protocol], VNI: &vni, DestinationPort: &dstVXLANPort}
 
 	tc := data.createTrafficControl(t, "tc-", nil, tcTestConfig.podLabels, v1alpha2.DirectionBoth, v1alpha2.ActionMirror, targetPort, true, nil)
-	defer data.crdClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
 	// Wait flows of the TrafficControl to be realized.
 	time.Sleep(time.Second)
 
@@ -244,7 +244,7 @@ func testMirrorToLocal(t *testing.T, data *TestData) {
 	portName := "test-port"
 	targetPort := &v1alpha2.OVSInternalPort{Name: portName}
 	tc := data.createTrafficControl(t, "tc-", nil, tcTestConfig.podLabels, v1alpha2.DirectionBoth, v1alpha2.ActionMirror, targetPort, false, nil)
-	defer data.crdClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
 	// Wait flows of the TrafficControl to be realized.
 	time.Sleep(time.Second)
 
@@ -312,7 +312,7 @@ ip link set dev %[2]s up`, targetPortName, returnPortName)
 	returnPort := &v1alpha2.NetworkDevice{Name: returnPortName}
 
 	tc := data.createTrafficControl(t, "tc-", nil, tcTestConfig.podLabels, v1alpha2.DirectionBoth, v1alpha2.ActionRedirect, targetPort, false, returnPort)
-	defer data.crdClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1alpha2().TrafficControls().Delete(context.TODO(), tc.Name, metav1.DeleteOptions{})
 	// Wait flows of TrafficControl to be realized.
 	time.Sleep(time.Second)
 
diff --git a/test/e2e/vmagent_test.go b/test/e2e/vmagent_test.go
index a83181ed17d..c7f9571b97e 100644
--- a/test/e2e/vmagent_test.go
+++ b/test/e2e/vmagent_test.go
@@ -143,9 +143,9 @@ func testExternalNodeSupportBundleCollection(t *testing.T, data *TestData, vmLis
 			},
 		},
 	}
-	_, err = data.crdClient.CrdV1alpha1().SupportBundleCollections().Create(context.TODO(), sbc, metav1.CreateOptions{})
+	_, err = data.CRDClient.CrdV1alpha1().SupportBundleCollections().Create(context.TODO(), sbc, metav1.CreateOptions{})
 	require.NoError(t, err)
-	defer data.crdClient.CrdV1alpha1().SupportBundleCollections().Delete(context.TODO(), bundleName, metav1.DeleteOptions{})
+	defer data.CRDClient.CrdV1alpha1().SupportBundleCollections().Delete(context.TODO(), bundleName, metav1.DeleteOptions{})
 	failOnError(data.waitForSupportBundleCollectionRealized(t, bundleName, 30*time.Second), t)
 	pods, err := data.clientset.CoreV1().Pods(data.testNamespace).List(context.TODO(), metav1.ListOptions{LabelSelector: "app=sftp"})
 	require.NoError(t, err)
@@ -255,7 +255,7 @@ func teardownVMAgentTest(t *testing.T, data *TestData, vmList []vmInfo) {
 	}
 	t.Logf("TestVMAgent teardown")
 	for _, vm := range vmList {
-		err := data.crdClient.CrdV1alpha1().ExternalNodes(namespace).Delete(context.TODO(), vm.nodeName, metav1.DeleteOptions{})
+		err := data.CRDClient.CrdV1alpha1().ExternalNodes(namespace).Delete(context.TODO(), vm.nodeName, metav1.DeleteOptions{})
 		assert.NoError(t, err, "Failed to delete ExternalNode %s", vm.nodeName)
 		verifyExternalEntityExistence(t, data, vm.eeName, vm.nodeName, false)
 		verifyUpLinkAfterCleanup(vm)
@@ -265,7 +265,7 @@ func teardownVMAgentTest(t *testing.T, data *TestData, vmList []vmInfo) {
 func verifyExternalEntityExistence(t *testing.T, data *TestData, eeName string, vmNodeName string, expectExists bool) {
 	if err := wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 1*time.Minute, true, func(ctx context.Context) (done bool, err error) {
 		t.Logf("Verifying ExternalEntity %s, expectExists %t", eeName, expectExists)
-		_, err = data.crdClient.CrdV1alpha2().ExternalEntities(namespace).Get(context.TODO(), eeName, metav1.GetOptions{})
+		_, err = data.CRDClient.CrdV1alpha2().ExternalEntities(namespace).Get(context.TODO(), eeName, metav1.GetOptions{})
 		if err != nil && !errors.IsNotFound(err) {
 			t.Errorf("Failed to get ExternalEntity %s by ExternalNode %s: %v", eeName, vmNodeName, err)
 			return false, err
@@ -453,7 +453,7 @@ func createExternalNodeCRD(data *TestData, nodeName string, ifName string, ip st
 	testEn.AddInterface(ifName, ipList)
 	// Add labels on the VMs.
 	testEn.AddLabels(map[string]string{externalNodeLabelKey: nodeName})
-	return data.crdClient.CrdV1alpha1().ExternalNodes(namespace).Create(context.TODO(), testEn.Get(), metav1.CreateOptions{})
+	return data.CRDClient.CrdV1alpha1().ExternalNodes(namespace).Create(context.TODO(), testEn.Get(), metav1.CreateOptions{})
 }
 
 func testExternalNodeWithANP(t *testing.T, data *TestData, vmList []vmInfo) {


Reviews:
user: login: antoninbas
body: new tests are needed under `test/e2e-secondary-network` to validate this patch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-11T22:14:52Z
user: login: wenyingd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-12T03:47:52Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-17T06:35:46Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-17T06:36:32Z
user: login: rajnkamr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-23T06:54:05Z
user: login: rajnkamr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-23T07:00:17Z
user: login: wenyingd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-26T08:33:50Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-03T23:01:44Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-05T20:19:11Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-05T20:23:54Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-06T04:42:20Z
user: login: wenyingd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-06T08:07:56Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-06T20:57:01Z
user: login: wenyingd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-07T10:08:33Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-17T23:47:50Z
user: login: rajnkamr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-20T05:18:38Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-21T23:48:47Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-23T06:25:43Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-27T03:53:28Z
user: login: jianjuns
body: Could you also check if possible to add unit tests?

Check these tests for reference:
https://github.com/antrea-io/antrea/blob/main/pkg/agent/cniserver/server_linux_test.go#L613
https://github.com/antrea-io/antrea/blob/main/pkg/agent/agent_test.go#L66



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-28T23:12:43Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-07T22:10:44Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-10T10:05:55Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T01:19:07Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T05:33:04Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T05:39:13Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-15T01:23:05Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-18T05:32:31Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T21:33:12Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:24:33Z
user: login: rajnkamr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T09:29:38Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T09:36:45Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T11:14:38Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-21T08:58:16Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-21T18:37:36Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-22T01:02:29Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T10:11:50Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T10:13:16Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T10:24:06Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T10:24:21Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T10:24:34Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T19:02:24Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T08:29:45Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T18:57:45Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T01:22:19Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T18:42:26Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T18:23:33Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T05:40:18Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T10:10:16Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T10:16:35Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T10:24:41Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T10:48:42Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T19:54:56Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T20:34:17Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T20:55:21Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:22:22Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:06:54Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:28:32Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:49:31Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T18:40:19Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T18:42:36Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T04:38:55Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T04:39:18Z
user: login: jianjuns
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T05:35:39Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:03:55Z
user: login: jianjuns
body: LGTM. Let us see if @antoninbas has more comments.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T18:14:51Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:48:24Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T19:56:11Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T22:07:55Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T10:26:48Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T13:32:37Z
user: login: antoninbas
body: a couple of nits, otherwise LGTM

let's trigger the e2e test job for SR-IOV Secondary networks after you address these 2 comments
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:32:57Z
user: login: KMAnju-2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T05:03:14Z
user: login: antoninbas
body: LGTM, we can merge when we get passing Kind e2e tests and a passing e2e test for SRIOV
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T19:32:40Z

Review comments:
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
path: cmd/antrea-agent/agent.go
user: login: antoninbas
body: at this stage I don't think there is a guarantee that `o.config.SecondaryNetwork.OVSBridges` will not be empty?
created_at: 2024-12-11T21:50:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
+		ovsSecBridgeClient := ovsconfig.NewOVSBridge(bridgeConfig.BridgeName, ovsDatapathType, ovsdbConnection, ovsconfig.WithRequiredPortExternalIDs(interfacestore.AntreaInterfaceTypeKey))
+		secondaryBridge :=agent.NewSecondaryBridgeInitializer(ovsSecBridgeClient, ifaceStore, bridgeConfig.BridgeName)
+		err := secondaryBridge.InitializeInterfaceStore()
+		if err != nil {
+		  klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
path: cmd/antrea-agent/agent.go
user: login: antoninbas
body: the indentation is off here, it is "wrong" in several other places as well (you may need to adjust your IDE settings). By "wrong" I mean it doesn't match the rest of the code base
created_at: 2024-12-11T21:51:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
+		ovsSecBridgeClient := ovsconfig.NewOVSBridge(bridgeConfig.BridgeName, ovsDatapathType, ovsdbConnection, ovsconfig.WithRequiredPortExternalIDs(interfacestore.AntreaInterfaceTypeKey))
+		secondaryBridge :=agent.NewSecondaryBridgeInitializer(ovsSecBridgeClient, ifaceStore, bridgeConfig.BridgeName)
path: cmd/antrea-agent/agent.go
user: login: antoninbas
body: As far as I can tell there is already a different interface store for secondary network interfaces in the `PodController`: https://github.com/antrea-io/antrea/blob/325890854da4fe0d5168bc7f516f84d4ef85eb35/pkg/agent/secondarynetwork/podwatch/controller.go#L92

So it doesn't seem right to use the "primary" one here? It feels like the primary one will not be updated when a new secondary network interface is created for a Pod.

I would expect that the scenario described in https://github.com/antrea-io/antrea/issues/6578 would still fail  even with this patch, as when deleting the Pod we would not find the interface in the "correct" interface store?

Maybe @jianjuns can comment as to why we have a separate interface store for secondary network interfaces, but it makes sense to me.
created_at: 2024-12-11T22:00:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -468,15 +468,15 @@ func (pc *podConfigurator) reconcile(pods []corev1.Pod, containerAccess *contain
 				continue
 			}
 			podWg.Add(1)
-			go func(containerID, pod, namespace string) {
+			go func(containerID, iface, pod, namespace string) {
path: pkg/agent/cniserver/pod_configuration.go
user: login: antoninbas
body: you probably can undo changes to this file if you use the correct interface store?
created_at: 2024-12-11T22:08:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -274,6 +292,47 @@ func (i *Initializer) validateSupportedDPFeatures() error {
 	return nil
 }
 
+func (s *SecondaryBridgeInitializer) InitializeInterfaceStore() error {
path: pkg/agent/agent.go
user: login: antoninbas
body: maybe we could do this initialization in `pkg/agent/secondarynetwork`, and avoid the extra hook in `cmd/antrea-agent`?
created_at: 2024-12-11T22:13:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -274,6 +292,47 @@ func (i *Initializer) validateSupportedDPFeatures() error {
 	return nil
 }
 
+func (s *SecondaryBridgeInitializer) InitializeInterfaceStore() error {
path: pkg/agent/agent.go
user: login: wenyingd
body: Agree with @antoninbas , it is more reasonable to move the dedicated initialization to the feature itself.
created_at: 2024-12-12T03:25:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
path: cmd/antrea-agent/agent.go
user: login: wenyingd
body: According to the original design for agent, we should firstly initialize the agent, then each feature can start including its own initialize functions.
created_at: 2024-12-12T03:28:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
+		ovsSecBridgeClient := ovsconfig.NewOVSBridge(bridgeConfig.BridgeName, ovsDatapathType, ovsdbConnection, ovsconfig.WithRequiredPortExternalIDs(interfacestore.AntreaInterfaceTypeKey))
path: cmd/antrea-agent/agent.go
user: login: wenyingd
body: Would it cause issues if the user uses a single NIC for both primary and secondary network interface? I remember we used to handles some issues with this scenario.
created_at: 2024-12-12T03:30:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
+		ovsSecBridgeClient := ovsconfig.NewOVSBridge(bridgeConfig.BridgeName, ovsDatapathType, ovsdbConnection, ovsconfig.WithRequiredPortExternalIDs(interfacestore.AntreaInterfaceTypeKey))
+		secondaryBridge :=agent.NewSecondaryBridgeInitializer(ovsSecBridgeClient, ifaceStore, bridgeConfig.BridgeName)
path: cmd/antrea-agent/agent.go
user: login: wenyingd
body: The same as the OVS bridgeClient, a new `ovsBridgeClient` is created in function secondarynetwork.NewController(). it would be better to move the initialize functions to `secondarynetwok.Controller` rather than introducing a new initializer object?
created_at: 2024-12-12T03:45:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
path: cmd/antrea-agent/agent.go
user: login: KMAnju-2021
body: I verified that it wasn't empty when agent restart, will move code in secondary controller.go file
created_at: 2024-12-17T06:35:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,6 +294,16 @@ func run(o *Options) error {
 		NodePortAddressesIPv6: nodePortAddressesIPv6,
 	}
 
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
+		bridgeConfig := o.config.SecondaryNetwork.OVSBridges[0]
+		ovsSecBridgeClient := ovsconfig.NewOVSBridge(bridgeConfig.BridgeName, ovsDatapathType, ovsdbConnection, ovsconfig.WithRequiredPortExternalIDs(interfacestore.AntreaInterfaceTypeKey))
+		secondaryBridge :=agent.NewSecondaryBridgeInitializer(ovsSecBridgeClient, ifaceStore, bridgeConfig.BridgeName)
path: cmd/antrea-agent/agent.go
user: login: KMAnju-2021
body: Sure, I will update the code.
created_at: 2024-12-17T06:36:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      Config.PodName,
+			PodNamespace: Config.PodNamespace,
+			ContainerID:  Config.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+
+	}
+}
+
+func InitializeInterfaceStore(ovsClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) error {
+	ovsPorts, err := ovsClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: rajnkamr
body: ```suggestion
		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", ovsClient.GetBridgeName())

```
created_at: 2024-12-23T06:54:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      Config.PodName,
+			PodNamespace: Config.PodNamespace,
+			ContainerID:  Config.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+
+	}
+}
+
+func InitializeInterfaceStore(ovsClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) error {
+	ovsPorts, err := ovsClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge")
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: rajnkamr
body: ```suggestion
			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType, "interfaceName", port.Name)
```
created_at: 2024-12-23T07:00:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +138,9 @@ func NewPodController(
 		// Subscribe Pod CNI add/del events.
 		podUpdateSubscriber.Subscribe(pc.processCNIUpdate)
 	}
+
+	pc.RestoreSecondaryNetwork(ovsBridgeClient, ifaceStore)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: what about calling this function after the local cache is synced and before running the workers in goroutines https://github.com/antrea-io/antrea/blob/main/pkg/agent/secondarynetwork/podwatch/controller.go#L488  ? Then you can have check on the errors occurred in function `RestoreSecondaryNetwork` ?
created_at: 2024-12-26T08:22:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      Config.PodName,
+			PodNamespace: Config.PodNamespace,
+			ContainerID:  Config.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+
+	}
+}
+
+func InitializeInterfaceStore(ovsClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) error {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: Why not use PodController's signature? e.g.,
```
func (pc *PodController) initializeInterfaceStore() error{
...
}
```

Then you can access `pc.interfaceStore` and `pc.ovsBridgeClient` inside the function directly.
created_at: 2024-12-26T08:24:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: Is this as desired to re-send the PodUpdate event after agent restart?
created_at: 2024-12-26T08:28:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: I am a bit confused here: `knownInterfaces` is read from interface store, and you are loop with `knownInterfaces`, then in what case `exists` would be false since you still check it in the interfacestore?

I guess here you should compare the `knownInterfaces` with the Pods existing in the kube-apiserver, and remove the stale ports from both OVSDB and interfacestore?
created_at: 2024-12-26T08:33:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -291,6 +291,9 @@ func (pc *PodController) syncPod(key string) error {
 			if err := pc.removeInterfaces(storedInterfaces); err != nil {
 				return err
 			}
+	
+		} else {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Why make this change?
created_at: 2025-01-03T22:37:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Humm.. I think we should re-construct cniCache using the information in the primary interface store (after its restoration), and then post a deletion event for each Pod in the secondary interface store, but not in cniCache / the primary interface store.
@wenyingd : thoughts?

created_at: 2025-01-03T22:40:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +508,66 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore interfacedstore and cniCache when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork() error {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Name it "restoreSecondaryInterfaces" or "reconcileSecondaryInterfaces" given it can delete stale interfaces/OVS ports.
created_at: 2025-01-03T22:41:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +508,66 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore interfacedstore and cniCache when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork() error {
+	err := pc.InitializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	return nil
+}
+
+func (pc *PodController) InitializeInterfaceStore() error {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: initializeInterfaceStore
created_at: 2025-01-03T22:41:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: updated the code
created_at: 2025-01-05T20:19:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -291,6 +291,9 @@ func (pc *PodController) syncPod(key string) error {
 			if err := pc.removeInterfaces(storedInterfaces); err != nil {
 				return err
 			}
+	
+		} else {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: trying to prevent calling "return pc.handleAddUpdatePod(pod, cniInfo, storedInterfaces)" in restore logic when agent restart.
created_at: 2025-01-05T20:23:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: @jianjuns you mean to re-construct cniCache, post add event using primary interfacestore in https://github.com/antrea-io/antrea/blob/main/pkg/agent/cniserver/pod_configuration.go#L443, post delete event for each Pod in the secondary interfacestore( after its restoration) in reconcileSecondaryInterfaces() function ?
created_at: 2025-01-06T04:42:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: > Humm.. I think we should re-construct cniCache using the information in the primary interface store (after its restoration), and then post a deletion event for each Pod in the secondary interface store, but not in cniCache / the primary interface store. @wenyingd : thoughts?

Honestly, it is not easier to sync between the primary interface store and the secondary interface store, since the two interface store are independent, and mapping to different OVS bridges (OVSDB configurations). 

If my understanding is correct, we have also configured the Pod's Namespace and Name, and the container ID in the secondary OVSDB, so we should already loaded the necessary configurations from the secondary network interface store restore logic after agent restart. Then we can use these OVSDB configurations to restore the cniCache and make diffs with the latest Pods from apiserver to remove the stale data, in this way we can remove t the dependency from the primary interface store, and don't need to re-send the PodUpdate event in restore stage. @KMAnju-2021 can help confirm and correct me.
created_at: 2025-01-06T08:07:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: In the normal workflow, cniCache is built with updates of the primary interfaces, not from the secondary interfaces. Esp. for the ContainerID field, the values saved in the secondary OVSDB may be stale. From the primary InterfaceStore, could we gurrantee the restored ContainerIDs are correct after reconciliation? @wenyingd 
created_at: 2025-01-06T20:57:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +505,69 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// RestoreSecondaryNetwork restore cniCache and pod interface when agent restart.
+func (pc *PodController) RestoreSecondaryNetwork(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) {
+	err := InitializeInterfaceStore(ovsBridgeClient, ifaceStore)
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	knownInterfaces := ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		Config, exists := ifaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists {
+			klog.InfoS("The container interface has been deleted ", "container id", Config.ContainerID)
+			return
+		}
+		event := types.PodUpdate{
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: wenyingd
body: > From the primary InterfaceStore, could we gurrantee the restored ContainerIDs are correct after reconciliation?

No. For the reconcile logic of the primary interface store, it **does not** cover the case that Pod's Namespaced name exists in the apiserver, but the sandbox container ID has updated during antrea's down time. We just load the known interfaces from OVSDB which were written when receiving CmdAdd call from kubelet, and check if the referred Pod still exists in apiserver. If no, remove the interfaces from OVS.

I can't think of the workflow to produce the case for the primary interface restore logic that Pod's Namespaced name still exists but the sandbox container ID has changed during agent downtime. For CNI case, the single source of the truth is the cmdAdd event sent from kubelet, For the case that a Pod with the same name has modified the sandbox container, it should be a Pod's restart. But if agent is down, kubelet should not successfully send the CmdAdd call. So cniserver should have no chance to get the new containerID from the existing logic.

created_at: 2025-01-07T10:08:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,6 +49,7 @@ func NewController(
 	podInformer cache.SharedIndexInformer,
 	podUpdateSubscriber channel.Subscriber,
 	secNetConfig *agentconfig.SecondaryNetworkConfig, ovsdb *ovsdb.OVSDB,
+	pIfacestore interfacestore.InterfaceStore,
path: pkg/agent/secondarynetwork/init.go
user: login: jianjuns
body: Nit: pIfacestore -> pIfaceStore
Probably better to put the argument after `podUpdateSubscriber` and before `secNetConfig`
created_at: 2025-01-17T23:07:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -766,7 +766,7 @@ func run(o *Options) error {
 			o.config.ClientConnection, o.config.KubeAPIServerOverride,
 			k8sClient, localPodInformer.Get(),
 			podUpdateChannel,
-			&o.config.SecondaryNetwork, ovsdbConnection)
+			&o.config.SecondaryNetwork, ovsdbConnection, ifaceStore)
path: cmd/antrea-agent/agent.go
user: login: jianjuns
body: It would be safer to initialize secondary interface store and subscribe to the podUpdateChannel before starting the primary CNI Servers (CNIServer.Run()), so no CNI update would be missed. Could you check if possible to move secondary network init to be before CNIServer.Run()?
created_at: 2025-01-17T23:12:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Maybe better to move this call to NewPodController(), so it initializes the interface store first, and then calls reconcileSecondaryInterfaces to reconcile interfaces in the store?
created_at: 2025-01-17T23:17:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
+		_, exists := pIfacestore.GetContainerInterface(containerId)
+		if !exists {
+			pc.interfaceStore.DeleteInterface(containerConfig)
+		}
+	}
+	return nil
+}
+
+func (pc *PodController) initializeInterfaceStore() error {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Is it possible to share the code with initInterfaceStore in agent.go? We may move the common code to the `interfacestore` package if needed.
created_at: 2025-01-17T23:35:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: I do not think this is necessary. Why not just construct cniCache directly here?
created_at: 2025-01-17T23:39:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: containerId -> containerID
created_at: 2025-01-17T23:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
+		_, exists := pIfacestore.GetContainerInterface(containerId)
+		if !exists {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Let us also check OFPort ID like https://github.com/antrea-io/antrea/pull/6919/files#diff-45b3b65cc709f5e30232327df9fe18defe28142e6da18a5df6e62702af5ba0ccR107.
created_at: 2025-01-17T23:40:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
+		_, exists := pIfacestore.GetContainerInterface(containerId)
+		if !exists {
+			pc.interfaceStore.DeleteInterface(containerConfig)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: You should delete the OVS port and release the IP, etc. Check `removeInterfaces` (consider adding a shared removeInterface() for the common code of deleting a single interface).
created_at: 2025-01-17T23:47:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
+		_, exists := pIfacestore.GetContainerInterface(containerId)
+		if !exists {
+			pc.interfaceStore.DeleteInterface(containerConfig)
+		}
+	}
+	return nil
+}
+
+func (pc *PodController) initializeInterfaceStore() error {
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: rajnkamr
body: ./pkg/agent/agent.go has got similar function like this which initializes InterfaceStore with all OVS ports retrieved, Either we can reuse the function or rename this function to initializeSecondaryInterfaceStore ! Additionally for each new utility function , prefer to add a comment liner before function body.

created_at: 2025-01-20T05:18:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,16 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: We should return the error and fail the agent startup.
created_at: 2025-01-21T23:19:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,16 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	if err := pc.reconcileSecondaryInterfaces(pIfaceStore); err != nil {
+		klog.ErrorS(err, "Failed to restore the cniCache")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: We should return the error and fail the agent startup.
created_at: 2025-01-21T23:19:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,16 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+	}
+
+	if err := pc.reconcileSecondaryInterfaces(pIfaceStore); err != nil {
+		klog.ErrorS(err, "Failed to restore the cniCache")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: restore the cniCache -> restore CNI cache and reconcile secondary interfaces
created_at: 2025-01-21T23:19:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,16 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: secondary bridge interface store -> secondary interface store
created_at: 2025-01-21T23:20:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -288,9 +317,11 @@ func (pc *PodController) syncPod(key string) error {
 			// not deleted yet. First delete them before processing the new Pod's
 			// secondary networks.
 			storedInterfaces[0].ContainerID != cniInfo.containerID {
-			if err := pc.removeInterfaces(storedInterfaces); err != nil {
+			if err := pc.removeInterfaces(storedInterfaces, nil); err != nil {
 				return err
 			}
+		} else {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: This change can be reverted?
created_at: 2025-01-21T23:20:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,40 +234,58 @@ func (pc *PodController) handleAddUpdatePod(pod *corev1.Pod, podCNIInfo *podCNII
 	return pc.configurePodSecondaryNetwork(pod, networklist, podCNIInfo)
 }
 
-func (pc *PodController) removeInterfaces(interfaces []*interfacestore.InterfaceConfig) error {
-	var savedErr error
-	for _, interfaceConfig := range interfaces {
-		podName := interfaceConfig.PodName
-		podNamespace := interfaceConfig.PodNamespace
-		klog.V(1).InfoS("Deleting secondary interface",
-			"Pod", klog.KRef(podNamespace, podName), "interface", interfaceConfig.IFDev)
+// deleteInterfaceAndReleaseResources to delete interface and release resources
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: to delete interface and release -> deletes a secondary interface and releases
created_at: 2025-01-21T23:23:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +533,75 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
+	}
+
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache and delete ports not in the CNI cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerID := containerConfig.ContainerID
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Seems not necessary to add a new temporary variable?
created_at: 2025-01-21T23:27:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +533,75 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
+	}
+
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache and delete ports not in the CNI cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerID := containerConfig.ContainerID
+		_, exists := pIfaceStore.GetContainerInterface(containerID)
+		if !exists || containerConfig.OFPort == -1 {
+			pc.interfaceStore.DeleteInterface(containerConfig)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: removeInterfaces() will try to get the interface from the store (and will delete the interface from store)? 
created_at: 2025-01-21T23:28:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +533,75 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
+	}
+
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache and delete ports not in the CNI cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerID := containerConfig.ContainerID
+		_, exists := pIfaceStore.GetContainerInterface(containerID)
+		if !exists || containerConfig.OFPort == -1 {
+			pc.interfaceStore.DeleteInterface(containerConfig)
+			err := pc.removeInterfaces(secondaryInterfaces, containerConfig)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Why not add all interfaces to be deleted to a list, and call removeInterfaces() to remove them together? Then no need to change removeInterfaces()
created_at: 2025-01-21T23:28:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -680,6 +680,18 @@ func run(o *Options) error {
 		return err
 	}
 
+	var secondaryNetworkController *secondarynetwork.Controller
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
path: cmd/antrea-agent/agent.go
user: login: jianjuns
body: How about moving this block to line 629 to be closer to CNIServer init? Please also add a comment saying secondary network Controller should be created before CNIServer.Run() to make sure no Pod CNI updates will be missed.
created_at: 2025-01-21T23:47:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +510,74 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// reconcileSecondaryInterfaces restore interfacestore and cniCache when agent restart.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfacestore interfacestore.InterfaceStore) error {
+	err := pc.initializeInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary bridge interface store")
+		return err
+	}
+
+	knownInterfaces := pIfacestore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		event := types.PodUpdate{
+			IsAdd:        true,
+			PodName:      containerConfig.ContainerInterfaceConfig.PodName,
+			PodNamespace: containerConfig.ContainerInterfaceConfig.PodNamespace,
+			ContainerID:  containerConfig.ContainerInterfaceConfig.ContainerID,
+		}
+		pc.processCNIUpdate(event)
+	}
+
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		containerId := containerConfig.ContainerID
+		_, exists := pIfacestore.GetContainerInterface(containerId)
+		if !exists {
+			pc.interfaceStore.DeleteInterface(containerConfig)
+		}
+	}
+	return nil
+}
+
+func (pc *PodController) initializeInterfaceStore() error {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: we are restoring secondary interface store  so it's better if we can rename  and keep it in secondarynetwork pkg.
created_at: 2025-01-23T06:25:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -680,6 +680,18 @@ func run(o *Options) error {
 		return err
 	}
 
+	var secondaryNetworkController *secondarynetwork.Controller
+	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
path: cmd/antrea-agent/agent.go
user: login: KMAnju-2021
body: done
created_at: 2025-01-27T03:53:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,18 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary interface store")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Typically we do not log if the returned error will be logged in the caller; instead we can append extra context to the returned error like: `return fmt.Errorf("failed to initialize secondary interface store: %v", err)`

created_at: 2025-01-28T22:45:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,18 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	if err != nil {
+		klog.ErrorS(err, "Failed to initialize the secondary interface store")
+		return &pc, err
+	}
+
+	if err := pc.reconcileSecondaryInterfaces(pIfaceStore); err != nil {
+		klog.ErrorS(err, "Failed to restore CNI cache and reconcile secondary interfaces")
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Ditto
created_at: 2025-01-28T22:48:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: restore -> restores

interfacestore -> "interface store" or "InterfaceStore"

restart -> restarts
created_at: 2025-01-28T22:48:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Again, do we need to log here?
created_at: 2025-01-28T22:52:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: restores

restarts

InterfaceStore
created_at: 2025-01-28T22:53:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Why we queue the task? I feel it is not needed.
created_at: 2025-01-28T22:55:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
+	}
+
+	var staleInterfaces []*interfacestore.InterfaceConfig
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache and delete ports not in the CNI cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		_, exists := pIfaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists || containerConfig.OFPort == -1 {
+			staleInterfaces = append(staleInterfaces, containerConfig)
+		}
+	}
+
+	// If there are any stale interfaces, pass them to removeInterfaces()
+	if len(staleInterfaces) > 0 {
+		if err := pc.removeInterfaces(staleInterfaces); err != nil {
+			return fmt.Errorf("failed to remove stale interfaces: %w", err)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: I feel we should just log the error, but no need to return it and fail the agent start.

Check https://github.com/antrea-io/antrea/blob/main/pkg/agent/cniserver/pod_configuration.go#L472
created_at: 2025-01-28T22:59:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +515,82 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restore secondary interfacestore when agent restart.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		klog.ErrorS(err, "Failed to list OVS ports for the secondary bridge", "bridgeName", pc.ovsBridgeClient.GetBridgeName())
+		return err
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restore cniCache when agent restart using primary interfacestore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+		pc.queue.Add(podKey)
+	}
+
+	var staleInterfaces []*interfacestore.InterfaceConfig
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache and delete ports not in the CNI cache.
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: I feel this sentence needs to be revised.

Probably make it:
`// secondaryInterfaces is the list of interfaces currently in the secondary local cache.`

And move `// Deletes ports not in the CNI cache.` to be before line 584?

created_at: 2025-01-28T23:05:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -646,6 +646,19 @@ func run(o *Options) error {
 		}
 	}
 
+	// Secondary network Controller should be created before CNIServer.Run() to make sure no Pod CNI updates will be missed.
path: cmd/antrea-agent/agent.go
user: login: jianjuns
body: Controller -> controller
created_at: 2025-01-28T23:08:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %v", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	if pIfaceStore == nil {
+		klog.InfoS("Primary interfaceStore is nil, skipping reconciliation for Secondary Network")
+		return nil
+	}
+
+	knownInterfaces := pIfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range knownInterfaces {
+		config := containerConfig.ContainerInterfaceConfig
+		podKey := podKeyGet(config.PodName, config.PodNamespace)
+		pc.cniCache.Store(podKey, &podCNIInfo{
+			containerID: config.ContainerID,
+		})
+	}
+
+	var staleInterfaces []*interfacestore.InterfaceConfig
+	// secondaryInterfaces is the list of interfaces currently in the secondary local cache.
+	secondaryInterfaces := pc.interfaceStore.GetInterfacesByType(interfacestore.ContainerInterface)
+	for _, containerConfig := range secondaryInterfaces {
+		_, exists := pIfaceStore.GetContainerInterface(containerConfig.ContainerID)
+		if !exists || containerConfig.OFPort == -1 {
+			// Deletes ports not in the CNI cache.
+			staleInterfaces = append(staleInterfaces, containerConfig)
+		}
+	}
+
+	// If there are any stale interfaces, pass them to removeInterfaces()
+	if len(staleInterfaces) > 0 {
+		if err := pc.removeInterfaces(staleInterfaces); err != nil {
+			klog.ErrorS(err, "Failed to remove stale interfaces", "staleInterfaces", staleInterfaces)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Nit: stale interfaces -> stale secondary interfaces
created_at: 2025-02-07T22:07:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +237,18 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testRetstartAntreaAgentPods", func(t *testing.T) {
+		if err := e2eTestData.RestartAntreaAgentPods(defaultTimeout); err != nil {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Could we remove some Pods after agent stops and check stale OVS ports are removed?
created_at: 2025-02-07T22:08:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +237,18 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testRetstartAntreaAgentPods", func(t *testing.T) {
+		if err := e2eTestData.RestartAntreaAgentPods(defaultTimeout); err != nil {
+			t.Fatalf("Failed to restart Antrea agent pods within timeout %v: %v", defaultTimeout, err)
+		}
+	})
+	// Again verify interfaces restoration and connnectivity
+	t.Run("testpingBetweenInterfaces", func(t *testing.T) {
+		err := testData.pingBetweenInterfaces(t)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Could we also check secondary OVS ports can be removed after Pods are removed? Can be better if we can check IPs can be released too.
created_at: 2025-02-07T22:10:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +237,18 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testRetstartAntreaAgentPods", func(t *testing.T) {
+		if err := e2eTestData.RestartAntreaAgentPods(defaultTimeout); err != nil {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: sure
created_at: 2025-02-10T10:05:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +219,124 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) GetOVSPorts(targetPod *testPodInfo) ([]string, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: All these funcs need not to be public?
created_at: 2025-02-12T01:09:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +219,124 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) GetOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// MatchOVSPorts ensures that all ports in afterOVSPorts exist in beforeOVSPorts.
+func (data *testData) CheckOVSPorts(beforeOVSPorts, afterOVSPorts []string) error {
+	portSet := make(map[string]struct{})
+	for _, port := range beforeOVSPorts {
+		portSet[port] = struct{}{}
+	}
+
+	for _, port := range afterOVSPorts {
+		if _, exists := portSet[port]; !exists {
+			return fmt.Errorf("OVS port %s is missing after restart", port)
+		}
+	}
+
+	return nil
+}
+
+// CheckOVSPortsAfterAgentRestart verifies OVS ports before and after restarting Antrea agent pods.
+func (data *testData) CheckOVSPortsAfterAgentRestart(e2eTestData *antreae2e.TestData, t *testing.T) error {
+	var beforeRestartOVSPorts []string
+	for _, pod := range data.pods {
+		ports, err := data.GetOVSPorts(pod)
+		if err != nil {
+			t.Fatalf("Error getting OVS ports before restart: %v", err)
+		}
+		beforeRestartOVSPorts = append(beforeRestartOVSPorts, ports...)
+	}
+
+	if err := e2eTestData.RestartAntreaAgentPods(90 * time.Second); err != nil {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I meant could we remove some Pods after Agent stops, and then check the secondary OVS ports are removed after Agent restarts.
created_at: 2025-02-12T01:11:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +219,124 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) GetOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// MatchOVSPorts ensures that all ports in afterOVSPorts exist in beforeOVSPorts.
+func (data *testData) CheckOVSPorts(beforeOVSPorts, afterOVSPorts []string) error {
+	portSet := make(map[string]struct{})
+	for _, port := range beforeOVSPorts {
+		portSet[port] = struct{}{}
+	}
+
+	for _, port := range afterOVSPorts {
+		if _, exists := portSet[port]; !exists {
+			return fmt.Errorf("OVS port %s is missing after restart", port)
+		}
+	}
+
+	return nil
+}
+
+// CheckOVSPortsAfterAgentRestart verifies OVS ports before and after restarting Antrea agent pods.
+func (data *testData) CheckOVSPortsAfterAgentRestart(e2eTestData *antreae2e.TestData, t *testing.T) error {
+	var beforeRestartOVSPorts []string
+	for _, pod := range data.pods {
+		ports, err := data.GetOVSPorts(pod)
+		if err != nil {
+			t.Fatalf("Error getting OVS ports before restart: %v", err)
+		}
+		beforeRestartOVSPorts = append(beforeRestartOVSPorts, ports...)
+	}
+
+	if err := e2eTestData.RestartAntreaAgentPods(90 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	var afterRestartOVSPorts []string
+	for _, pod := range data.pods {
+		ports, err := data.GetOVSPorts(pod)
+		if err != nil {
+			t.Fatalf("Error getting OVS ports after restart: %v", err)
+		}
+		afterRestartOVSPorts = append(afterRestartOVSPorts, ports...)
+	}
+
+	logs.Infof("Before Restart OVS Ports: %v", beforeRestartOVSPorts)
+	logs.Infof("After Restart OVS Ports: %v", afterRestartOVSPorts)
+
+	if err := data.CheckOVSPorts(beforeRestartOVSPorts, afterRestartOVSPorts); err != nil {
+		t.Fatalf("Mismatch in OVS ports before and after restart: %v", err)
+	}
+	return nil
+}
+
+// RemovePodsAndCheckOVSPorts verifies that OVS ports are removed when pods are deleted.
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: pods -> Pods

Please change other occurrences too.
created_at: 2025-02-12T01:12:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +219,124 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) GetOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// MatchOVSPorts ensures that all ports in afterOVSPorts exist in beforeOVSPorts.
+func (data *testData) CheckOVSPorts(beforeOVSPorts, afterOVSPorts []string) error {
+	portSet := make(map[string]struct{})
+	for _, port := range beforeOVSPorts {
+		portSet[port] = struct{}{}
+	}
+
+	for _, port := range afterOVSPorts {
+		if _, exists := portSet[port]; !exists {
+			return fmt.Errorf("OVS port %s is missing after restart", port)
+		}
+	}
+
+	return nil
+}
+
+// CheckOVSPortsAfterAgentRestart verifies OVS ports before and after restarting Antrea agent pods.
+func (data *testData) CheckOVSPortsAfterAgentRestart(e2eTestData *antreae2e.TestData, t *testing.T) error {
+	var beforeRestartOVSPorts []string
+	for _, pod := range data.pods {
+		ports, err := data.GetOVSPorts(pod)
+		if err != nil {
+			t.Fatalf("Error getting OVS ports before restart: %v", err)
+		}
+		beforeRestartOVSPorts = append(beforeRestartOVSPorts, ports...)
+	}
+
+	if err := e2eTestData.RestartAntreaAgentPods(90 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	var afterRestartOVSPorts []string
+	for _, pod := range data.pods {
+		ports, err := data.GetOVSPorts(pod)
+		if err != nil {
+			t.Fatalf("Error getting OVS ports after restart: %v", err)
+		}
+		afterRestartOVSPorts = append(afterRestartOVSPorts, ports...)
+	}
+
+	logs.Infof("Before Restart OVS Ports: %v", beforeRestartOVSPorts)
+	logs.Infof("After Restart OVS Ports: %v", afterRestartOVSPorts)
+
+	if err := data.CheckOVSPorts(beforeRestartOVSPorts, afterRestartOVSPorts); err != nil {
+		t.Fatalf("Mismatch in OVS ports before and after restart: %v", err)
+	}
+	return nil
+}
+
+// RemovePodsAndCheckOVSPorts verifies that OVS ports are removed when pods are deleted.
+func (data *testData) RemovePodsAndCheckOVSPorts(pods []*testPodInfo, waitTime time.Duration) error {
+	pod := data.pods[0] // Select first pod for removal
+	beforeRemovePorts, err := data.GetOVSPorts(pod)
+	if err != nil {
+		return fmt.Errorf("error getting OVS ports before pod removal: %v", err)
+	}
+	logs.Infof("Before Removal - Pod: %s, OVS Ports: %v", pod.podName, beforeRemovePorts)
+
+	// Delete selected pods
+	if err := data.e2eTestData.DeletePodAndWait(waitTime, pod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", pod.podName, err)
+	}
+
+	// Ensure pods are actually deleted
+	exists, _ := data.CheckPodExists(pod.podName, data.e2eTestData.GetTestNamespace())
+	if exists {
+		return fmt.Errorf("pod %s still exists after deletion", pod.podName)
+	}
+
+	afterRemovePorts, err := data.GetOVSPorts(pod)
+	if err == nil {
+		return fmt.Errorf("error getting OVS ports after pod removal: %v", err)
+	}
+	logs.Infof("After Removal - Pod: %s, OVS Ports: %v", pod.podName, afterRemovePorts)
+
+	// Ensure no stale OVS ports remain
+	if err := data.CheckOVSPorts(beforeRemovePorts, afterRemovePorts); err != nil {
+		logs.Infof("OVS ports has been removed after pod deletion: %v", afterRemovePorts)
+	}
+
+	// Verify that the deleted pod's IPs are released
+	podIPs, err := data.listPodIPs(pod)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I meant check secondary interface IPs are released to Antrea IPPool, for removed Pods when Agent stopped.
I am fine not to include it to the test case, if it is complex to implement (probably not that hard to get the state from IPPool CR).
created_at: 2025-02-12T01:19:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +230,141 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseDeployment pauses a Kubernetes deployment using kubectl
+func (data *testData) pauseAndResumeDeployment(namespace, deploymentName string, state string) error {
+	cmd := exec.Command("kubectl", "rollout", state, "deployment", deploymentName, "-n", namespace)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to pause deployment: %v, output: %s", err, string(output))
+	}
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// CheckOVSPortExistence verifies if an OVS port still exists.
+func (data *testData) CheckOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// CheckIPReleased verifies if the VLAN IP from the deleted pod is released.
+func (data *testData) checkIPReleased(vlanIP string) error {
+	ipPoolCmd := []string{"kubectl", "get", "ippool", "-o", "jsonpath={.status.allocatedIPs}"}
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I feel it is simpler/better to check the IPPool CR status directly via API. You can check example code in antreaipam_test.go. Sorry, I thought about sending you the pointer earlier but forgot it.
created_at: 2025-02-13T05:33:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +230,141 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseDeployment pauses a Kubernetes deployment using kubectl
+func (data *testData) pauseAndResumeDeployment(namespace, deploymentName string, state string) error {
+	cmd := exec.Command("kubectl", "rollout", state, "deployment", deploymentName, "-n", namespace)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to pause deployment: %v, output: %s", err, string(output))
+	}
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// CheckOVSPortExistence verifies if an OVS port still exists.
+func (data *testData) CheckOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// CheckIPReleased verifies if the VLAN IP from the deleted pod is released.
+func (data *testData) checkIPReleased(vlanIP string) error {
+	ipPoolCmd := []string{"kubectl", "get", "ippool", "-o", "jsonpath={.status.allocatedIPs}"}
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: Sure , refactoring the code, thanks!
created_at: 2025-02-13T05:39:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Nit: pod -> Pod

Could you change other occurances too?
created_at: 2025-02-15T01:09:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Pod
created_at: 2025-02-15T01:10:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
+	}
+
+	var networkStatus []NetworkStatus
+	if err := json.Unmarshal(output, &networkStatus); err != nil {
+		return "", fmt.Errorf("failed to parse network status: %v", err)
+	}
+
+	// Extract the secondary network IP
+	for _, net := range networkStatus {
+		for _, iface := range net.Interfaces {
+			if iface.Name != "eth0" { // secondary interfaces are not eth0
+				return iface.IPs[0], nil // Return the first IP found
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no secondary IP found for pod %s", podName)
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) testPauseAgentDeletePodRestartAgent(vlanPod *testPodInfo, waitTime time.Duration) error {
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getOVSPorts(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+	logs.Infof("Fetching OVS ports for pod %s before deletion", beforePorts)
+
+	ipPoolNmae, err := data.getIPPoolFromPod(vlanPod.podName, data.e2eTestData.GetTestNamespace())
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
+	} else {
+		fmt.Println("IPPool name:", ipPoolNmae)
+	}
+
+	vlanIP, err := data.getPodSecondaryIP("default", vlanPod.podName)
+	if err != nil {
+		fmt.Println("Error:", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Should we return the error?
created_at: 2025-02-15T01:15:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
+	}
+
+	var networkStatus []NetworkStatus
+	if err := json.Unmarshal(output, &networkStatus); err != nil {
+		return "", fmt.Errorf("failed to parse network status: %v", err)
+	}
+
+	// Extract the secondary network IP
+	for _, net := range networkStatus {
+		for _, iface := range net.Interfaces {
+			if iface.Name != "eth0" { // secondary interfaces are not eth0
+				return iface.IPs[0], nil // Return the first IP found
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no secondary IP found for pod %s", podName)
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) testPauseAgentDeletePodRestartAgent(vlanPod *testPodInfo, waitTime time.Duration) error {
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getOVSPorts(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+	logs.Infof("Fetching OVS ports for pod %s before deletion", beforePorts)
+
+	ipPoolNmae, err := data.getIPPoolFromPod(vlanPod.podName, data.e2eTestData.GetTestNamespace())
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
+	} else {
+		fmt.Println("IPPool name:", ipPoolNmae)
+	}
+
+	vlanIP, err := data.getPodSecondaryIP("default", vlanPod.podName)
+	if err != nil {
+		fmt.Println("Error:", err)
+	} else {
+		fmt.Println("Secondary IP:", vlanIP)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: logs.Infof?
created_at: 2025-02-15T01:15:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
+	}
+
+	var networkStatus []NetworkStatus
+	if err := json.Unmarshal(output, &networkStatus); err != nil {
+		return "", fmt.Errorf("failed to parse network status: %v", err)
+	}
+
+	// Extract the secondary network IP
+	for _, net := range networkStatus {
+		for _, iface := range net.Interfaces {
+			if iface.Name != "eth0" { // secondary interfaces are not eth0
+				return iface.IPs[0], nil // Return the first IP found
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no secondary IP found for pod %s", podName)
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) testPauseAgentDeletePodRestartAgent(vlanPod *testPodInfo, waitTime time.Duration) error {
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getOVSPorts(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+	logs.Infof("Fetching OVS ports for pod %s before deletion", beforePorts)
+
+	ipPoolNmae, err := data.getIPPoolFromPod(vlanPod.podName, data.e2eTestData.GetTestNamespace())
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: IPPool
created_at: 2025-02-15T01:17:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +453,13 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+
+	t.Run("testPauseAgentDeletePodRestartAgent", func(t *testing.T) {
+		err := testData.testPauseAgentDeletePodRestartAgent(pods[0], defaultTimeout)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Should we call it something like testReconcilationAfterAgentRestart?
created_at: 2025-02-15T01:18:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
+	}
+
+	var networkStatus []NetworkStatus
+	if err := json.Unmarshal(output, &networkStatus); err != nil {
+		return "", fmt.Errorf("failed to parse network status: %v", err)
+	}
+
+	// Extract the secondary network IP
+	for _, net := range networkStatus {
+		for _, iface := range net.Interfaces {
+			if iface.Name != "eth0" { // secondary interfaces are not eth0
+				return iface.IPs[0], nil // Return the first IP found
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no secondary IP found for pod %s", podName)
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) testPauseAgentDeletePodRestartAgent(vlanPod *testPodInfo, waitTime time.Duration) error {
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getOVSPorts(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+	logs.Infof("Fetching OVS ports for pod %s before deletion", beforePorts)
+
+	ipPoolNmae, err := data.getIPPoolFromPod(vlanPod.podName, data.e2eTestData.GetTestNamespace())
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
+	} else {
+		fmt.Println("IPPool name:", ipPoolNmae)
+	}
+
+	vlanIP, err := data.getPodSecondaryIP("default", vlanPod.podName)
+	if err != nil {
+		fmt.Println("Error:", err)
+	} else {
+		fmt.Println("Secondary IP:", vlanIP)
+	}
+
+
+	namespace := "kube-system"
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: These can be defined in pauseAndResumeAntreaAgent?
created_at: 2025-02-15T01:18:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +453,13 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+
+	t.Run("testPauseAgentDeletePodRestartAgent", func(t *testing.T) {
+		err := testData.testPauseAgentDeletePodRestartAgent(pods[0], defaultTimeout)
+		if err != nil {
+			t.Fatalf("Error when restarting antrea agent: %v", err)
+		}
+	})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Could we add a test to check after agent restart for the following:
1) a Pod that is not deleted should still have its secondary interface OVS port
2) delete the Pod
3) check OVS port is deleted and IP is released to the IPPool
created_at: 2025-02-15T01:21:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: @antoninbas : do you have a suggestion on the right way to stop agent and start agent? We do not have existing code for these?
created_at: 2025-02-15T01:23:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +237,204 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses or resumes the Antrea agent DaemonSet by modifying its update strategy.
+func (data *testData) pauseAndResumeAntreaAgent(namespace, daemonsetName, action string) error {
+	var patch string
+	if action == "pause" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else if action == "resume" {
+		patch = `{"spec": {"updateStrategy": {"rollingUpdate": {"maxUnavailable": 1}}}}`
+	} else {
+		return fmt.Errorf("invalid action: %s. Use 'pause' or 'resume'", action)
+	}
+
+	cmd := exec.Command("kubectl", "patch", "daemonset", daemonsetName, "-n", namespace, "--type=merge", "-p", patch)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("failed to %s daemonset %s: %v, output: %s", action, daemonsetName, err, string(output))
+	}
+	logs.Infof("Successfully %sd daemonset %s", action, daemonsetName)
+	return nil
+}
+
+// GetOVSPorts returns a list of OVS ports for a given pod.
+func (data *testData) getOVSPorts(targetPod *testPodInfo) ([]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	var ovsPorts []string
+	lines := strings.Split(stdout, "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName := strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+			ovsPorts = append(ovsPorts, ifaceName)
+		}
+	}
+	return ovsPorts, nil
+}
+
+// checkOVSPortExistence verifies if an OVS ports still exists.
+func (data *testData) checkOVSPortExistence(beforePorts, afterPorts []string) error {
+	beforeSet := make(map[string]struct{})
+	for _, port := range beforePorts {
+		beforeSet[port] = struct{}{}
+	}
+
+	for _, port := range afterPorts {
+		if _, exists := beforeSet[port]; exists {
+			return fmt.Errorf("stale OVS port found after pod deletion: %s", port)
+		}
+	}
+	return nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+func (data *testData) getIPPoolFromPod(podName, namespace string) (string, error) {
+	// Fetch the Pod object
+	logs.Info("namespace", data.e2eTestData.GetTestNamespace())
+	pod, err := data.clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod %s: %v", podName, err)
+	}
+
+	// Extract the network annotation
+	networkAnnotation, exists := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
+	if !exists {
+		return "", fmt.Errorf("no network annotation found for pod %s", podName)
+	}
+
+	// Parse annotation JSON
+	var networks []map[string]interface{}
+	if err := json.Unmarshal([]byte(networkAnnotation), &networks); err != nil {
+		return "", fmt.Errorf("failed to parse network annotation for pod %s: %v", podName, err)
+	}
+
+	// Extract IPPool name from the first network entry
+	for _, network := range networks {
+		if ipPool, found := network["ippools"]; found {
+			ipPools, ok := ipPool.([]interface{})
+			if ok && len(ipPools) > 0 {
+				if poolName, ok := ipPools[0].(string); ok {
+					return poolName, nil
+				}
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no IPPool found in annotation for pod %s", podName)
+}
+
+// getPodSecondaryIP fetches the secondary interface IP from the pod's annotation.
+func (data *testData) getPodSecondaryIP(namespace, podName string) (string, error) {
+	cmd := exec.Command("kubectl", "get", "pod", podName, "-n", namespace, "-o", "jsonpath={.metadata.annotations.k8s\\.v1\\.cni\\.cncf\\.io/networks}")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get pod annotations: %v", err)
+	}
+
+	var networkStatus []NetworkStatus
+	if err := json.Unmarshal(output, &networkStatus); err != nil {
+		return "", fmt.Errorf("failed to parse network status: %v", err)
+	}
+
+	// Extract the secondary network IP
+	for _, net := range networkStatus {
+		for _, iface := range net.Interfaces {
+			if iface.Name != "eth0" { // secondary interfaces are not eth0
+				return iface.IPs[0], nil // Return the first IP found
+			}
+		}
+	}
+
+	return "", fmt.Errorf("no secondary IP found for pod %s", podName)
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) testPauseAgentDeletePodRestartAgent(vlanPod *testPodInfo, waitTime time.Duration) error {
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getOVSPorts(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+	logs.Infof("Fetching OVS ports for pod %s before deletion", beforePorts)
+
+	ipPoolNmae, err := data.getIPPoolFromPod(vlanPod.podName, data.e2eTestData.GetTestNamespace())
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Return the error?
created_at: 2025-02-18T05:08:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +226,167 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// PauseAndResumeAntreaAgent adds a nodeSelector to the DaemonSet, preventing it from scheduling on worker nodes.
+func (data *testData) PauseAndResumeAntreaAgent() error {
+	namespace := "kube-system"
+	name := "antrea-agent"
+	// pause antrea-agent pods
+	patch := []byte(`[
+		{
+			"op": "add",
+			"path": "/spec/template/spec/nodeSelector",
+			"value": {
+				"pause-daemonset": "true"
+			}
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent %s: %v", name, err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	logs.Infof("Deleting VLAN pod %s", data.pods[1].podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, data.pods[1].podName, "default"); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", data.pods[1].podName, err)
+	}
+
+	// resume antrea-agent pods
+	patch = []byte(`[
+		{
+			"op": "remove",
+			"path": "/spec/template/spec/nodeSelector"
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v, output: %s", err, name)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string) // to store interface names and their associated IPs
+	var ifaceName string
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		// If line indicates an interface
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		// If the line contains an IP address (either IPv4 or IPv6)
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			// Add the IP address to the list for the corresponding interface
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+// getIPPoolNames fetches the IPPool names from a given NetworkAttachmentDefinition
+func (data *testData)getIPPoolNames(networkName string) ([]string, error) {
+	cmd := exec.Command("kubectl", "get", "net-attach-def", networkName, "-o", "jsonpath={.spec.config}")
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NAD config: %v, output: %s", err, output)
+	}
+
+	// Parse the JSON output
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(output), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %v", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) ReconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface1 := "eth1"
+	networkName := vlanPod.interfaceNetworks[iface1]
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+
+	podIp1, _, _ := net.ParseCIDR(beforePorts[iface1][0])
+	fmt.Println("ip", podIp1)
+
+	ipPoolNmae, err := data.getIPPoolNames(networkName)
+	if err != nil {
+		fmt.Println("Failed to get ippool:", err)
+	} else {
+		fmt.Println("IPPool name:", ipPoolNmae)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Is it a temporary debug message?
created_at: 2025-02-18T05:08:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +226,167 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// PauseAndResumeAntreaAgent adds a nodeSelector to the DaemonSet, preventing it from scheduling on worker nodes.
+func (data *testData) PauseAndResumeAntreaAgent() error {
+	namespace := "kube-system"
+	name := "antrea-agent"
+	// pause antrea-agent pods
+	patch := []byte(`[
+		{
+			"op": "add",
+			"path": "/spec/template/spec/nodeSelector",
+			"value": {
+				"pause-daemonset": "true"
+			}
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent %s: %v", name, err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	logs.Infof("Deleting VLAN pod %s", data.pods[1].podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, data.pods[1].podName, "default"); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", data.pods[1].podName, err)
+	}
+
+	// resume antrea-agent pods
+	patch = []byte(`[
+		{
+			"op": "remove",
+			"path": "/spec/template/spec/nodeSelector"
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v, output: %s", err, name)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string) // to store interface names and their associated IPs
+	var ifaceName string
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		// If line indicates an interface
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		// If the line contains an IP address (either IPv4 or IPv6)
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			// Add the IP address to the list for the corresponding interface
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+// getIPPoolNames fetches the IPPool names from a given NetworkAttachmentDefinition
+func (data *testData)getIPPoolNames(networkName string) ([]string, error) {
+	cmd := exec.Command("kubectl", "get", "net-attach-def", networkName, "-o", "jsonpath={.spec.config}")
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NAD config: %v, output: %s", err, output)
+	}
+
+	// Parse the JSON output
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(output), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %v", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Change the name
created_at: 2025-02-18T05:10:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +226,167 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// PauseAndResumeAntreaAgent adds a nodeSelector to the DaemonSet, preventing it from scheduling on worker nodes.
+func (data *testData) PauseAndResumeAntreaAgent() error {
+	namespace := "kube-system"
+	name := "antrea-agent"
+	// pause antrea-agent pods
+	patch := []byte(`[
+		{
+			"op": "add",
+			"path": "/spec/template/spec/nodeSelector",
+			"value": {
+				"pause-daemonset": "true"
+			}
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent %s: %v", name, err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	logs.Infof("Deleting VLAN pod %s", data.pods[1].podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, data.pods[1].podName, "default"); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", data.pods[1].podName, err)
+	}
+
+	// resume antrea-agent pods
+	patch = []byte(`[
+		{
+			"op": "remove",
+			"path": "/spec/template/spec/nodeSelector"
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v, output: %s", err, name)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string) // to store interface names and their associated IPs
+	var ifaceName string
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		// If line indicates an interface
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		// If the line contains an IP address (either IPv4 or IPv6)
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			// Add the IP address to the list for the corresponding interface
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+// getIPPoolNames fetches the IPPool names from a given NetworkAttachmentDefinition
+func (data *testData)getIPPoolNames(networkName string) ([]string, error) {
+	cmd := exec.Command("kubectl", "get", "net-attach-def", networkName, "-o", "jsonpath={.spec.config}")
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NAD config: %v, output: %s", err, output)
+	}
+
+	// Parse the JSON output
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(output), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %v", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// testPauseAgentDeletePodRestartAgent verifies OVS cleanup and IP release.
+func (data *testData) ReconcilationAfterAgentRestart(t *testing.T) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: reconcilationAfterAgentRestart
created_at: 2025-02-18T05:10:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +226,167 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// PauseAndResumeAntreaAgent adds a nodeSelector to the DaemonSet, preventing it from scheduling on worker nodes.
+func (data *testData) PauseAndResumeAntreaAgent() error {
+	namespace := "kube-system"
+	name := "antrea-agent"
+	// pause antrea-agent pods
+	patch := []byte(`[
+		{
+			"op": "add",
+			"path": "/spec/template/spec/nodeSelector",
+			"value": {
+				"pause-daemonset": "true"
+			}
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent %s: %v", name, err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	logs.Infof("Deleting VLAN pod %s", data.pods[1].podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, data.pods[1].podName, "default"); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", data.pods[1].podName, err)
+	}
+
+	// resume antrea-agent pods
+	patch = []byte(`[
+		{
+			"op": "remove",
+			"path": "/spec/template/spec/nodeSelector"
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v, output: %s", err, name)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string) // to store interface names and their associated IPs
+	var ifaceName string
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		// If line indicates an interface
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		// If the line contains an IP address (either IPv4 or IPv6)
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			// Add the IP address to the list for the corresponding interface
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I feel you should add a GetCRDClient() func to TestData to return the crdClient
created_at: 2025-02-18T05:23:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +226,167 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// PauseAndResumeAntreaAgent adds a nodeSelector to the DaemonSet, preventing it from scheduling on worker nodes.
+func (data *testData) PauseAndResumeAntreaAgent() error {
+	namespace := "kube-system"
+	name := "antrea-agent"
+	// pause antrea-agent pods
+	patch := []byte(`[
+		{
+			"op": "add",
+			"path": "/spec/template/spec/nodeSelector",
+			"value": {
+				"pause-daemonset": "true"
+			}
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent %s: %v", name, err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	logs.Infof("Deleting VLAN pod %s", data.pods[1].podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, data.pods[1].podName, "default"); err != nil {
+		return fmt.Errorf("failed to delete pod %s: %v", data.pods[1].podName, err)
+	}
+
+	// resume antrea-agent pods
+	patch = []byte(`[
+		{
+			"op": "remove",
+			"path": "/spec/template/spec/nodeSelector"
+		}
+	]`)
+
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patch); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v, output: %s", err, name)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string) // to store interface names and their associated IPs
+	var ifaceName string
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		// If line indicates an interface
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		// If the line contains an IP address (either IPv4 or IPv6)
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			// Add the IP address to the list for the corresponding interface
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP from the deleted pods is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	// Fetch the IPPool CR
+	ipPool, err := data.crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+	}
+
+	podIP := net.ParseIP(podIPString)
+	if podIP == nil {
+		return fmt.Errorf("invalid pod IP: %s", podIPString)
+	}
+
+	// Check if podIP exists in the allocated IPs
+	for _, ipAddress := range ipPool.Status.IPAddresses {
+		if podIP.Equal(net.ParseIP(ipAddress.IPAddress)) {
+			return fmt.Errorf("IP is still allocated: %v", err)
+		}
+	}
+
+	return nil // IP is released
+}
+
+// getIPPoolNames fetches the IPPool names from a given NetworkAttachmentDefinition
+func (data *testData)getIPPoolNames(networkName string) ([]string, error) {
+	cmd := exec.Command("kubectl", "get", "net-attach-def", networkName, "-o", "jsonpath={.spec.config}")
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Better to create a netAttachDefClient and use it to get the NetAttachDef CR.

You may add a GetKubeConfig func to TestData to get the kubeconfig to create the client.
created_at: 2025-02-18T05:32:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,6 +102,7 @@ func NewPodController(
 	netAttachDefClient netdefclient.K8sCniCncfIoV1Interface,
 	podInformer cache.SharedIndexInformer,
 	podUpdateSubscriber channel.Subscriber,
+	pIfaceStore interfacestore.InterfaceStore,
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: could you spell out `primaryInterfaceStore` everywhere for readability?
created_at: 2025-02-19T21:10:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,6 +134,15 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	if err := pc.initializeSecondaryInterfaceStore(); err != nil {
+		return nil, fmt.Errorf("failed to initialize secondary interface store: %v", err)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: nowadays, we try to use the `%w` formatter consistently when wrapping errors
created_at: 2025-02-19T21:11:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: is this actually possible? it is strange to just silently do nothing when `ovsBridgeClient` is nil. If this is not a supported case, IMO it is better to remove the check.
created_at: 2025-02-19T21:12:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %v", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: add `continue` here, and remove the `intf != nil` check below?
created_at: 2025-02-19T21:13:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %v", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	if pIfaceStore == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: a similar comment as above: is this actually possible / expected in some cases? If not, we should not "silently" ignore that case.
created_at: 2025-02-19T21:13:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,3 +940,143 @@ func testPodControllerStart(ctrl *gomock.Controller) (
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func setupMockController(t *testing.T) (*gomock.Controller, *ovsconfigtest.MockOVSBridgeClient, *podwatchtesting.MockInterfaceConfigurator, *podwatchtesting.MockIPAMAllocator, *PodController) {
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: can't this be unified with the `testPodController` function?
created_at: 2025-02-19T21:24:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,3 +940,143 @@ func testPodControllerStart(ctrl *gomock.Controller) (
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func setupMockController(t *testing.T) (*gomock.Controller, *ovsconfigtest.MockOVSBridgeClient, *podwatchtesting.MockInterfaceConfigurator, *podwatchtesting.MockIPAMAllocator, *PodController) {
+	ctrl := gomock.NewController(t)
+	mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(ctrl)
+	interfaceConfigurator := podwatchtesting.NewMockInterfaceConfigurator(ctrl)
+	mockIPAM := podwatchtesting.NewMockIPAMAllocator(ctrl)
+
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient:       mockOVSBridgeClient,
+		interfaceStore:        store,
+		cniCache:              sync.Map{},
+		interfaceConfigurator: interfaceConfigurator,
+		ipamAllocator:         mockIPAM,
+	}
+	return ctrl, mockOVSBridgeClient, interfaceConfigurator, mockIPAM, pc
+
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: this hasn't been necessary for a while with recent versions of `gomock`
created_at: 2025-02-19T21:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,3 +940,143 @@ func testPodControllerStart(ctrl *gomock.Controller) (
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func setupMockController(t *testing.T) (*gomock.Controller, *ovsconfigtest.MockOVSBridgeClient, *podwatchtesting.MockInterfaceConfigurator, *podwatchtesting.MockIPAMAllocator, *PodController) {
+	ctrl := gomock.NewController(t)
+	mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(ctrl)
+	interfaceConfigurator := podwatchtesting.NewMockInterfaceConfigurator(ctrl)
+	mockIPAM := podwatchtesting.NewMockIPAMAllocator(ctrl)
+
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient:       mockOVSBridgeClient,
+		interfaceStore:        store,
+		cniCache:              sync.Map{},
+		interfaceConfigurator: interfaceConfigurator,
+		ipamAllocator:         mockIPAM,
+	}
+	return ctrl, mockOVSBridgeClient, interfaceConfigurator, mockIPAM, pc
+
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	// Test Case 1: OVSBridgeClient is nil
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient: nil,
+		interfaceStore:  store,
+	}
+	err := pc.initializeSecondaryInterfaceStore()
+	assert.NoError(t, err, "No error when OVSBridgeClient is nil")
+
+	// Test Case 2: OVSBridgeClient returns an error
+	ctrl, mockOVSBridgeClient, _, _, pc := setupMockController(t)
+	defer ctrl.Finish()
+
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, ovsconfig.NewTransactionError(fmt.Errorf("Failed to list OVS ports"), true))
+	err = pc.initializeSecondaryInterfaceStore()
+	assert.Error(t, err, "Failed to list OVS ports")
+
+	// Test Case 3: OVSBridgeClient returns valid ports
+	uuids, ovsPorts, _ := createTestInterfaces()
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(ovsPorts, nil)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	assert.NoError(t, err, "OVS ports list successfully")
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: you need to use `require` in this case, as the rest of the test is guaranteed to fail or even panic

please review other assertions as well
created_at: 2025-02-19T21:26:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: I don't know if we need to make the test this complex, instead of just restarting the antrea-agent Pod on the Node (by deleting it). If you look at #6578, the issue was occurring even when the Pod was deleted after the restart of the agent. So IMO, trying to delete the Pod _while_ the Agent is down may be making things unnecessary complex. cc @jianjuns 
created_at: 2025-02-19T21:31:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
+	const namespace, name = "kube-system", "antrea-agent"
+
+	patchPause := []byte(`[
+		{"op": "add", "path": "/spec/template/spec/nodeSelector", "value": {"pause-daemonset": "true"}}
+	]`)
+
+	patchResume := []byte(`[
+		{"op": "remove", "path": "/spec/template/spec/nodeSelector"}
+	]`)
+
+	// Pause antrea-agent
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patchPause); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent: %v", err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	// Delete pod after pausing agent
+	podName := data.pods[0].podName
+	logs.Infof("Deleting VLAN pod %s", podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		logs.Infof("Antrea Agent has been paused; pod %s status updated once agent resumed", podName)
+	}
+
+	// Resume antrea-agent
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patchResume); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v", err)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string)
+	var ifaceName string
+
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %v", err)
+	}
+
+	timeout := time.After(40 * time.Second)
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-timeout:
+			return fmt.Errorf("timeout waiting for IP %s to be released", podIPString)
+		case <-ticker.C:
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+			if err != nil {
+				return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+			}
+
+			released := true
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIPString == ipAddress.IPAddress {
+					released = false
+					break
+				}
+			}
+
+			if released {
+				return nil
+			}
+		}
+	}
+}
+
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %v", err)
+	}
+
+	client, err := netattdef.NewForConfig(config)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create NetAttachDef client: %v", err)
+	}
+	return client, nil
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %v", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %v", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[0]
+	iface1 := "eth1"
+
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+
+	podIP, _, _ := net.ParseCIDR(beforePorts[iface1][0])
+
+	IPPools, err := data.getIPPoolNames(vlanPod.interfaceNetworks[iface1], "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %v", err)
+	}
+
+	if err := data.pauseAndResumeAntreaAgent(); err != nil {
+		return fmt.Errorf("failed to pause Antrea agent: %v", err)
+	}
+
+	logs.Infof("Checking OVS ports after VLAN pod deletion")
+	afterPorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		logs.Infof("OVS ports for pod %s has been deleted %v", vlanPod.podName, err)
+	}
+
+	if len(afterPorts) != 0 {
+		if afterPorts[iface1][0] == beforePorts[iface1][0] {
+			return fmt.Errorf("Ovs ports still there")
+		}
+	}
+
+	logs.Infof("Checking if VLAN IP is released")
+	if err := data.checkIPReleased(IPPools[0], podIP.String()); err != nil {
+		return fmt.Errorf("VLAN IP release failed: %v", err)
+	}
+
+	return nil
+}
+
+// removePodsAndCheckOVSPorts removes a pod and check if its OVS ports and IPs are released.
+func (data *testData) removePodsAndCheckOVSPorts(t *testing.T) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: out of curiosity, I assume that this scenario was already handled correctly prior to this patch?
created_at: 2025-02-19T21:33:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: So, you suggest not to a test case of deleting Pod when Agent is down? But one main purpose of this PR is to handle such cases after Agent restarts.
created_at: 2025-02-20T00:21:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
+	const namespace, name = "kube-system", "antrea-agent"
+
+	patchPause := []byte(`[
+		{"op": "add", "path": "/spec/template/spec/nodeSelector", "value": {"pause-daemonset": "true"}}
+	]`)
+
+	patchResume := []byte(`[
+		{"op": "remove", "path": "/spec/template/spec/nodeSelector"}
+	]`)
+
+	// Pause antrea-agent
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patchPause); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent: %v", err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	// Delete pod after pausing agent
+	podName := data.pods[0].podName
+	logs.Infof("Deleting VLAN pod %s", podName)
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		logs.Infof("Antrea Agent has been paused; pod %s status updated once agent resumed", podName)
+	}
+
+	// Resume antrea-agent
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patchResume); err != nil {
+		return fmt.Errorf("failed to resume Antrea agent: %v", err)
+	}
+	fmt.Println("Antrea Agent resumed successfully")
+
+	return nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string)
+	var ifaceName string
+
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			ipAddress := fields[1]
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], ipAddress)
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %v", err)
+	}
+
+	timeout := time.After(40 * time.Second)
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-timeout:
+			return fmt.Errorf("timeout waiting for IP %s to be released", podIPString)
+		case <-ticker.C:
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+			if err != nil {
+				return fmt.Errorf("failed to get IPPool %s: %v", ipPoolName, err)
+			}
+
+			released := true
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIPString == ipAddress.IPAddress {
+					released = false
+					break
+				}
+			}
+
+			if released {
+				return nil
+			}
+		}
+	}
+}
+
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %v", err)
+	}
+
+	client, err := netattdef.NewForConfig(config)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create NetAttachDef client: %v", err)
+	}
+	return client, nil
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %v", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %v", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[0]
+	iface1 := "eth1"
+
+	logs.Infof("Fetching OVS ports for pod %s before deletion", vlanPod.podName)
+	beforePorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS ports before deletion: %v", err)
+	}
+
+	podIP, _, _ := net.ParseCIDR(beforePorts[iface1][0])
+
+	IPPools, err := data.getIPPoolNames(vlanPod.interfaceNetworks[iface1], "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %v", err)
+	}
+
+	if err := data.pauseAndResumeAntreaAgent(); err != nil {
+		return fmt.Errorf("failed to pause Antrea agent: %v", err)
+	}
+
+	logs.Infof("Checking OVS ports after VLAN pod deletion")
+	afterPorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		logs.Infof("OVS ports for pod %s has been deleted %v", vlanPod.podName, err)
+	}
+
+	if len(afterPorts) != 0 {
+		if afterPorts[iface1][0] == beforePorts[iface1][0] {
+			return fmt.Errorf("Ovs ports still there")
+		}
+	}
+
+	logs.Infof("Checking if VLAN IP is released")
+	if err := data.checkIPReleased(IPPools[0], podIP.String()); err != nil {
+		return fmt.Errorf("VLAN IP release failed: %v", err)
+	}
+
+	return nil
+}
+
+// removePodsAndCheckOVSPorts removes a pod and check if its OVS ports and IPs are released.
+func (data *testData) removePodsAndCheckOVSPorts(t *testing.T) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: We want to cover the case after Agent restart existing interfaces can be correctly restored and then Pod deletion can delete them and release the IPs.
created_at: 2025-02-20T00:23:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
+	const namespace, name = "kube-system", "antrea-agent"
+
+	patchPause := []byte(`[
+		{"op": "add", "path": "/spec/template/spec/nodeSelector", "value": {"pause-daemonset": "true"}}
+	]`)
+
+	patchResume := []byte(`[
+		{"op": "remove", "path": "/spec/template/spec/nodeSelector"}
+	]`)
+
+	// Pause antrea-agent
+	if err := data.e2eTestData.PatchDaemonSet(namespace, name, patchPause); err != nil {
+		return fmt.Errorf("failed to pause antrea-agent: %v", err)
+	}
+	fmt.Println("Antrea Agent paused successfully")
+
+	// Delete pod after pausing agent
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Please change all "pod" (in comments, logs, errors) to "Pod".
created_at: 2025-02-20T00:24:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: rajnkamr
body: 
Currently agent down case is tested in following seq
stop the antrea agent
delete the testpod
restart/resume the antrea agent
check for ovs ports
check for secondary interface ip (released or not)

Since antrea agent is down, it is taking 40s after the agent is resumed to get the ip released back to ippool.
During the 40s period ip is still shown as used. Ideally it could be expected, as  agent was down and hence it took time to process.

created_at: 2025-02-20T09:29:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: Actually i added this to handle :https://github.com/antrea-io/antrea/blob/main/pkg/agent/secondarynetwork/podwatch/controller_test.go#L223, in  TestPodControllerRun() 
OVSBridgeClient) and  primaryInterface store are nil.
```
podController, _ := NewPodController(
		client,
		netdefclient,
		informerFactory.Core().V1().Pods().Informer(),
		nil, nil, nil) 
```
created_at: 2025-02-20T09:36:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,221 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// pauseAndResumeAntreaAgent pauses and resumes the antrea-agent DaemonSet.
+func (data *testData) pauseAndResumeAntreaAgent() error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: I updated PR description with e2e cases:
in case1:
after (pod deletion+resume agent)  i put 40s timeout and 5s  ticker to call API to check secondary interface ip released or not, sometime it pass and  sometime it fail even considering 40s timeout.
```
timeout := time.After(40 * time.Second)
ticker := time.NewTicker(5 * time.Second)
```

https://github.com/antrea-io/antrea/actions/runs/13433383732/job/37530310194?pr=6853
 
created_at: 2025-02-20T11:14:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %v", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+		}
+
+		if intf != nil {
+			ifaceList = append(ifaceList, intf)
+		}
+
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(pIfaceStore interfacestore.InterfaceStore) error {
+	if pIfaceStore == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: same reason as  OVSBridgeClient.
created_at: 2025-02-21T08:58:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: antoninbas
body: It's usually not a good idea to modify your logic specifically to accommodate for unit tests, it would make more sense to modify the tests instead
created_at: 2025-02-21T18:27:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1011,145 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	// Test Case 1: OVSBridgeClient is nil
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient: nil,
+		interfaceStore:  store,
+	}
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "No error when OVSBridgeClient is nil")
+
+	// Test Case 2: OVSBridgeClient returns an error
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, ovsconfig.NewTransactionError(fmt.Errorf("Failed to list OVS ports"), true))
+	err = pc.initializeSecondaryInterfaceStore()
+	require.Error(t, err, "Failed to list OVS ports") // require since failure prevents further checks
+
+	// Test Case 3: OVSBridgeClient returns valid ports
+	uuids, ovsPorts, _ := createTestInterfaces()
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(ovsPorts, nil)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "OVS ports list successfully")
+
+	// Validate stored interfaces
+	require.Equal(t, 3, pc.interfaceStore.Len(), "Only valid interfaces should be stored") // require as test logic depends on it
+
+	_, found1 := pc.interfaceStore.GetContainerInterface(uuids["uuid1"])
+	require.True(t, found1, "Interface 1 should be stored")
+
+	_, found2 := pc.interfaceStore.GetContainerInterface(uuids["uuid2"])
+	require.True(t, found2, "Interface 2 should be stored")
+
+	_, found3 := pc.interfaceStore.GetContainerInterface(uuids["uuid4"])
+	require.False(t, found3, "Unknown interface type should not be stored")
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: these should use `assert`, not `require`
created_at: 2025-02-21T18:29:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1011,145 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	// Test Case 1: OVSBridgeClient is nil
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient: nil,
+		interfaceStore:  store,
+	}
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "No error when OVSBridgeClient is nil")
+
+	// Test Case 2: OVSBridgeClient returns an error
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, ovsconfig.NewTransactionError(fmt.Errorf("Failed to list OVS ports"), true))
+	err = pc.initializeSecondaryInterfaceStore()
+	require.Error(t, err, "Failed to list OVS ports") // require since failure prevents further checks
+
+	// Test Case 3: OVSBridgeClient returns valid ports
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: if you really have 3 independent test cases, then you should define 3 separate subtests, each with its own gomock controller object
created_at: 2025-02-21T18:30:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1011,145 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	// Test Case 1: OVSBridgeClient is nil
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient: nil,
+		interfaceStore:  store,
+	}
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "No error when OVSBridgeClient is nil")
+
+	// Test Case 2: OVSBridgeClient returns an error
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, ovsconfig.NewTransactionError(fmt.Errorf("Failed to list OVS ports"), true))
+	err = pc.initializeSecondaryInterfaceStore()
+	require.Error(t, err, "Failed to list OVS ports") // require since failure prevents further checks
+
+	// Test Case 3: OVSBridgeClient returns valid ports
+	uuids, ovsPorts, _ := createTestInterfaces()
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(ovsPorts, nil)
+
+	err = pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "OVS ports list successfully")
+
+	// Validate stored interfaces
+	require.Equal(t, 3, pc.interfaceStore.Len(), "Only valid interfaces should be stored") // require as test logic depends on it
+
+	_, found1 := pc.interfaceStore.GetContainerInterface(uuids["uuid1"])
+	require.True(t, found1, "Interface 1 should be stored")
+
+	_, found2 := pc.interfaceStore.GetContainerInterface(uuids["uuid2"])
+	require.True(t, found2, "Interface 2 should be stored")
+
+	_, found3 := pc.interfaceStore.GetContainerInterface(uuids["uuid4"])
+	require.False(t, found3, "Unknown interface type should not be stored")
+}
+
+func TestReconcileSecondaryInterfaces(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	pc, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
+	primaryStore := interfacestore.NewInterfaceStore()
+
+	_, _, ifaces := createTestInterfaces()
+
+	// Add interfaces to primary store
+	primaryStore.AddInterface(ifaces[0])
+	primaryStore.AddInterface(ifaces[1])
+
+	// Add interfaces to controller secondaryInterfaceStore
+	pc.interfaceStore.AddInterface(ifaces[0])
+	pc.interfaceStore.AddInterface(ifaces[1])
+	pc.interfaceStore.AddInterface(ifaces[2])
+
+	interfaceConfigurator.EXPECT().DeleteVLANSecondaryInterface(gomock.Any()).Return(nil).Times(1)
+	mockIPAM.EXPECT().SecondaryNetworkRelease(gomock.Any()).Return(nil).Times(1)
+
+	err := pc.reconcileSecondaryInterfaces(primaryStore)
+	require.NoError(t, err)
+
+	pc.interfaceStore.DeleteInterface(ifaces[2])
+
+	// Check CNI Cache
+	_, foundPod1 := pc.cniCache.Load("ns1/Pod1")
+	require.True(t, foundPod1, "CNI Cache should contain ns1/Pod1")
+
+	_, foundPod2 := pc.cniCache.Load("ns2/Pod2")
+	require.True(t, foundPod2, "CNI Cache should contain ns2/Pod2")
+
+	// Ensure stale interfaces are removed
+	_, foundPod3 := pc.cniCache.Load("ns3/Pod3")
+	require.False(t, foundPod3, "Stale interface should have been removed")
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: use `assert`
created_at: 2025-02-21T18:30:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
+func (data *testData) checkPodExists(podName, namespace string) (bool, error) {
+	_, err := data.e2eTestData.PodWaitFor(1*time.Second, podName, namespace, nil)
+	return err == nil, nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target Pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for Pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string)
+	var ifaceName string
+
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], fields[1])
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	timeout := time.After(40 * time.Second)
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-timeout:
+			return fmt.Errorf("timeout waiting for IP %s to be released", podIPString)
+		case <-ticker.C:
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+			if err != nil {
+				return fmt.Errorf("failed to get IPPool %s: %w", ipPoolName, err)
+			}
+
+			released := true
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIPString == ipAddress.IPAddress {
+					released = false
+					break
+				}
+			}
+
+			if released {
+				return nil
+			}
+		}
+	}
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforePorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS port and Pod IP before agent restart: %w", err)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	afterPorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil || len(afterPorts) == 0 || afterPorts[iface][0] != beforePorts[iface][0] {
+		return fmt.Errorf("OVS port/IP mismatch after agent restart")
+	}
+
+	return nil
+}
+
+// removePodsAndCheckIP verifies that OVS ports are removed when pods are deleted.
+func (data *testData) removePodsAndCheckIP() error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforePorts, err := data.getPodPortsAndIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS port and Pod IP before Pod deletion: %w", err)
+	}
+
+	podIP, _, _ := net.ParseCIDR(beforePorts[iface][0])
+	ipPools, err := data.getIPPoolNames(vlanPod.interfaceNetworks[iface], "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	if exists, _ := data.checkPodExists(vlanPod.podName, data.e2eTestData.GetTestNamespace()); exists {
+		return fmt.Errorf("Pod %s still exists after deletion", vlanPod.podName)
+	}
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: this is not necessary
created_at: 2025-02-21T18:31:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
+func (data *testData) checkPodExists(podName, namespace string) (bool, error) {
+	_, err := data.e2eTestData.PodWaitFor(1*time.Second, podName, namespace, nil)
+	return err == nil, nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target Pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: can you unify with the existing `listPodIPs` function? They do essentially the same thing
created_at: 2025-02-21T18:32:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
+func (data *testData) checkPodExists(podName, namespace string) (bool, error) {
+	_, err := data.e2eTestData.PodWaitFor(1*time.Second, podName, namespace, nil)
+	return err == nil, nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target Pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for Pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string)
+	var ifaceName string
+
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], fields[1])
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	timeout := time.After(40 * time.Second)
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	for {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: we always use the polling functions for this (e.g., `wait.PollUntilContextTimeout`)
created_at: 2025-02-21T18:35:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +390,16 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testreconcilationAfterAgentRestart", func(t *testing.T) {
+		if err := testData.reconcilationAfterAgentRestart(t); err != nil {
+			t.Fatalf("Error when restarting antrea-agent: %v", err)
+		}
+	})
+	t.Run("testremovePodsAndCheckIP", func(t *testing.T) {
+		if err := testData.removePodsAndCheckIP(); err != nil {
+			t.Fatalf("Error when removing the Pod: %v", err)
+		}
+	})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: from what i can tell, the first test is not actually testing anything, while you assume that the second test (`testremovePodsAndCheckIP`) runs after the first one (`testreconcilationAfterAgentRestart`). You should merge these into a single test case, there is no reason to have 2 if they are not meant to be independent.
created_at: 2025-02-21T18:37:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -502,3 +512,85 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Could we use MockOVSBridgeClient?
created_at: 2025-02-22T00:56:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -521,3 +531,83 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %w", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+			continue
+		}
+
+		ifaceList = append(ifaceList, intf)
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(primaryInterfaceStore interfacestore.InterfaceStore) error {
+	if primaryInterfaceStore == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Passing an empty interface store should be good too?
created_at: 2025-02-22T00:57:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: pod -> Pod
namespace -> Namespace
created_at: 2025-02-22T00:58:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +390,16 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testreconcilationAfterAgentRestart", func(t *testing.T) {
+		if err := testData.reconcilationAfterAgentRestart(t); err != nil {
+			t.Fatalf("Error when restarting antrea-agent: %v", err)
+		}
+	})
+	t.Run("testremovePodsAndCheckIP", func(t *testing.T) {
+		if err := testData.removePodsAndCheckIP(); err != nil {
+			t.Fatalf("Error when removing the Pod: %v", err)
+		}
+	})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Agree to merge the two tests too (we should keep existing code in testreconcilationAfterAgentRestart which can check we do not mistakenly remove OVS ports after Agent restarts).
created_at: 2025-02-22T01:02:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1011,145 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{iface1, iface2, iface3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	// Test Case 1: OVSBridgeClient is nil
+	store := interfacestore.NewInterfaceStore()
+	pc := &PodController{
+		ovsBridgeClient: nil,
+		interfaceStore:  store,
+	}
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "No error when OVSBridgeClient is nil")
+
+	// Test Case 2: OVSBridgeClient returns an error
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(nil, ovsconfig.NewTransactionError(fmt.Errorf("Failed to list OVS ports"), true))
+	err = pc.initializeSecondaryInterfaceStore()
+	require.Error(t, err, "Failed to list OVS ports") // require since failure prevents further checks
+
+	// Test Case 3: OVSBridgeClient returns valid ports
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: KMAnju-2021
body: just need one.
created_at: 2025-02-24T10:11:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -521,3 +531,83 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
+		return nil
+	}
+
+	ovsPorts, err := pc.ovsBridgeClient.GetPortList()
+	if err != nil {
+		return fmt.Errorf("failed to list OVS ports for the secondary bridge: %w", err)
+	}
+
+	ifaceList := make([]*interfacestore.InterfaceConfig, 0, len(ovsPorts))
+	for index := range ovsPorts {
+		port := &ovsPorts[index]
+		ovsPort := &interfacestore.OVSPortConfig{
+			PortUUID: port.UUID,
+			OFPort:   port.OFPort,
+		}
+
+		interfaceType, ok := port.ExternalIDs[interfacestore.AntreaInterfaceTypeKey]
+		if !ok {
+			klog.InfoS("Interface type is not set for the secondary bridge", "interfaceName", port.Name)
+			continue
+		}
+
+		var intf *interfacestore.InterfaceConfig
+		switch interfaceType {
+		case interfacestore.AntreaContainer:
+			intf = cniserver.ParseOVSPortInterfaceConfig(port, ovsPort)
+		default:
+			klog.InfoS("Unknown Antrea interface type for the secondary bridge", "type", interfaceType)
+			continue
+		}
+
+		ifaceList = append(ifaceList, intf)
+	}
+
+	pc.interfaceStore.Initialize(ifaceList)
+	klog.InfoS("Successfully initialized the secondary bridge interface store")
+
+	return nil
+}
+
+// reconcileSecondaryInterfaces restores cniCache when agent restarts using primary interfaceStore.
+func (pc *PodController) reconcileSecondaryInterfaces(primaryInterfaceStore interfacestore.InterfaceStore) error {
+	if primaryInterfaceStore == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: KMAnju-2021
body: I will remove this check.
created_at: 2025-02-24T10:13:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
+func (data *testData) checkPodExists(podName, namespace string) (bool, error) {
+	_, err := data.e2eTestData.PodWaitFor(1*time.Second, podName, namespace, nil)
+	return err == nil, nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target Pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: yes
created_at: 2025-02-24T10:24:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +223,155 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// checkPodExists returns true if the pod exists in the given namespace.
+func (data *testData) checkPodExists(podName, namespace string) (bool, error) {
+	_, err := data.e2eTestData.PodWaitFor(1*time.Second, podName, namespace, nil)
+	return err == nil, nil
+}
+
+// getPodPortsAndIPs retrieves the interfaces and IPs of a target Pod.
+func (data *testData) getPodPortsAndIPs(targetPod *testPodInfo) (map[string][]string, error) {
+	cmd := []string{"ip", "addr", "show"}
+	stdout, _, err := data.e2eTestData.RunCommandFromPod(data.e2eTestData.GetTestNamespace(), targetPod.podName, containerName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("error listing interfaces for Pod %s: %w", targetPod.podName, err)
+	}
+
+	portsAndIPs := make(map[string][]string)
+	var ifaceName string
+
+	for _, line := range strings.Split(stdout, "\n") {
+		fields := strings.Fields(line)
+		if len(fields) >= 2 && strings.HasSuffix(fields[0], ":") {
+			ifaceName = strings.Split(strings.TrimSuffix(fields[1], ":"), "@")[0]
+		}
+		if len(fields) > 1 && (fields[0] == "inet" || fields[0] == "inet6") {
+			portsAndIPs[ifaceName] = append(portsAndIPs[ifaceName], fields[1])
+		}
+	}
+	return portsAndIPs, nil
+}
+
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	timeout := time.After(40 * time.Second)
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	for {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: done
created_at: 2025-02-24T10:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,6 +390,16 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 			t.Fatalf("Error when pinging between interfaces: %v", err)
 		}
 	})
+	t.Run("testreconcilationAfterAgentRestart", func(t *testing.T) {
+		if err := testData.reconcilationAfterAgentRestart(t); err != nil {
+			t.Fatalf("Error when restarting antrea-agent: %v", err)
+		}
+	})
+	t.Run("testremovePodsAndCheckIP", func(t *testing.T) {
+		if err := testData.removePodsAndCheckIP(); err != nil {
+			t.Fatalf("Error when removing the Pod: %v", err)
+		}
+	})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: merged
created_at: 2025-02-24T10:24:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,128 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: this should not be required?
created_at: 2025-02-25T18:47:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,128 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: didn't you mean to use `uuid4` here? It doesn't make sense to use the same UUID as for `ovsPort3`, right?
created_at: 2025-02-25T18:51:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,128 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: why do this instead of storing the result of the`interfacestore.NewContainerInterface` calls above? I just find it confusing that we seem to be building the `interfacestore.InterfaceConfig` objects twice.
created_at: 2025-02-25T18:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +224,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: look for existing uses of `PollUntilContextTimeout`. When using this function the first parameter should be `context.Background()`. Here you are defining the timeout twice (once in the context and once using parameters to the function).
created_at: 2025-02-25T18:59:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +224,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: there is a context parameter to the condition function, you should use that instead of `context.TODO()`
created_at: 2025-02-25T18:59:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,6 +224,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: s/nad/netAttachDef
everywhere in this function
created_at: 2025-02-25T19:01:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1683,6 +1683,20 @@ func (data *TestData) PatchPod(namespace, name string, patch []byte) error {
 	return nil
 }
 
+func (data *TestData) GetCRDClient() (crdclientset.Interface, error) {
+	if data.crdClient == nil {
+		return nil, fmt.Errorf("CRD client is not initialized")
+	}
+	return data.crdClient, nil
+}
+
+func (data *TestData) GetKubeConfig() (*restclient.Config, error) {
+	if data.kubeConfig == nil {
+		return nil, fmt.Errorf("kubeconfig is not initialized")
+	}
+	return data.kubeConfig, nil
+}
path: test/e2e/framework.go
user: login: antoninbas
body: I don't think this is needed. All our existing test cases use `data.crdClient` and `data.kubeConfig` directly. It's up to the testing framework to ensure that these are initialized successfully. Am I missing something?
created_at: 2025-02-25T19:02:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1683,6 +1683,20 @@ func (data *TestData) PatchPod(namespace, name string, patch []byte) error {
 	return nil
 }
 
+func (data *TestData) GetCRDClient() (crdclientset.Interface, error) {
+	if data.crdClient == nil {
+		return nil, fmt.Errorf("CRD client is not initialized")
+	}
+	return data.crdClient, nil
+}
+
+func (data *TestData) GetKubeConfig() (*restclient.Config, error) {
+	if data.kubeConfig == nil {
+		return nil, fmt.Errorf("kubeconfig is not initialized")
+	}
+	return data.kubeConfig, nil
+}
path: test/e2e/framework.go
user: login: KMAnju-2021
body: Actually i created these getter functions because fields are private I cannot access it directly?
created_at: 2025-02-26T08:29:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1683,6 +1683,20 @@ func (data *TestData) PatchPod(namespace, name string, patch []byte) error {
 	return nil
 }
 
+func (data *TestData) GetCRDClient() (crdclientset.Interface, error) {
+	if data.crdClient == nil {
+		return nil, fmt.Errorf("CRD client is not initialized")
+	}
+	return data.crdClient, nil
+}
+
+func (data *TestData) GetKubeConfig() (*restclient.Config, error) {
+	if data.kubeConfig == nil {
+		return nil, fmt.Errorf("kubeconfig is not initialized")
+	}
+	return data.kubeConfig, nil
+}
path: test/e2e/framework.go
user: login: antoninbas
body: you should make the fields that you need public. Actually `KubeConfig` is already 

I have noticed that the SRIOV test creates a new client, using `testData.KubeConfig`, which doesn't make sense to me as we should just be able to use the existing client: https://github.com/antrea-io/antrea/blob/61f55bbf9e9be049162e218a39786a4f41cb08bc/test/e2e-secondary-network/secondary_network_test.go#L339-L342

Anyway, just like `data.KubeConfig` is exported now (you should rebase your PR), we can export `data.ClientSet` and `data.CrdClient`
created_at: 2025-02-26T18:57:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +225,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Maybe better to save the client to testData. This is minor and up to you.
created_at: 2025-02-28T01:17:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +225,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+		if err != nil {
+			return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName, err)
+		}
+
+		for _, ipAddress := range ipPool.Status.IPAddresses {
+			if podIPString == ipAddress.IPAddress {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	})
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforeIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	beforeIP, exists := beforeIPs[iface]
+	if !exists || beforeIP == nil {
+		return fmt.Errorf("no IP found for interface %s before agent restart", iface)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	afterIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	afterIP, exists := afterIPs[iface]
+	if !exists || afterIP == nil || !beforeIP.Equal(afterIP) {
+		return fmt.Errorf("OVS port/IP mismatch after agent restart: before=%v, after=%v, iface=%v", beforeIP, afterIP, iface)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I remember earlier you checked OVS ports exist too, but you now removed that? Why?
created_at: 2025-02-28T01:20:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +225,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+		if err != nil {
+			return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName, err)
+		}
+
+		for _, ipAddress := range ipPool.Status.IPAddresses {
+			if podIPString == ipAddress.IPAddress {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	})
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforeIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	beforeIP, exists := beforeIPs[iface]
+	if !exists || beforeIP == nil {
+		return fmt.Errorf("no IP found for interface %s before agent restart", iface)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	afterIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	afterIP, exists := afterIPs[iface]
+	if !exists || afterIP == nil || !beforeIP.Equal(afterIP) {
+		return fmt.Errorf("OVS port/IP mismatch after agent restart: before=%v, after=%v, iface=%v", beforeIP, afterIP, iface)
+	}
+
+	ipPools, err := data.getIPPoolNames(vlanPod.interfaceNetworks[iface], "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	return data.checkIPReleased(ipPools[0], beforeIP.String())
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Could we check if OVS ports are removed?
created_at: 2025-02-28T01:21:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +225,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+		if err != nil {
+			return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName, err)
+		}
+
+		for _, ipAddress := range ipPool.Status.IPAddresses {
+			if podIPString == ipAddress.IPAddress {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	})
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforeIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	beforeIP, exists := beforeIPs[iface]
+	if !exists || beforeIP == nil {
+		return fmt.Errorf("no IP found for interface %s before agent restart", iface)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	afterIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	afterIP, exists := afterIPs[iface]
+	if !exists || afterIP == nil || !beforeIP.Equal(afterIP) {
+		return fmt.Errorf("OVS port/IP mismatch after agent restart: before=%v, after=%v, iface=%v", beforeIP, afterIP, iface)
+	}
+
+	ipPools, err := data.getIPPoolNames(vlanPod.interfaceNetworks[iface], "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	return data.checkIPReleased(ipPools[0], beforeIP.String())
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: yes refactoring the code, will update the PR.
created_at: 2025-02-28T18:42:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,128 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: KMAnju-2021
body: corrected
created_at: 2025-03-01T18:23:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -255,7 +255,7 @@ type TestData struct {
 	KubeConfig         *restclient.Config
 	clientset          kubernetes.Interface
 	aggregatorClient   aggregatorclientset.Interface
-	crdClient          crdclientset.Interface
+	CrdClient          crdclientset.Interface
path: test/e2e/framework.go
user: login: jianjuns
body: CRDClient - we use all upper cases for abbrs.
created_at: 2025-03-02T05:22:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Fix the gofmt errors.
created_at: 2025-03-02T05:22:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CrdClient
+	
+	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I feel we should not wait for that long. At most 10 seconds, and m maybe poll every 1 seconds.
created_at: 2025-03-02T05:32:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CrdClient
+	
+	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+
+	beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+	}
+
+	afterAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+		return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", vlanPod.podName)
+	}
+
+	logs.Infof("Pod IPs and Interfaces before agent restart:")
+	for iface, ip := range beforeAgentRestartIPsAndIfaces {
+		logs.Infof("  Interface: %s, IP: %s", iface, ip)
+	}
+
+	logs.Infof("Pod IPs and Interfaces after agent restart:")
+	for iface, ip := range afterAgentRestartIPsAndIfaces {
+		logs.Infof("  Interface: %s, IP: %s", iface, ip)
+	}
+
+	ipPools, err := data.getIPPoolNames(interfaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	return data.checkIPReleased(ipPools, beforeAgentRestartIPsAndIfaces, interfaces)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: A reminder for checking OVS ports deleted.
created_at: 2025-03-02T05:33:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CrdClient
+	
+	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+
+	beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+	}
+
+	afterAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Can we check all Pods here (but fine to just test deleting only one Pod later)?
created_at: 2025-03-02T05:35:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,128 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+		cniCache:              sync.Map{},
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "ns1", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "ns2", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "ns3", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid3,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	// Interface configurations
+	iface1 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort1, &interfacestore.OVSPortConfig{PortUUID: ovsPort1.UUID, OFPort: ovsPort1.OFPort})
+	iface2 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort2, &interfacestore.OVSPortConfig{PortUUID: ovsPort2.UUID, OFPort: ovsPort2.OFPort})
+	iface3 := cniserver.ParseOVSPortInterfaceConfig(&ovsPort3, &interfacestore.OVSPortConfig{PortUUID: ovsPort3.UUID, OFPort: ovsPort3.OFPort})
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: KMAnju-2021
body: I modified the code  to use  interfacestore.NewContainerInterface  to create containerConfig but I need set oFPort manually in reconcile function before storing it inside primary and secondary Interfacestore, because it was showing nil pointer error.
created_at: 2025-03-02T10:10:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +225,110 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getNetAttachDefClient returns a NetAttachDef client.
+func (data *testData) getNetAttachDefClient() (netattdef.Interface, error) {
+	config, err := data.e2eTestData.GetKubeConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
+	}
+	return netattdef.NewForConfig(config)
+}
+
+// getIPPoolNames retrieves the IPPool names from a NetworkAttachmentDefinition.
+func (data *testData) getIPPoolNames(networkName, namespace string) ([]string, error) {
+	client, err := data.getNetAttachDefClient()
+	if err != nil {
+		return nil, err
+	}
+
+	nad, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s: %w", networkName, err)
+	}
+
+	var nadConfig struct {
+		IPAM struct {
+			IPPools []string `json:"ippools"`
+		} `json:"ipam"`
+	}
+
+	if err := json.Unmarshal([]byte(nad.Spec.Config), &nadConfig); err != nil {
+		return nil, fmt.Errorf("failed to parse NAD config JSON: %w", err)
+	}
+
+	return nadConfig.IPAM.IPPools, nil
+}
+
+// checkIPReleased verifies if the VLAN IP is released.
+func (data *testData) checkIPReleased(ipPoolName, podIPString string) error {
+	crdClient, err := data.e2eTestData.GetCRDClient()
+	if err != nil {
+		return fmt.Errorf("failed to get CRD client: %w", err)
+	}
+
+	// Poll every 5 seconds for up to 40 seconds
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+	defer cancel()
+
+	return wait.PollUntilContextTimeout(ctx, 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		ipPool, err := crdClient.CrdV1beta1().IPPools().Get(context.TODO(), ipPoolName, metav1.GetOptions{})
+		if err != nil {
+			return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName, err)
+		}
+
+		for _, ipAddress := range ipPool.Status.IPAddresses {
+			if podIPString == ipAddress.IPAddress {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	})
+}
+
+// reconcilationAfterAgentRestart verifies OVS cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	iface := "eth1"
+
+	beforeIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	beforeIP, exists := beforeIPs[iface]
+	if !exists || beforeIP == nil {
+		return fmt.Errorf("no IP found for interface %s before agent restart", iface)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent pods: %v", err)
+	}
+
+	afterIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	afterIP, exists := afterIPs[iface]
+	if !exists || afterIP == nil || !beforeIP.Equal(afterIP) {
+		return fmt.Errorf("OVS port/IP mismatch after agent restart: before=%v, after=%v, iface=%v", beforeIP, afterIP, iface)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: added for all pods, thanks
created_at: 2025-03-02T10:16:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CrdClient
+	
+	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+
+	beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+	}
+
+	afterAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: done
created_at: 2025-03-02T10:24:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,133 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface 
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CrdClient
+	
+	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 40*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+
+	beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+	}
+
+	// Restarting the Antrea agent
+	if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+		t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+	}
+
+	afterAgentRestartIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+	}
+
+	if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+		return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", vlanPod.podName)
+	}
+
+	logs.Infof("Pod IPs and Interfaces before agent restart:")
+	for iface, ip := range beforeAgentRestartIPsAndIfaces {
+		logs.Infof("  Interface: %s, IP: %s", iface, ip)
+	}
+
+	logs.Infof("Pod IPs and Interfaces after agent restart:")
+	for iface, ip := range afterAgentRestartIPsAndIfaces {
+		logs.Infof("  Interface: %s, IP: %s", iface, ip)
+	}
+
+	ipPools, err := data.getIPPoolNames(interfaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	return data.checkIPReleased(ipPools, beforeAgentRestartIPsAndIfaces, interfaces)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: done
created_at: 2025-03-02T10:48:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,146 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I feel you comments are too long. We generally wrap comments at 80. You may also do 100, 120, but not too long.
created_at: 2025-03-03T19:37:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,146 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+
+		logs.Infof("Pod %v IPs before agent restart:", pod.podName)
+		for iface, ip := range beforeAgentRestartIPsAndIfaces {
+			logs.Infof("  Interface: %s, IP: %s", iface, ip)
+		}
+
+		logs.Infof("Pod %v IPs after agent restart:", pod.podName)
+		for iface, ip := range afterAgentRestartIPsAndIfaces {
+			logs.Infof("  Interface: %s, IP: %s", iface, ip)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+	beforePodDeletionIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(interfaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+	afterPodDeletionIPsAndIfacess, err := data.listPodIPs(vlanPod)
+	if len(afterPodDeletionIPsAndIfacess) != 0 || err == nil {
+		return fmt.Errorf("Pod interfaces and IP still exist")
+	}
+
+	return data.checkIPReleased(ipPools, beforePodDeletionIPsAndIfaces, interfaces)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Still I did not see you check OVS ports are deleted?
created_at: 2025-03-03T19:42:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -521,3 +531,79 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: You meant this can happen in a non-UT agent run? How can? pc.ovsBridgeClient was already set before initializeSecondaryInterfaceStore()
created_at: 2025-03-03T19:54:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -521,3 +531,79 @@ func checkForPodSecondaryNetworkAttachement(pod *corev1.Pod) (string, bool) {
 		return netObj, false
 	}
 }
+
+// initializeSecondaryInterfaceStore restores secondary interfaceStore when agent restarts.
+func (pc *PodController) initializeSecondaryInterfaceStore() error {
+	if pc.ovsBridgeClient == nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: As we talked offline, this can happen when secondary bridge is not configured, meaning no VLAN interfaces at all. In this case, we should skip both initializeSecondaryInterfaceStore and reconcileSecondaryInterfaces. And I suggest doing this in NewPodController()
created_at: 2025-03-03T20:32:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -134,6 +135,15 @@ func NewPodController(
 		},
 		resyncPeriod,
 	)
+
+	if err := pc.initializeSecondaryInterfaceStore(); err != nil {
path: pkg/agent/secondarynetwork/podwatch/controller.go
user: login: jianjuns
body: Probably let us check ovsBridgeClient != nil here, and otherwise skip initializeSecondaryInterfaceStore and reconcileSecondaryInterfaces. Please add a comment explaining this happen when secondary bridge is not configured.
created_at: 2025-03-03T20:33:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,146 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(interfaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range interfaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, podIPsAndIfaces map[string]net.IP, interfaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range interfaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := podIPsAndIfaces[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+// reconcilationAfterAgentRestart verifies Interface cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+
+		logs.Infof("Pod %v IPs before agent restart:", pod.podName)
+		for iface, ip := range beforeAgentRestartIPsAndIfaces {
+			logs.Infof("  Interface: %s, IP: %s", iface, ip)
+		}
+
+		logs.Infof("Pod %v IPs after agent restart:", pod.podName)
+		for iface, ip := range afterAgentRestartIPsAndIfaces {
+			logs.Infof("  Interface: %s, IP: %s", iface, ip)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	interfaces := []string{"eth1", "eth2"}
+	beforePodDeletionIPsAndIfaces, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get Pod IP: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(interfaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+	afterPodDeletionIPsAndIfacess, err := data.listPodIPs(vlanPod)
+	if len(afterPodDeletionIPsAndIfacess) != 0 || err == nil {
+		return fmt.Errorf("Pod interfaces and IP still exist")
+	}
+
+	return data.checkIPReleased(ipPools, beforePodDeletionIPsAndIfaces, interfaces)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: my bad,  done please check.
created_at: 2025-03-04T20:55:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -232,14 +390,44 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 
 	testData := &testData{e2eTestData: e2eTestData, networkType: networkType, pods: pods}
 
-	err = testData.createPods(t, e2eTestData.GetTestNamespace())
-	if err != nil {
-		t.Fatalf("Error when create test Pods: %v", err)
-	}
-	err = testData.pingBetweenInterfaces(t)
-	if err != nil {
-		t.Fatalf("Error when pinging between interfaces: %v", err)
+	t.Run("testCreateTestPodOnNode", func(t *testing.T) {
+		testData.createPods(t, e2eTestData.GetTestNamespace())
+	})
+	t.Run("testpingBetweenInterfaces", func(t *testing.T) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: testping -> testPing
created_at: 2025-03-04T22:17:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -232,14 +390,44 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 
 	testData := &testData{e2eTestData: e2eTestData, networkType: networkType, pods: pods}
 
-	err = testData.createPods(t, e2eTestData.GetTestNamespace())
-	if err != nil {
-		t.Fatalf("Error when create test Pods: %v", err)
-	}
-	err = testData.pingBetweenInterfaces(t)
-	if err != nil {
-		t.Fatalf("Error when pinging between interfaces: %v", err)
+	t.Run("testCreateTestPodOnNode", func(t *testing.T) {
+		testData.createPods(t, e2eTestData.GetTestNamespace())
+	})
+	t.Run("testpingBetweenInterfaces", func(t *testing.T) {
+		err := testData.pingBetweenInterfaces(t)
+		if err != nil {
+			t.Fatalf("Error when pinging between interfaces: %v", err)
+		}
+	})
+	t.Run("testreconcilationAfterAgentRestart", func(t *testing.T) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: testReconcilationAfterAgentRestart
created_at: 2025-03-04T22:18:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Still good to keep the earlier code of checking interfaces and IPs. Why remove that code?
created_at: 2025-03-04T22:19:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports After Pod deletion: %w", err)
+	}
+
+	if checkOVSPorts(ovsPortsBeforeDeletion, ovsPortsAfterDeletion) {
+		return fmt.Errorf("OVS Ports for Vlan Pod still exists: %w", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Vlan -> VLAN

exists -> exist
created_at: 2025-03-04T22:20:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports After Pod deletion: %w", err)
+	}
+
+	if checkOVSPorts(ovsPortsBeforeDeletion, ovsPortsAfterDeletion) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Could we check the exact the OVS port was deleted? We can look up the port by the name right?
created_at: 2025-03-04T22:21:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Maybe first check the sizes of the two slices match.
created_at: 2025-03-04T22:22:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: I thought we need to just check ovs ports , again I added that, thanks!
created_at: 2025-03-05T06:06:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: I am using it for both cases to check ovs ports maybe I can check sizes before calling this function.
created_at: 2025-03-05T06:28:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports After Pod deletion: %w", err)
+	}
+
+	if checkOVSPorts(ovsPortsBeforeDeletion, ovsPortsAfterDeletion) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: Actually ovs ports are like this vlan-pod-(*) for all pods just id/suffix are different then how to check exact pod port ? in my code I am checking:
1. sizes 
2.  after deletion check if any  ports deleted, list size will change, maybe I can log pod ovs ports before and after deletion to verify? 
ovsports:
[eth1 eth1~ vlan-pod-4edc1d vlan-pod-c3600d vlan-pod-cb137b vlan-pod-d6d8d4]
created_at: 2025-03-05T06:49:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,198 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if len(beforeAgentRestartIPsAndIfaces) != len(afterAgentRestartIPsAndIfaces) || !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I saw you also check the len()s in checkIPsAndIfaces()? I feel that is a better approach.
created_at: 2025-03-06T18:35:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: I do not see why you cannot check sizes in this func. That should work for the two callers in your current code.
created_at: 2025-03-06T18:38:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports After Pod deletion: %w", err)
+	}
+
+	if checkOVSPorts(ovsPortsBeforeDeletion, ovsPortsAfterDeletion) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Ok, but I think the names are generated with a deterministic method (by hashing Pod + interface name, I think). So, you can run the test once and find out the port names got deleted e.g. from a debug log, and then use the names in your code?
created_at: 2025-03-06T18:42:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	nodes := []string{data.pods[0].nodeName, data.pods[2].nodeName}
+	for _, node := range nodes {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, node)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+	}
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
+	if err != nil {
+		return fmt.Errorf("failed to get IPPool: %w", err)
+	}
+
+	// Remove Pod and check IP released or not.
+	if err := data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()); err != nil {
+		return fmt.Errorf("failed to delete Pod %s: %w", vlanPod.podName, err)
+	}
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports After Pod deletion: %w", err)
+	}
+
+	if checkOVSPorts(ovsPortsBeforeDeletion, ovsPortsAfterDeletion) {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: we discussed it offline OVS port name generated way is not deterministic , it takes containerID as an input  so the generated name can change.
created_at: 2025-03-19T04:38:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,161 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	afterSet := make(map[string]struct{})
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: done

created_at: 2025-03-19T04:39:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,201 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+
+		logs.Infof("Pod %v IPs before agent restart:", pod.podName)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: jianjuns
body: Do we need to keep all these logs?
created_at: 2025-03-19T05:34:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,201 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+
+		logs.Infof("Pod %v IPs before agent restart:", pod.podName)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: not required actually I added these logs for debugging , I can rm these logs.
created_at: 2025-03-19T12:03:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,140 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+	uuid4 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	// Create InterfaceConfig objects directly
+	containerConfig1 := interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "nsA", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)
+	containerConfig2 := interfacestore.NewContainerInterface("p2", uuid2, "Pod2", "nsA", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)
+	containerConfig3 := interfacestore.NewContainerInterface("p3", uuid3, "Pod3", "nsA", "eth0", p2NetMAC, []net.IP{p2NetIP}, 100)
+
+	ovsPort1 := ovsconfig.OVSPortData{
+		UUID: uuid1, Name: "p1", OFPort: 11,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig1))}
+
+	ovsPort2 := ovsconfig.OVSPortData{
+		UUID: uuid2, Name: "p2", OFPort: 12,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig2))}
+
+	ovsPort3 := ovsconfig.OVSPortData{
+		UUID: uuid3, Name: "p3", OFPort: -1,
+		ExternalIDs: convertExternalIDMap(cniserver.BuildOVSPortExternalIDs(
+			containerConfig3))}
+
+	ovsPort4 := ovsconfig.OVSPortData{
+		UUID:   uuid4,
+		Name:   "unknownIface",
+		OFPort: 20,
+		ExternalIDs: map[string]string{
+			"unknownKey": "unknownValue"}}
+
+	return map[string]string{"uuid1": uuid1, "uuid2": uuid2, "uuid3": uuid3, "uuid4": uuid4}, []ovsconfig.OVSPortData{ovsPort1, ovsPort2, ovsPort3, ovsPort4}, []*interfacestore.InterfaceConfig{containerConfig1, containerConfig2, containerConfig3}
+}
+
+func TestInitializeSecondaryInterfaceStore(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	pc, _, _, mockOVSBridgeClient := testPodController(ctrl)
+	uuids, ovsPorts, _ := createTestInterfaces()
+	mockOVSBridgeClient.EXPECT().GetPortList().Return(ovsPorts, nil)
+
+	err := pc.initializeSecondaryInterfaceStore()
+	require.NoError(t, err, "OVS ports list successfully")
+
+	// Validate stored interfaces
+	require.Equal(t, 3, pc.interfaceStore.Len(), "Only valid interfaces should be stored")
+
+	_, found1 := pc.interfaceStore.GetContainerInterface(uuids["uuid1"])
+	assert.True(t, found1, "Interface 1 should be stored")
+
+	_, found2 := pc.interfaceStore.GetContainerInterface(uuids["uuid2"])
+	assert.True(t, found2, "Interface 2 should be stored")
+
+	_, found3 := pc.interfaceStore.GetContainerInterface(uuids["uuid3"])
+	assert.True(t, found3, "Interface 3 should be stored")
+
+	_, found4 := pc.interfaceStore.GetContainerInterface(uuids["uuid4"])
+	assert.False(t, found4, "Unknown interface type should not be stored")
+}
+
+func TestReconcileSecondaryInterfaces(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	pc, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
+	primaryStore := interfacestore.NewInterfaceStore()
+
+	_, _, containerConfigs := createTestInterfaces()
+
+	// Ensure OVSPortConfig is initialized to avoid nil pointer dereference issues
+	// when accessing or modifying OFPort.
+	containerConfigs[0].OVSPortConfig = &interfacestore.OVSPortConfig{}
+	containerConfigs[0].OFPort = 11
+	containerConfigs[1].OVSPortConfig = &interfacestore.OVSPortConfig{}
+	containerConfigs[1].OFPort = 12
+	containerConfigs[2].OVSPortConfig = &interfacestore.OVSPortConfig{}
+	containerConfigs[2].OFPort = -1
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: it feels like this could have been done in `createTestInterfaces`?
created_at: 2025-03-21T00:30:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,146 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: you didn't address this comment @KMAnju-2021 
it seems the line is still 150+ chars
created_at: 2025-03-21T00:31:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+	}
+
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	if err != nil {
+		return fmt.Errorf("failed to get IPs of Interfaces: %w", err)
+	}
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	if err != nil {
+		return fmt.Errorf("failed to get OVS Ports Before Pod deletion: %w", err)
+	}
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, "default")
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: maybe create a `ipPoolNamespace` const, similar to the one we already have: `attachDefNamespace`
created_at: 2025-03-21T00:36:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: in other places you return an error with `return fmt.Errorf(...)`, but here you use `t.Fatalf` which is inconistent.

I feel like you should use assertions in this function (with `require` / `assert`). This would make the function shorter and also remove the need for the `checkOVSPorts` and `checkIPsAndIfaces` functions.
created_at: 2025-03-21T00:43:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: probably worth defining a constant for `br-secondary` as well
created_at: 2025-03-21T00:44:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+	}
+
+	return data.deleteVLANPod(t)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: I would recommend adding a comment here, something along these lines:

> We delete the Pod here and we make sure that the Agent has all the necessary information after the restart above to perform the secondary network interface clean up correctly.
created_at: 2025-03-21T00:47:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+	}
+
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: IMO you should merge this function into `reconcilationAfterAgentRestart`. It doesn't really make sense as a standalone function with such a generic name. If you switch to assertions as I suggested above, the functions will be shorter anyway.
created_at: 2025-03-21T00:48:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +226,191 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames retrieves the IPPool names associated with a given set of interfaces in a Pod. It queries the NetworkAttachmentDefinition for each interface
+// and extracts the list of IPPools from its IPAM configurations. It returns a map where each interface name maps to its corresponding IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func checkOVSPorts(before, after []string) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	afterSet := make(map[string]struct{})
+	for _, port := range after {
+		afterSet[port] = struct{}{}
+	}
+
+	for _, port := range before {
+		if _, exists := afterSet[port]; !exists {
+			return false
+		}
+	}
+
+	return true
+}
+
+func checkIPsAndIfaces(before, after map[string]net.IP) bool {
+	if len(before) != len(after) {
+		return false
+	}
+	for iface, beforeIP := range before {
+		afterIP, exists := after[iface]
+		if !exists || !beforeIP.Equal(afterIP) {
+			return false
+		}
+	}
+	return true
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", "br-secondary"}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) error {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports before agent restart: %w", err)
+		}
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP before agent restart: %w", err)
+		}
+
+		// Restarting the Antrea agent
+		if err := data.e2eTestData.RestartAntreaAgentPods(30 * time.Second); err != nil {
+			t.Fatalf("Failed to restart Antrea agent Pods: %v", err)
+		}
+
+		ovsPortsAfterAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		if err != nil {
+			return fmt.Errorf("failed to get Secondary bridge OVS Ports after agent restart: %w", err)
+		}
+
+		afterAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		if err != nil {
+			return fmt.Errorf("failed to get Pod IP after agent restart: %w", err)
+		}
+
+		if !checkOVSPorts(ovsPortsBeforeAgentRestart, ovsPortsAfterAgentRestart) {
+			return fmt.Errorf("Secondary bridge OVS Ports mismatch after agent restart: %w", err)
+		}
+
+		if !checkIPsAndIfaces(beforeAgentRestartIPsAndIfaces, afterAgentRestartIPsAndIfaces) {
+			return fmt.Errorf("Pod: %s, Interfaces and IPs mismatch after agent restart", pod.podName)
+		}
+	}
+
+	return data.deleteVLANPod(t)
+}
+
+func (data *testData) deleteVLANPod(t *testing.T) error {
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: updated 
created_at: 2025-03-22T19:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,136 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+	uuid4 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	// Create InterfaceConfig objects directly
+	containerConfig1 := interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "nsA", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)
+	containerConfig1.OVSPortConfig = &interfacestore.OVSPortConfig{}
+	containerConfig1.OVSPortConfig.OFPort = 11
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: antoninbas
body: use:
```go
containerConfig1.OVSPortConfig = &interfacestore.OVSPortConfig{
        OFPort: 11,
}
```
?
created_at: 2025-03-25T21:59:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,129 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames map each pod interface to its associated IPPools.
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: ```suggestion
// getIPPoolNames maps each Pod's interface to its associated IPPools.
```
created_at: 2025-03-25T22:01:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,129 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames map each pod interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports before agent restart")
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP before agent restart")
+
+		require.NoError(t, data.e2eTestData.RestartAntreaAgentPods(30*time.Second), "Failed to restart Antrea agent Pods")
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: please update the test so that you only restart the Agents once:

1) collect port data and IPs first
1) restart Agents
1) collect new data, compare

created_at: 2025-03-25T22:06:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -232,14 +362,45 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 
 	testData := &testData{e2eTestData: e2eTestData, networkType: networkType, pods: pods}
 
-	err = testData.createPods(t, e2eTestData.GetTestNamespace())
-	if err != nil {
-		t.Fatalf("Error when create test Pods: %v", err)
-	}
-	err = testData.pingBetweenInterfaces(t)
-	if err != nil {
-		t.Fatalf("Error when pinging between interfaces: %v", err)
+	t.Run("testCreateTestPodOnNode", func(t *testing.T) {
+		err := testData.createPods(t, e2eTestData.GetTestNamespace())
+		if err != nil {
+			t.Fatalf("Error when create test Pods: %v", err)
+		}
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: let's use `require.Error` here as well for conciseness
created_at: 2025-03-25T22:07:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -232,14 +362,45 @@ func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo)
 
 	testData := &testData{e2eTestData: e2eTestData, networkType: networkType, pods: pods}
 
-	err = testData.createPods(t, e2eTestData.GetTestNamespace())
-	if err != nil {
-		t.Fatalf("Error when create test Pods: %v", err)
-	}
-	err = testData.pingBetweenInterfaces(t)
-	if err != nil {
-		t.Fatalf("Error when pinging between interfaces: %v", err)
+	t.Run("testCreateTestPodOnNode", func(t *testing.T) {
+		err := testData.createPods(t, e2eTestData.GetTestNamespace())
+		if err != nil {
+			t.Fatalf("Error when create test Pods: %v", err)
+		}
+	})
+	t.Run("testPingBetweenInterfaces", func(t *testing.T) {
+		err := testData.pingBetweenInterfaces(t)
+		if err != nil {
+			t.Fatalf("Error when pinging between interfaces: %v", err)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: ditto
created_at: 2025-03-25T22:07:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1005,18 +1014,136 @@ func testPodController(ctrl *gomock.Controller) (
 		interfaceConfigurator: interfaceConfigurator,
 		ipamAllocator:         mockIPAM,
 		interfaceStore:        interfacestore.NewInterfaceStore(),
-	}, mockIPAM, interfaceConfigurator
+	}, mockIPAM, interfaceConfigurator, mockOVSBridgeClient
 }
 
 // Create a test PodController and start informerFactory.
 func testPodControllerStart(ctrl *gomock.Controller) (
 	*PodController, *podwatchtesting.MockIPAMAllocator,
 	*podwatchtesting.MockInterfaceConfigurator) {
-	podController, mockIPAM, interfaceConfigurator := testPodController(ctrl)
+	podController, mockIPAM, interfaceConfigurator, _ := testPodController(ctrl)
 	informerFactory := informers.NewSharedInformerFactory(podController.kubeClient, resyncPeriod)
 	podController.podInformer = informerFactory.Core().V1().Pods().Informer()
 	stopCh := make(chan struct{})
 	informerFactory.Start(stopCh)
 	informerFactory.WaitForCacheSync(stopCh)
 	return podController, mockIPAM, interfaceConfigurator
 }
+
+func convertExternalIDMap(in map[string]interface{}) map[string]string {
+	out := make(map[string]string, len(in))
+	for k, v := range in {
+		out[k] = v.(string)
+	}
+	return out
+}
+
+func createTestInterfaces() (map[string]string, []ovsconfig.OVSPortData, []*interfacestore.InterfaceConfig) {
+	uuid1 := uuid.New().String()
+	uuid2 := uuid.New().String()
+	uuid3 := uuid.New().String()
+	uuid4 := uuid.New().String()
+
+	p1MAC, p1IP := "11:22:33:44:55:66", "192.168.1.10"
+	p2MAC, p2IP := "11:22:33:44:55:77", "192.168.1.11"
+
+	p1NetMAC, _ := net.ParseMAC(p1MAC)
+	p1NetIP := net.ParseIP(p1IP)
+	p2NetMAC, _ := net.ParseMAC(p2MAC)
+	p2NetIP := net.ParseIP(p2IP)
+
+	// Create InterfaceConfig objects directly
+	containerConfig1 := interfacestore.NewContainerInterface("p1", uuid1, "Pod1", "nsA", "eth0", p1NetMAC, []net.IP{p1NetIP}, 100)
+	containerConfig1.OVSPortConfig = &interfacestore.OVSPortConfig{}
+	containerConfig1.OVSPortConfig.OFPort = 11
path: pkg/agent/secondarynetwork/podwatch/controller_test.go
user: login: KMAnju-2021
body: yes, I can updated it.
created_at: 2025-03-26T10:26:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,129 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames map each pod interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	for _, pod := range data.pods {
+		ovsPortsBeforeAgentRestart, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports before agent restart")
+
+		beforeAgentRestartIPsAndIfaces, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP before agent restart")
+
+		require.NoError(t, data.e2eTestData.RestartAntreaAgentPods(30*time.Second), "Failed to restart Antrea agent Pods")
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: updated
created_at: 2025-03-26T13:32:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,147 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames maps each Pod's interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	ovsPortsBeforeAgentRestart := make(map[string][]string)
+	beforeAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: use consistent variable names
created_at: 2025-03-26T22:31:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,147 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames maps each Pod's interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	ovsPortsBeforeAgentRestart := make(map[string][]string)
+	beforeAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports before agent restart")
+		ovsPortsBeforeAgentRestart[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP before agent restart")
+		beforeAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	require.NoError(t, data.e2eTestData.RestartAntreaAgentPods(30*time.Second), "Failed to restart Antrea agent Pods")
+
+	ovsPortsAfterAgentRestart := make(map[string][]string)
+	afterAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports after agent restart")
+		ovsPortsAfterAgentRestart[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP after agent restart")
+		afterAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	// Compare OVS ports before and after restart
+	for nodeName, portsBefore := range ovsPortsBeforeAgentRestart {
+		assert.ElementsMatch(t, portsBefore, ovsPortsAfterAgentRestart[nodeName],
+			"Secondary bridge OVS Ports mismatch after agent restart on node %s", nodeName)
+	}
+
+	// Compare IPs and interfaces before and after restart
+	for podName, ipsBefore := range beforeAgentRestartIPsAndIfaces {
+		assert.Equal(t, ipsBefore, afterAgentRestartIPsAndIfaces[podName],
+			"Pod: %s, Interfaces and IPs mismatch after agent restart", podName)
+	}
+
+	// Remove Pod and check IP released or not.
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	require.NoError(t, err, "Failed to get IPs of Interfaces")
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports Before Pod deletion")
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, ipPoolNamespace)
+	require.NoError(t, err, "Failed to get IPPool")
+
+	require.NoError(t, data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()), "Failed to delete Pod")
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports After Pod deletion")
+
+	assert.NotEqual(t, ovsPortsBeforeDeletion, ovsPortsAfterDeletion, "OVS Ports for VLAN Pod still exist")
+
+	data.checkIPReleased(ipPools, ifacesIPs, ifaces)
+
path: test/e2e-secondary-network/secondary_network_test.go
user: login: antoninbas
body: remove
created_at: 2025-03-26T22:31:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,6 +230,147 @@ func (data *testData) pingBetweenInterfaces(t *testing.T) error {
 	return nil
 }
 
+// getIPPoolNames maps each Pod's interface to its associated IPPools.
+func (data *testData) getIPPoolNames(ifaces []string, vlanPod *testPodInfo, namespace string) (map[string][]string, error) {
+	client, err := netattdef.NewForConfig(data.e2eTestData.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	ipPoolsMap := make(map[string][]string)
+
+	for _, iface := range ifaces {
+		networkName, exists := vlanPod.interfaceNetworks[iface]
+		if !exists {
+			return nil, fmt.Errorf("network name not found for interface %s", iface)
+		}
+		netAttachDef, err := client.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), networkName, metav1.GetOptions{})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get NetworkAttachmentDefinition %s for interface %s: %w", networkName, iface, err)
+		}
+
+		var netAttachDefConfig struct {
+			IPAM struct {
+				IPPools []string `json:"ippools"`
+			} `json:"ipam"`
+		}
+
+		if err := json.Unmarshal([]byte(netAttachDef.Spec.Config), &netAttachDefConfig); err != nil {
+			return nil, fmt.Errorf("failed to parse NetworkAttachmentDefinition config JSON for interface %s: %w", iface, err)
+		}
+
+		ipPoolsMap[iface] = netAttachDefConfig.IPAM.IPPools
+
+	}
+	return ipPoolsMap, nil
+}
+
+func (data *testData) checkIPReleased(ipPools map[string][]string, ifacesIPs map[string]net.IP, ifaces []string) error {
+	crdClient := data.e2eTestData.CRDClient
+
+	return wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		for _, iface := range ifaces {
+			ipPoolName, exists := ipPools[iface]
+			if !exists {
+				return false, fmt.Errorf("no IPPool found for interface %s", iface)
+			}
+
+			podIP, exists := ifacesIPs[iface]
+			if !exists {
+				return false, fmt.Errorf("no IP found for interface %s", iface)
+			}
+
+			ipPool, err := crdClient.CrdV1beta1().IPPools().Get(ctx, ipPoolName[0], metav1.GetOptions{})
+			if err != nil {
+				return false, fmt.Errorf("failed to get IPPool %s: %w", ipPoolName[0], err)
+			}
+
+			for _, ipAddress := range ipPool.Status.IPAddresses {
+				if podIP.String() == ipAddress.IPAddress {
+					return false, nil
+				}
+			}
+			logs.Infof("Released PodIP: %v, Interface: %s", podIP, iface)
+		}
+		return true, nil
+	})
+}
+
+func (data *testData) getOVSPortsOnSecondaryBridge(t *testing.T, nodeName string) ([]string, error) {
+	cmd := []string{"ovs-vsctl", "list-ports", secondaryOVSBridge}
+
+	stdout, stderr, err := data.e2eTestData.RunCommandFromAntreaPodOnNode(nodeName, cmd)
+	if err != nil {
+		return nil, fmt.Errorf("failed to run ovs-vsctl on node %s: %v\nstderr: %s", nodeName, err, stderr)
+	}
+
+	ovsPorts := strings.Fields(stdout)
+	return ovsPorts, nil
+}
+
+// reconcilationAfterAgentRestart verifies OVS Ports cleanup and IP release.
+func (data *testData) reconcilationAfterAgentRestart(t *testing.T) {
+	ovsPortsBeforeAgentRestart := make(map[string][]string)
+	beforeAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports before agent restart")
+		ovsPortsBeforeAgentRestart[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP before agent restart")
+		beforeAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	require.NoError(t, data.e2eTestData.RestartAntreaAgentPods(30*time.Second), "Failed to restart Antrea agent Pods")
+
+	ovsPortsAfterAgentRestart := make(map[string][]string)
+	afterAgentRestartIPsAndIfaces := make(map[string]map[string]net.IP)
+	for _, pod := range data.pods {
+		ports, err := data.getOVSPortsOnSecondaryBridge(t, pod.nodeName)
+		require.NoError(t, err, "Failed to get Secondary bridge OVS Ports after agent restart")
+		ovsPortsAfterAgentRestart[pod.nodeName] = ports
+
+		ips, err := data.listPodIPs(pod)
+		require.NoError(t, err, "Failed to get Pod IP after agent restart")
+		afterAgentRestartIPsAndIfaces[pod.podName] = ips
+	}
+
+	// Compare OVS ports before and after restart
+	for nodeName, portsBefore := range ovsPortsBeforeAgentRestart {
+		assert.ElementsMatch(t, portsBefore, ovsPortsAfterAgentRestart[nodeName],
+			"Secondary bridge OVS Ports mismatch after agent restart on node %s", nodeName)
+	}
+
+	// Compare IPs and interfaces before and after restart
+	for podName, ipsBefore := range beforeAgentRestartIPsAndIfaces {
+		assert.Equal(t, ipsBefore, afterAgentRestartIPsAndIfaces[podName],
+			"Pod: %s, Interfaces and IPs mismatch after agent restart", podName)
+	}
+
+	// Remove Pod and check IP released or not.
+	vlanPod := data.pods[1]
+	ifaces := []string{"eth1", "eth2"}
+	ifacesIPs, err := data.listPodIPs(vlanPod)
+	require.NoError(t, err, "Failed to get IPs of Interfaces")
+
+	ovsPortsBeforeDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports Before Pod deletion")
+
+	ipPools, err := data.getIPPoolNames(ifaces, vlanPod, ipPoolNamespace)
+	require.NoError(t, err, "Failed to get IPPool")
+
+	require.NoError(t, data.e2eTestData.DeletePodAndWait(defaultTimeout, vlanPod.podName, data.e2eTestData.GetTestNamespace()), "Failed to delete Pod")
+
+	ovsPortsAfterDeletion, err := data.getOVSPortsOnSecondaryBridge(t, vlanPod.nodeName)
+	require.NoError(t, err, "Failed to get OVS Ports After Pod deletion")
+
+	assert.NotEqual(t, ovsPortsBeforeDeletion, ovsPortsAfterDeletion, "OVS Ports for VLAN Pod still exist")
+
+	data.checkIPReleased(ipPools, ifacesIPs, ifaces)
+
path: test/e2e-secondary-network/secondary_network_test.go
user: login: KMAnju-2021
body: Hi @antoninbas , you mean to remove assert and  checkIPReleased function?
created_at: 2025-03-27T05:03:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: luolanzone
created_at: 2025-01-07T02:37:28Z
body: @KMAnju-2021 I didn't see any new tests for the patch, re-post Antonin's comment: 

> new tests are needed under `test/e2e-secondary-network` to validate this patch


author_association: CONTRIBUTOR
user: login: jianjuns
created_at: 2025-02-22T01:03:51Z
body: I feel "Reconcile secondary network OVS ports after Agent restart" is a better name for the PR/commit.
author_association: CONTRIBUTOR
user: login: jianjuns
created_at: 2025-03-19T16:41:50Z
body:  /test-e2e
author_association: CONTRIBUTOR
user: login: KMAnju-2021
created_at: 2025-03-27T19:00:51Z
body: /test-sriov-secondary-network-e2e
author_association: CONTRIBUTOR
user: login: KMAnju-2021
created_at: 2025-03-27T19:05:20Z
body: /test-sriov-secondary-network-e2e
author_association: CONTRIBUTOR
user: login: antoninbas
created_at: 2025-03-27T19:35:11Z
body: @jianjuns @luolanzone @rajnkamr I see the `action/backport` label, but do we really want to backport this? We still have some key fixes required for the SecondaryNetwork feature (see https://github.com/antrea-io/antrea/issues/7065). Unless we also plan to backport these additional fixes, we probably don't need to backport this one.
author_association: CONTRIBUTOR
user: login: rajnkamr
created_at: 2025-03-28T08:52:37Z
body: > @jianjuns @luolanzone @rajnkamr I see the `action/backport` label, but do we really want to backport this? We still have some key fixes required for the SecondaryNetwork feature (see #7065). Unless we also plan to backport these additional fixes, we probably don't need to backport this one.

Back porting not required for this PR , removed backport tag, we can plan when all fixes are available to be backported for secondary network. Also the PR resolves #6853 #5623 which are n't reported by end user.
author_association: CONTRIBUTOR
user: login: wenqiq
created_at: 2025-03-28T11:33:51Z
body: /test-sriov-secondary-network-e2e
author_association: CONTRIBUTOR
user: login: wenqiq
created_at: 2025-03-28T11:42:10Z
body: /test-sriov-secondary-network-e2e
author_association: CONTRIBUTOR