diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index 56e627ca4c6..797535a5368 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..ae726d6c6f1 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,15 @@ func NewPodController( }, resyncPeriod, ) + + 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 +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 { + klog.InfoS("Primary interfaceStore is nil, skipping reconciliation for Secondary Network") + return nil + } + + 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..d3a7bdcb722 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 ( @@ -220,7 +224,7 @@ func TestPodControllerRun(t *testing.T) { client, netdefclient, informerFactory.Core().V1().Pods().Informer(), - nil, nil) + nil, nil, nil) podController.interfaceConfigurator = interfaceConfigurator podController.ipamAllocator = mockIPAM cniCache := &podController.cniCache @@ -968,7 +972,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 +988,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 +1011,15 @@ 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{}) @@ -1020,3 +1027,129 @@ 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 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") +} diff --git a/test/e2e-secondary-network/secondary_network_test.go b/test/e2e-secondary-network/secondary_network_test.go index 262069f137b..1e1702f107d 100644 --- a/test/e2e-secondary-network/secondary_network_test.go +++ b/test/e2e-secondary-network/secondary_network_test.go @@ -15,15 +15,19 @@ 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" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" antreae2e "antrea.io/antrea/test/e2e" ) @@ -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) + } + + return data.checkIPReleased(ipPools[0], podIP.String()) +} + func testSecondaryNetwork(t *testing.T, networkType string, pods []*testPodInfo) { e2eTestData, err := antreae2e.SetupTest(t) if err != nil { @@ -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) + } + }) } func TestSriovNetwork(t *testing.T) { diff --git a/test/e2e/framework.go b/test/e2e/framework.go index 8064723c2f2..b43631de293 100644 --- a/test/e2e/framework.go +++ b/test/e2e/framework.go @@ -1683,6 +1683,30 @@ func (data *TestData) PatchPod(namespace, name string, patch []byte) error { return nil } +func (data *TestData) PatchDaemonSet(namespace, name string, patch []byte) error { + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + _, err := data.clientset.AppsV1().DaemonSets(namespace).Patch(context.TODO(), name, types.JSONPatchType, patch, metav1.PatchOptions{}) + return err + }); err != nil { + return err + } + 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 +} + // DeletePod deletes a Pod in the test namespace. func (data *TestData) DeletePod(namespace, name string) error { var gracePeriodSeconds int64 = 5