From 3e9f84e3b366c9c711607571a571f95c4553b842 Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Thu, 25 Jul 2024 16:40:09 +0530 Subject: [PATCH] UDN: Add vrfmanager unit tests This commit adds required unit tests for vrf manager and secondary node network controller to test add vrf, delete vrf and reconcile vrf functions. Signed-off-by: Periyasamy Palanisamy --- go-controller/hack/test-go.sh | 2 +- .../network_attach_def_controller.go | 21 ++- .../node_network_controller_manager_test.go | 98 +++++++++- .../secondary_node_network_controller_test.go | 148 +++++++++++++++ .../node/vrfmanager/vrf_manager_suite_test.go | 13 ++ .../pkg/node/vrfmanager/vrf_manager_test.go | 178 ++++++++++++++++++ go-controller/pkg/testing/net.go | 14 ++ 7 files changed, 461 insertions(+), 13 deletions(-) create mode 100644 go-controller/pkg/node/vrfmanager/vrf_manager_suite_test.go create mode 100644 go-controller/pkg/node/vrfmanager/vrf_manager_test.go diff --git a/go-controller/hack/test-go.sh b/go-controller/hack/test-go.sh index bea625081e4..003b18d15d4 100755 --- a/go-controller/hack/test-go.sh +++ b/go-controller/hack/test-go.sh @@ -72,7 +72,7 @@ function testrun { } # These packages requires root for network namespace manipulation in unit tests -root_pkgs=("github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iptables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/rulemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/controllers/egressip") +root_pkgs=("github.com/ovn-org/ovn-kubernetes/go-controller/pkg/network-controller-manager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iptables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/rulemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/controllers/egressip") # These packages are big and require more than the 10m default to run the unit tests big_pkgs=("github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn") diff --git a/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go b/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go index 624c42d94d1..0250c4e6959 100644 --- a/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go +++ b/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go @@ -71,23 +71,28 @@ func NewNetAttachDefinitionController( ncm NetworkControllerManager, wf watchFactory, ) (*NetAttachDefinitionController, error) { - nadInformer := wf.NADInformer() nadController := &NetAttachDefinitionController{ - name: fmt.Sprintf("[%s NAD controller]", name), - netAttachDefLister: nadInformer.Lister(), - networkManager: newNetworkManager(name, ncm), - networks: map[string]util.NetInfo{}, - nads: map[string]string{}, + name: fmt.Sprintf("[%s NAD controller]", name), + networkManager: newNetworkManager(name, ncm), + networks: map[string]util.NetInfo{}, + nads: map[string]string{}, } + config := &controller.ControllerConfig[nettypes.NetworkAttachmentDefinition]{ RateLimiter: workqueue.DefaultControllerRateLimiter(), - Informer: nadInformer.Informer(), - Lister: nadController.netAttachDefLister.List, Reconcile: nadController.sync, ObjNeedsUpdate: nadNeedsUpdate, // this controller is not thread safe Threadiness: 1, } + + nadInformer := wf.NADInformer() + if nadInformer != nil { + nadController.netAttachDefLister = nadInformer.Lister() + config.Informer = nadInformer.Informer() + config.Lister = nadController.netAttachDefLister.List + } + nadController.controller = controller.NewController( nadController.name, config, diff --git a/go-controller/pkg/network-controller-manager/node_network_controller_manager_test.go b/go-controller/pkg/network-controller-manager/node_network_controller_manager_test.go index beb77b42c8d..c2a71df5fdd 100644 --- a/go-controller/pkg/network-controller-manager/node_network_controller_manager_test.go +++ b/go-controller/pkg/network-controller-manager/node_network_controller_manager_test.go @@ -4,14 +4,17 @@ import ( "fmt" "sync" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" factoryMocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory/mocks" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" @@ -97,7 +100,7 @@ var _ = Describe("Healthcheck tests", func() { Describe("checkForStaleOVSRepresentorInterfaces", func() { var ncm *nodeNetworkControllerManager nodeName := "localNode" - podList := []*v1.Pod{ + podList := []*corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Name: "a-pod", @@ -105,7 +108,7 @@ var _ = Describe("Healthcheck tests", func() { Annotations: map[string]string{}, UID: "pod-a-uuid-1", }, - Spec: v1.PodSpec{ + Spec: corev1.PodSpec{ NodeName: nodeName, }, }, @@ -116,7 +119,7 @@ var _ = Describe("Healthcheck tests", func() { Annotations: map[string]string{}, UID: "pod-b-uuid-2", }, - Spec: v1.PodSpec{ + Spec: corev1.PodSpec{ NodeName: nodeName, }, }, @@ -164,5 +167,92 @@ var _ = Describe("Healthcheck tests", func() { Expect(execMock.CalledMatchesExpected()).To(BeTrue(), execMock.ErrorDesc) }) }) + + }) + + Context("verify cleanup of deleted networks", func() { + var ( + staleNetID uint = 100 + nodeName string = "worker1" + nad = ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", + types.Layer3Topology, "100.128.0.0/16", types.NetworkRolePrimary) + netName = "bluenet" + netID = 3 + v4NodeSubnet = "10.128.0.0/24" + v6NodeSubnet = "ae70::66/112" + testNS ns.NetNS + fakeClient *util.OVNClientset + ) + + BeforeEach(func() { + // Restore global default values before each testcase + Expect(config.PrepareTestConfig()).To(Succeed()) + + testNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + v1Objects := []runtime.Object{} + fakeClient = &util.OVNClientset{ + KubeClient: fake.NewSimpleClientset(v1Objects...), + } + }) + + AfterEach(func() { + Expect(testNS.Close()).To(Succeed()) + Expect(testutils.UnmountNS(testNS)).To(Succeed()) + }) + + It("check vrf devices are cleaned for deleted networks", func() { + config.OVNKubernetesFeature.EnableNetworkSegmentation = true + config.OVNKubernetesFeature.EnableMultiNetwork = true + + factoryMock := factoryMocks.NodeWatchFactory{} + NetInfo, err := util.ParseNADInfo(nad) + Expect(err).NotTo(HaveOccurred()) + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Annotations: map[string]string{ + "k8s.ovn.org/network-ids": fmt.Sprintf("{\"%s\": \"%d\"}", netName, netID), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"%s\":[\"%s\", \"%s\"]}", netName, v4NodeSubnet, v6NodeSubnet)}, + }, + } + nodeList := []*corev1.Node{node} + factoryMock.On("GetNode", nodeName).Return(nodeList[0], nil) + factoryMock.On("GetNodes").Return(nodeList, nil) + factoryMock.On("NADInformer").Return(nil) + + ncm, err := NewNodeNetworkControllerManager(fakeClient, &factoryMock, nodeName, &sync.WaitGroup{}, nil) + Expect(err).NotTo(HaveOccurred()) + + err = testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + staleVrfDevice := util.GetVRFDeviceNameForUDN(int(staleNetID)) + ovntest.AddVRFLink(staleVrfDevice, uint32(staleNetID)) + _, err = util.GetNetLinkOps().LinkByName(staleVrfDevice) + Expect(err).NotTo(HaveOccurred()) + + validVrfDevice := util.GetVRFDeviceNameForUDN(int(netID)) + ovntest.AddVRFLink(validVrfDevice, uint32(netID)) + _, err = util.GetNetLinkOps().LinkByName(validVrfDevice) + Expect(err).NotTo(HaveOccurred()) + + err = ncm.CleanupDeletedNetworks(NetInfo) + Expect(err).NotTo(HaveOccurred()) + + // Verify CleanupDeletedNetworks cleans up VRF configuration for + // already deleted network. + _, err = util.GetNetLinkOps().LinkByName(staleVrfDevice) + Expect(err).To(HaveOccurred()) + + // Verify CleanupDeletedNetworks didn't cleanup VRF configuration for + // existing network. + _, err = util.GetNetLinkOps().LinkByName(validVrfDevice) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) }) }) diff --git a/go-controller/pkg/node/secondary_node_network_controller_test.go b/go-controller/pkg/node/secondary_node_network_controller_test.go index 7e62b85e3cc..fe375c30283 100644 --- a/go-controller/pkg/node/secondary_node_network_controller_test.go +++ b/go-controller/pkg/node/secondary_node_network_controller_test.go @@ -2,9 +2,16 @@ package node import ( "context" + "fmt" + "sync" + "time" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + "github.com/vishvananda/netlink" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -13,6 +20,8 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" factoryMocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory/mocks" + kubemocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube/mocks" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/vrfmanager" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" coreinformermocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/mocks/k8s.io/client-go/informers/core/v1" v1mocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/mocks/k8s.io/client-go/listers/core/v1" @@ -24,7 +33,62 @@ var _ = Describe("SecondaryNodeNetworkController", func() { var ( nad = ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", types.Layer3Topology, "100.128.0.0/16", types.NetworkRolePrimary) + netName = "bluenet" + netID = 3 + nodeName string = "worker1" + mgtPortMAC string = "00:00:00:55:66:77" + fexec *ovntest.FakeExec + testNS ns.NetNS + vrf *vrfmanager.Controller + v4NodeSubnet = "10.128.0.0/24" + v6NodeSubnet = "ae70::66/112" + mgtPort = fmt.Sprintf("%s%d", types.K8sMgmtIntfNamePrefix, netID) + stopCh chan struct{} + wg *sync.WaitGroup + kubeMock kubemocks.Interface ) + BeforeEach(func() { + // Restore global default values before each testcase + Expect(config.PrepareTestConfig()).To(Succeed()) + // Set up a fake vsctl command mock interface + kubeMock = kubemocks.Interface{} + fexec = ovntest.NewFakeExec() + err := util.SetExec(fexec) + Expect(err).NotTo(HaveOccurred()) + // Set up a fake k8sMgmt interface + testNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + err = testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + ovntest.AddLink(mgtPort) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + wg = &sync.WaitGroup{} + stopCh = make(chan struct{}) + vrf = vrfmanager.NewController() + wg2 := &sync.WaitGroup{} + defer func() { + wg2.Wait() + }() + wg2.Add(1) + go testNS.Do(func(netNS ns.NetNS) error { + defer wg2.Done() + defer GinkgoRecover() + err = vrf.Run(stopCh, wg) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + }) + AfterEach(func() { + defer func() { + close(stopCh) + wg.Wait() + }() + Expect(testNS.Close()).To(Succeed()) + Expect(testutils.UnmountNS(testNS)).To(Succeed()) + }) + It("should return networkID from one of the nodes in the cluster", func() { fakeClient := &util.OVNNodeClientset{ KubeClient: fake.NewSimpleClientset(&corev1.Node{ @@ -156,4 +220,88 @@ var _ = Describe("SecondaryNodeNetworkController", func() { Expect(err).NotTo(HaveOccurred()) Expect(controller.gateway).To(BeNil()) }) + It("ensure UDNGateway and VRFManager is invoked for Primary UDNs when feature gate is ON", func() { + config.OVNKubernetesFeature.EnableNetworkSegmentation = true + config.OVNKubernetesFeature.EnableMultiNetwork = true + + By("creating necessary mocks") + factoryMock := factoryMocks.NodeWatchFactory{} + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Annotations: map[string]string{ + "k8s.ovn.org/network-ids": fmt.Sprintf("{\"%s\": \"%d\"}", netName, netID), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"%s\":[\"%s\", \"%s\"]}", netName, v4NodeSubnet, v6NodeSubnet)}, + }, + } + nodeList := []*corev1.Node{node} + factoryMock.On("GetNode", nodeName).Return(nodeList[0], nil) + factoryMock.On("GetNodes").Return(nodeList, nil) + nodeInformer := coreinformermocks.NodeInformer{} + factoryMock.On("NodeCoreInformer").Return(&nodeInformer) + nodeLister := v1mocks.NodeLister{} + nodeInformer.On("Lister").Return(&nodeLister) + nodeLister.On("Get", mock.AnythingOfType("string")).Return(node, nil) + cnode := node.DeepCopy() + cnode.Annotations[util.OvnNodeManagementPortMacAddresses] = `{"bluenet":"00:00:00:55:66:77"}` + kubeMock.On("UpdateNodeStatus", cnode).Return(nil) + + By("creating NAD for primary UDN") + nad = ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", + types.Layer3Topology, "100.128.0.0/16", types.NetworkRolePrimary) + NetInfo, err := util.ParseNADInfo(nad) + Expect(err).NotTo(HaveOccurred()) + + By("creating secondary network controller for user defined primary network") + cnnci := CommonNodeNetworkControllerInfo{name: nodeName, watchFactory: &factoryMock} + controller, err := NewSecondaryNodeNetworkController(&cnnci, NetInfo, vrf) + Expect(err).NotTo(HaveOccurred()) + Expect(controller.gateway).To(Not(BeNil())) + controller.gateway.kubeInterface = &kubeMock + + err = testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + getCreationFakeOVSCommands(fexec, mgtPort, mgtPortMAC, netName, nodeName, NetInfo.MTU()) + Expect(err).NotTo(HaveOccurred()) + getDeletionFakeOVSCommands(fexec, mgtPort) + Expect(err).NotTo(HaveOccurred()) + + By("starting secondary network controller for user defined primary network") + err = controller.Start(context.Background()) + Expect(err).NotTo(HaveOccurred()) + + By("check management interface and VRF device is created for the network") + vrfDeviceName := util.GetVRFDeviceNameForUDN(netID) + vrfLink, err := util.GetNetLinkOps().LinkByName(vrfDeviceName) + Expect(err).NotTo(HaveOccurred()) + Expect(vrfLink.Type()).To(Equal("vrf")) + vrfDev, ok := vrfLink.(*netlink.Vrf) + Expect(ok).To(Equal(true)) + mplink, err := util.GetNetLinkOps().LinkByName(mgtPort) + Expect(err).NotTo(HaveOccurred()) + vrfTableId := util.CalculateRouteTableID(mplink.Attrs().Index) + Expect(vrfDev.Table).To(Equal(uint32(vrfTableId))) + + By("delete VRF device explicitly and ensure VRF Manager reconciles it") + err = util.GetNetLinkOps().LinkDelete(vrfLink) + Expect(err).NotTo(HaveOccurred()) + Eventually(func() error { + _, err := util.GetNetLinkOps().LinkByName(vrfDeviceName) + return err + }).WithTimeout(120 * time.Second).Should(BeNil()) + + By("delete the network and ensure its associated VRF device is also deleted") + cnode = node.DeepCopy() + kubeMock.On("UpdateNodeStatus", cnode).Return(nil) + err = controller.Cleanup() + Expect(err).NotTo(HaveOccurred()) + Eventually(func() error { + _, err := util.GetNetLinkOps().LinkByName(vrfDeviceName) + return err + }).WithTimeout(120 * time.Second).ShouldNot(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fexec.CalledMatchesExpected()).To(BeTrue(), fexec.ErrorDesc) + }) }) diff --git a/go-controller/pkg/node/vrfmanager/vrf_manager_suite_test.go b/go-controller/pkg/node/vrfmanager/vrf_manager_suite_test.go new file mode 100644 index 00000000000..70a2b09baf5 --- /dev/null +++ b/go-controller/pkg/node/vrfmanager/vrf_manager_suite_test.go @@ -0,0 +1,13 @@ +package vrfmanager + +import ( + "testing" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +func TestAdder(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Vrf Manager Suite") +} diff --git a/go-controller/pkg/node/vrfmanager/vrf_manager_test.go b/go-controller/pkg/node/vrfmanager/vrf_manager_test.go new file mode 100644 index 00000000000..0de29904386 --- /dev/null +++ b/go-controller/pkg/node/vrfmanager/vrf_manager_test.go @@ -0,0 +1,178 @@ +package vrfmanager + +import ( + "fmt" + "sync" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + netlink_mocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/mocks/github.com/vishvananda/netlink" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/mocks" + "github.com/stretchr/testify/mock" + "github.com/vishvananda/netlink" + "k8s.io/apimachinery/pkg/util/sets" +) + +var _ = ginkgo.Describe("VRF manager", func() { + + var ( + stopCh chan struct{} + wg *sync.WaitGroup + c *Controller + vrfLinkName1 = "100-vrf" + enslaveLinkName1 = "dev100" + enslaveLinkName2 = "dev101" + vrfLinkName2 = "200-vrf" + nlMock *mocks.NetLinkOps + vrfLinkMock1 *netlink_mocks.Link + enslaveLinkMock1 *netlink_mocks.Link + enslaveLinkMock2 *netlink_mocks.Link + vrfLinkMock2 *netlink_mocks.Link + ) + + linkIndexes := map[string]int{ + vrfLinkName1: 1, + enslaveLinkName1: 2, + enslaveLinkName2: 3, + vrfLinkName2: 4, + } + + masterIndexes := map[string]int{ + vrfLinkName1: 0, + enslaveLinkName1: 1, + enslaveLinkName2: 1, + vrfLinkName2: 0, + } + + getLinkIndex := func(linkName string) int { + index, ok := linkIndexes[linkName] + if !ok { + panic(fmt.Sprintf("failed to find index for link name %q", linkName)) + } + return index + } + + getLinkMasterIndex := func(linkName string) int { + masterIndex, ok := masterIndexes[linkName] + if !ok { + panic(fmt.Sprintf("failed to find index for link name %q", linkName)) + } + return masterIndex + } + + ginkgo.BeforeEach(func() { + wg = &sync.WaitGroup{} + stopCh = make(chan struct{}) + wg.Add(1) + c = NewController() + go func() { + c.Run(stopCh, wg) + wg.Done() + }() + + nlMock = &mocks.NetLinkOps{} + vrfLinkMock1 = new(netlink_mocks.Link) + enslaveLinkMock1 = new(netlink_mocks.Link) + enslaveLinkMock2 = new(netlink_mocks.Link) + vrfLinkMock2 = new(netlink_mocks.Link) + util.SetNetLinkOpMockInst(nlMock) + + nlMock.On("LinkByName", vrfLinkName1).Return(vrfLinkMock1, nil) + nlMock.On("LinkByName", enslaveLinkName1).Return(enslaveLinkMock1, nil) + nlMock.On("LinkByName", enslaveLinkName2).Return(enslaveLinkMock2, nil) + nlMock.On("IsLinkNotFoundError", mock.Anything).Return(true) + nlMock.On("LinkAdd", mock.Anything).Return(nil) + nlMock.On("LinkSetUp", mock.Anything).Return(nil) + vrfLinkMock1.On("Type").Return("vrf") + vrfLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: vrfLinkName1, MasterIndex: getLinkMasterIndex(vrfLinkName1), Index: getLinkIndex(vrfLinkName1)}, nil) + + nlMock.On("LinkByName", vrfLinkName2).Return(vrfLinkMock2, nil) + vrfLinkMock2.On("Type").Return("vrf") + vrfLinkMock2.On("Attrs").Return(&netlink.LinkAttrs{Name: vrfLinkName2, MasterIndex: getLinkMasterIndex(vrfLinkName2), Index: getLinkIndex(vrfLinkName2), OperState: netlink.OperUp}, nil) + nlMock.On("LinkDelete", vrfLinkMock2).Return(nil) + }) + + ginkgo.AfterEach(func() { + close(stopCh) + wg.Wait() + util.ResetNetLinkOpMockInst() + }) + + ginkgo.Context("VRFs", func() { + ginkgo.It("add VRF with a slave interface", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, enslaveLinkMock1}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: 0, Index: getLinkIndex(enslaveLinkName1)}, nil) + nlMock.On("LinkSetMaster", enslaveLinkMock1, vrfLinkMock1).Return(nil) + enslaveInfaces := sets.Set[string]{} + enslaveInfaces.Insert(enslaveLinkName1) + err := c.AddVRF(vrfLinkName1, enslaveInfaces, 10) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("add VRF with multiple slave interfaces", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, enslaveLinkMock1, enslaveLinkMock2}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: 0, Index: getLinkIndex(enslaveLinkName1)}, nil) + enslaveLinkMock2.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName2, MasterIndex: 0, Index: getLinkIndex(enslaveLinkName2)}, nil) + nlMock.On("LinkSetMaster", enslaveLinkMock1, vrfLinkMock1).Return(nil) + nlMock.On("LinkSetMaster", enslaveLinkMock2, vrfLinkMock1).Return(nil) + enslaveInfaces := sets.Set[string]{} + enslaveInfaces.Insert(enslaveLinkName1) + enslaveInfaces.Insert(enslaveLinkName2) + err := c.AddVRF(vrfLinkName1, enslaveInfaces, 10) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("update VRF with removing associated stale enslaved interface", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, enslaveLinkMock1}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: getLinkMasterIndex(enslaveLinkName1), Index: getLinkIndex(enslaveLinkName1)}, nil) + nlMock.On("LinkSetNoMaster", enslaveLinkMock1).Return(nil) + err := c.AddVRF(vrfLinkName1, sets.Set[string]{}, 10) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("update VRF with removing associated stale enslaved interfaces", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, enslaveLinkMock1, enslaveLinkMock2}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: getLinkMasterIndex(enslaveLinkName1), Index: getLinkIndex(enslaveLinkName1)}, nil) + enslaveLinkMock2.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName2, MasterIndex: getLinkMasterIndex(enslaveLinkName2), Index: getLinkIndex(enslaveLinkName2)}, nil) + nlMock.On("LinkSetNoMaster", enslaveLinkMock1).Return(nil) + nlMock.On("LinkSetNoMaster", enslaveLinkMock2).Return(nil) + err := c.AddVRF(vrfLinkName1, sets.Set[string]{}, 10) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("delete VRF", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock2}, nil) + err := c.AddVRF(vrfLinkName2, sets.Set[string]{}, 20) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = c.DeleteVRF(vrfLinkName2) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("reconcile VRFs", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, vrfLinkMock2, enslaveLinkMock1}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: getLinkMasterIndex(enslaveLinkName1), Index: getLinkIndex(enslaveLinkName1)}, nil) + enslaveInfaces := sets.Set[string]{} + enslaveInfaces.Insert(enslaveLinkName1) + err := c.AddVRF(vrfLinkName1, enslaveInfaces, 10) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = c.AddVRF(vrfLinkName2, sets.Set[string]{}, 20) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = util.GetNetLinkOps().LinkDelete(vrfLinkMock2) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = c.reconcile() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + + ginkgo.It("repair VRFs", func() { + nlMock.On("LinkList").Return([]netlink.Link{vrfLinkMock1, vrfLinkMock2, enslaveLinkMock1}, nil) + enslaveLinkMock1.On("Attrs").Return(&netlink.LinkAttrs{Name: enslaveLinkName1, MasterIndex: getLinkMasterIndex(enslaveLinkName1), Index: getLinkIndex(enslaveLinkName1)}, nil) + enslaveLinkMock1.On("Type").Return("dummy") + validVRFs := make(sets.Set[string]) + validVRFs.Insert(vrfLinkName1) + // Now the Repair call would delete vrfLinkMock2 link. + err := c.Repair(validVRFs) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }) + }) +}) diff --git a/go-controller/pkg/testing/net.go b/go-controller/pkg/testing/net.go index b2c602ae066..c95265f2421 100644 --- a/go-controller/pkg/testing/net.go +++ b/go-controller/pkg/testing/net.go @@ -27,3 +27,17 @@ func DelLink(name string) { err = netlink.LinkDel(origLink) Expect(err).NotTo(HaveOccurred()) } + +func AddVRFLink(name string, tableId uint32) netlink.Link { + vrfLink := &netlink.Vrf{ + LinkAttrs: netlink.LinkAttrs{Name: name}, + Table: tableId, + } + err := netlink.LinkAdd(vrfLink) + Expect(err).NotTo(HaveOccurred()) + origLink, err := netlink.LinkByName(name) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(origLink) + Expect(err).NotTo(HaveOccurred()) + return origLink +}