Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (

type fakeControllerInformers struct {
vpcInformer kubeovninformer.VpcInformer
vpcNatGwInformer kubeovninformer.VpcNatGatewayInformer
subnetInformer kubeovninformer.SubnetInformer
serviceInformer coreinformers.ServiceInformer
namespaceInformer coreinformers.NamespaceInformer
Expand Down Expand Up @@ -123,9 +124,11 @@ func newFakeControllerWithOptions(t *testing.T, opts *FakeControllerOptions) (*f
kubeovnInformerFactory := kubeovninformerfactory.NewSharedInformerFactory(kubeovnClient, 0)
vpcInformer := kubeovnInformerFactory.Kubeovn().V1().Vpcs()
subnetInformer := kubeovnInformerFactory.Kubeovn().V1().Subnets()
vpcNatGwInformer := kubeovnInformerFactory.Kubeovn().V1().VpcNatGateways()

fakeInformers := &fakeControllerInformers{
vpcInformer: vpcInformer,
vpcNatGwInformer: vpcNatGwInformer,
subnetInformer: subnetInformer,
serviceInformer: serviceInformer,
namespaceInformer: namespaceInformer,
Expand Down Expand Up @@ -155,6 +158,8 @@ func newFakeControllerWithOptions(t *testing.T, opts *FakeControllerOptions) (*f
DefaultLogicalSwitch: "ovn-default",
NodeSwitch: "join",
KubeOvnClient: kubeovnClient,
KubeClient: kubeClient,
PodNamespace: "kube-system",
AttachNetClient: nadClient,
}

Expand Down
14 changes: 8 additions & 6 deletions pkg/controller/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,11 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error {
staticExistedRoutes []*ovnnb.LogicalRouterStaticRoute
staticTargetRoutes []*kubeovnv1.StaticRoute
staticRouteMapping map[string][]*kubeovnv1.StaticRoute
externalIDs = map[string]string{"vendor": util.CniTypeName}
)

staticExistedRoutes, err = c.OVNNbClient.ListLogicalRouterStaticRoutes(vpc.Name, nil, nil, "", nil)
// only manage static routes which are kube-ovn managed, by filtering for vendor util.CniTypeName
staticExistedRoutes, err = c.OVNNbClient.ListLogicalRouterStaticRoutes(vpc.Name, nil, nil, "", externalIDs)
if err != nil {
klog.Errorf("failed to get vpc %s static route list, %v", vpc.Name, err)
return err
Expand Down Expand Up @@ -478,15 +480,15 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error {
if item.BfdID != "" {
klog.Infof("vpc %s add static ecmp route: %+v", vpc.Name, item)
if err = c.OVNNbClient.AddLogicalRouterStaticRoute(
vpc.Name, item.RouteTable, convertPolicy(item.Policy), item.CIDR, &item.BfdID, nil, item.NextHopIP,
vpc.Name, item.RouteTable, convertPolicy(item.Policy), item.CIDR, &item.BfdID, externalIDs, item.NextHopIP,
); err != nil {
klog.Errorf("failed to add bfd static route to vpc %s , %v", vpc.Name, err)
return err
}
} else {
klog.Infof("vpc %s add static route: %+v", vpc.Name, item)
if err = c.OVNNbClient.AddLogicalRouterStaticRoute(
vpc.Name, item.RouteTable, convertPolicy(item.Policy), item.CIDR, nil, nil, item.NextHopIP,
vpc.Name, item.RouteTable, convertPolicy(item.Policy), item.CIDR, nil, externalIDs, item.NextHopIP,
); err != nil {
klog.Errorf("failed to add normal static route to vpc %s , %v", vpc.Name, err)
return err
Expand All @@ -498,7 +500,6 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error {
var (
policyRouteExisted, policyRouteNeedDel, policyRouteNeedAdd []*kubeovnv1.PolicyRoute
policyRouteLogical []*ovnnb.LogicalRouterPolicy
externalIDs = map[string]string{"vendor": util.CniTypeName}
)

if vpc.Name == c.config.ClusterRouter {
Expand Down Expand Up @@ -948,18 +949,19 @@ func (c *Controller) batchDeletePolicyRouteFromVpc(name string, policies []*kube
}

func (c *Controller) addStaticRouteToVpc(name string, route *kubeovnv1.StaticRoute) error {
externalIDs := map[string]string{"vendor": util.CniTypeName}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and reduce code duplication, consider defining externalIDs as a package-level variable. This map is also defined in handleAddOrUpdateVpc (line 342) and in the new test file pkg/controller/vpc_test.go.

You could define it once at the top of the file and reuse it:

var kubeovnExternalIDs = map[string]string{"vendor": util.CniTypeName}

Then, you can use kubeovnExternalIDs in both addStaticRouteToVpc and handleAddOrUpdateVpc, as well as in the tests.

if route.BfdID != "" {
klog.Infof("vpc %s add static ecmp route: %+v", name, route)
if err := c.OVNNbClient.AddLogicalRouterStaticRoute(
name, route.RouteTable, convertPolicy(route.Policy), route.CIDR, &route.BfdID, nil, route.NextHopIP,
name, route.RouteTable, convertPolicy(route.Policy), route.CIDR, &route.BfdID, externalIDs, route.NextHopIP,
); err != nil {
klog.Errorf("failed to add bfd static route to vpc %s , %v", name, err)
return err
}
} else {
klog.Infof("vpc %s add static route: %+v", name, route)
if err := c.OVNNbClient.AddLogicalRouterStaticRoute(
name, route.RouteTable, convertPolicy(route.Policy), route.CIDR, nil, nil, route.NextHopIP,
name, route.RouteTable, convertPolicy(route.Policy), route.CIDR, nil, externalIDs, route.NextHopIP,
); err != nil {
klog.Errorf("failed to add normal static route to vpc %s , %v", name, err)
return err
Expand Down
296 changes: 296 additions & 0 deletions pkg/controller/vpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
package controller

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/keymutex"

kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
"github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb"
"github.com/kubeovn/kube-ovn/pkg/util"
)

func Test_handleAddOrUpdateVpc_staticRoutes(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The test suite is missing a key scenario to validate the main goal of this PR. You should add a test case to ensure that static routes without the kube-ovn vendor are ignored and not deleted by the controller.

This would involve:

  1. Creating a static route in OVN with ExternalIDs that are either nil or do not contain the vendor: kube-ovn key-value pair.
  2. Asserting that mockOvnClient.EXPECT().DeleteLogicalRouterStaticRoute(...) is not called for this route.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is implicitly tested - if there was an additional call to DeleteLogicalRouterStaticRoute, the test would fail. In addition - we check that the ListLogicalRouterStaticRoute is called with the externalIDs filter.

t.Parallel()

vpcName := "test-vpc"

// Policy variables for taking pointers
srcIPPolicy := ovnnb.LogicalRouterStaticRoutePolicySrcIP
dstIPPolicy := ovnnb.LogicalRouterStaticRoutePolicyDstIP

// Internal static route created directly in OVN with kube-ovn vendor
internalStaticRoute := &ovnnb.LogicalRouterStaticRoute{
UUID: "internal-static-route-uuid",
ExternalIDs: map[string]string{
"vendor": util.CniTypeName,
},
IPPrefix: "10.0.0.0/24",
Nexthop: "1.2.3.4",
Policy: &srcIPPolicy,
RouteTable: util.MainRouteTable,
}

// Static route that matches VPC spec
managedStaticRoute := &ovnnb.LogicalRouterStaticRoute{
UUID: "managed-static-route-uuid",
ExternalIDs: map[string]string{
"vendor": util.CniTypeName,
},
IPPrefix: "192.168.0.0/24",
Nexthop: "10.0.0.1",
Policy: &dstIPPolicy,
RouteTable: util.MainRouteTable,
}

t.Run("only try to manage static routes with vendor kube-ovn", func(t *testing.T) {
fakeController := newFakeController(t)
ctrl := fakeController.fakeController
fakeinformers := fakeController.fakeInformers
mockOvnClient := fakeController.mockOvnClient

// Initialize mutexes
ctrl.vpcKeyMutex = keymutex.NewHashed(500)

vpc := &kubeovnv1.Vpc{
ObjectMeta: metav1.ObjectMeta{
Name: vpcName,
},
Spec: kubeovnv1.VpcSpec{
StaticRoutes: []*kubeovnv1.StaticRoute{
{
CIDR: "192.168.0.0/24",
NextHopIP: "10.0.0.1",
Policy: kubeovnv1.PolicyDst,
RouteTable: util.MainRouteTable,
},
},
EnableExternal: false,
PolicyRoutes: nil,
},
Status: kubeovnv1.VpcStatus{
Subnets: []string{},
EnableExternal: false,
},
}

_, err := ctrl.config.KubeOvnClient.KubeovnV1().Vpcs().Create(context.Background(), vpc, metav1.CreateOptions{})
require.NoError(t, err)

err = fakeinformers.vpcInformer.Informer().GetStore().Add(vpc)
require.NoError(t, err)

existingKubeOvnRoutes := []*ovnnb.LogicalRouterStaticRoute{
internalStaticRoute,
}

externalIDs := map[string]string{"vendor": util.CniTypeName}

mockOvnClient.EXPECT().CreateLogicalRouter(vpcName).Return(nil)
mockOvnClient.EXPECT().UpdateLogicalRouter(gomock.Any(), gomock.Any()).Return(nil)
mockOvnClient.EXPECT().ListLogicalRouterStaticRoutes(vpcName, nil, nil, "", externalIDs).Return(existingKubeOvnRoutes, nil)
mockOvnClient.EXPECT().GetLogicalRouter(vpcName, false).Return(&ovnnb.LogicalRouter{
Name: vpcName,
Nat: []string{},
}, nil)
mockOvnClient.EXPECT().DeleteLogicalRouterStaticRoute(vpcName, gomock.Any(), gomock.Any(), "10.0.0.0/24", "1.2.3.4").Return(nil)
mockOvnClient.EXPECT().AddLogicalRouterStaticRoute(
vpcName,
util.MainRouteTable,
"dst-ip",
"192.168.0.0/24",
nil,
externalIDs,
"10.0.0.1",
).Return(nil)
mockOvnClient.EXPECT().ClearLogicalRouterPolicy(vpcName).Return(nil)
mockOvnClient.EXPECT().ListLogicalSwitch(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalSwitch{}, nil).AnyTimes()
mockOvnClient.EXPECT().ListLogicalRouter(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalRouter{}, nil).AnyTimes()
mockOvnClient.EXPECT().DeleteLogicalRouterPort(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
mockOvnClient.EXPECT().DeleteHAChassisGroup(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
err = ctrl.handleAddOrUpdateVpc(vpcName)
require.NoError(t, err)
})

t.Run("delete orphaned kube-ovn routes", func(t *testing.T) {
fakeController := newFakeController(t)
ctrl := fakeController.fakeController
fakeinformers := fakeController.fakeInformers
mockOvnClient := fakeController.mockOvnClient

ctrl.vpcKeyMutex = keymutex.NewHashed(500)

vpc := &kubeovnv1.Vpc{
ObjectMeta: metav1.ObjectMeta{
Name: vpcName,
},
Spec: kubeovnv1.VpcSpec{
StaticRoutes: []*kubeovnv1.StaticRoute{
{
CIDR: "192.168.0.0/24",
NextHopIP: "10.0.0.1",
Policy: kubeovnv1.PolicyDst,
RouteTable: util.MainRouteTable,
},
},
EnableExternal: false,
PolicyRoutes: nil,
},
Status: kubeovnv1.VpcStatus{
Subnets: []string{},
EnableExternal: false,
},
}

_, err := ctrl.config.KubeOvnClient.KubeovnV1().Vpcs().Create(context.Background(), vpc, metav1.CreateOptions{})
require.NoError(t, err)

err = fakeinformers.vpcInformer.Informer().GetStore().Add(vpc)
require.NoError(t, err)

existingKubeOvnRoutes := []*ovnnb.LogicalRouterStaticRoute{
internalStaticRoute,
managedStaticRoute,
}

externalIDs := map[string]string{"vendor": util.CniTypeName}

mockOvnClient.EXPECT().CreateLogicalRouter(vpcName).Return(nil)
mockOvnClient.EXPECT().UpdateLogicalRouter(gomock.Any(), gomock.Any()).Return(nil)
mockOvnClient.EXPECT().ListLogicalRouterStaticRoutes(vpcName, nil, nil, "", externalIDs).Return(existingKubeOvnRoutes, nil)
mockOvnClient.EXPECT().GetLogicalRouter(vpcName, false).Return(&ovnnb.LogicalRouter{
Name: vpcName,
Nat: []string{},
}, nil)
mockOvnClient.EXPECT().DeleteLogicalRouterStaticRoute(vpcName, gomock.Any(), gomock.Any(), "10.0.0.0/24", "1.2.3.4").Return(nil)
mockOvnClient.EXPECT().ClearLogicalRouterPolicy(vpcName).Return(nil)
mockOvnClient.EXPECT().ListLogicalSwitch(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalSwitch{}, nil).AnyTimes()
mockOvnClient.EXPECT().ListLogicalRouter(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalRouter{}, nil).AnyTimes()
mockOvnClient.EXPECT().DeleteLogicalRouterPort(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
mockOvnClient.EXPECT().DeleteHAChassisGroup(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
err = ctrl.handleAddOrUpdateVpc(vpcName)
require.NoError(t, err)
})

t.Run("handle empty VPC static routes", func(t *testing.T) {
fakeController := newFakeController(t)
ctrl := fakeController.fakeController
fakeinformers := fakeController.fakeInformers
mockOvnClient := fakeController.mockOvnClient

ctrl.vpcKeyMutex = keymutex.NewHashed(500)

vpcEmpty := &kubeovnv1.Vpc{
ObjectMeta: metav1.ObjectMeta{
Name: vpcName,
},
Spec: kubeovnv1.VpcSpec{
StaticRoutes: []*kubeovnv1.StaticRoute{},
EnableExternal: false,
PolicyRoutes: nil,
},
Status: kubeovnv1.VpcStatus{
Subnets: []string{},
EnableExternal: false,
},
}

_, err := ctrl.config.KubeOvnClient.KubeovnV1().Vpcs().Create(context.Background(), vpcEmpty, metav1.CreateOptions{})
require.NoError(t, err)

err = fakeinformers.vpcInformer.Informer().GetStore().Add(vpcEmpty)
require.NoError(t, err)

existingKubeOvnRoutes := []*ovnnb.LogicalRouterStaticRoute{
internalStaticRoute,
managedStaticRoute,
}

externalIDs := map[string]string{"vendor": util.CniTypeName}

mockOvnClient.EXPECT().CreateLogicalRouter(vpcName).Return(nil)
mockOvnClient.EXPECT().UpdateLogicalRouter(gomock.Any(), gomock.Any()).Return(nil)
mockOvnClient.EXPECT().ListLogicalRouterStaticRoutes(vpcName, nil, nil, "", externalIDs).Return(existingKubeOvnRoutes, nil)
mockOvnClient.EXPECT().GetLogicalRouter(vpcName, false).Return(&ovnnb.LogicalRouter{
Name: vpcName,
Nat: []string{},
}, nil)
mockOvnClient.EXPECT().DeleteLogicalRouterStaticRoute(vpcName, gomock.Any(), gomock.Any(), "10.0.0.0/24", "1.2.3.4").Return(nil)
mockOvnClient.EXPECT().DeleteLogicalRouterStaticRoute(vpcName, gomock.Any(), gomock.Any(), "192.168.0.0/24", "10.0.0.1").Return(nil)
mockOvnClient.EXPECT().ClearLogicalRouterPolicy(vpcName).Return(nil)
mockOvnClient.EXPECT().ListLogicalSwitch(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalSwitch{}, nil).AnyTimes()
mockOvnClient.EXPECT().ListLogicalRouter(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalRouter{}, nil).AnyTimes()
mockOvnClient.EXPECT().DeleteLogicalRouterPort(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
mockOvnClient.EXPECT().DeleteHAChassisGroup(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
err = ctrl.handleAddOrUpdateVpc(vpcName)
require.NoError(t, err)
})

t.Run("add static routes from VPC spec when none exist", func(t *testing.T) {
fakeController := newFakeController(t)
ctrl := fakeController.fakeController
fakeinformers := fakeController.fakeInformers
mockOvnClient := fakeController.mockOvnClient

ctrl.vpcKeyMutex = keymutex.NewHashed(500)

vpc := &kubeovnv1.Vpc{
ObjectMeta: metav1.ObjectMeta{
Name: vpcName,
},
Spec: kubeovnv1.VpcSpec{
StaticRoutes: []*kubeovnv1.StaticRoute{
{
CIDR: "192.168.0.0/24",
NextHopIP: "10.0.0.1",
Policy: kubeovnv1.PolicyDst,
RouteTable: util.MainRouteTable,
},
},
EnableExternal: false,
PolicyRoutes: nil,
},
Status: kubeovnv1.VpcStatus{
Subnets: []string{},
EnableExternal: false,
},
}

_, err := ctrl.config.KubeOvnClient.KubeovnV1().Vpcs().Create(context.Background(), vpc, metav1.CreateOptions{})
require.NoError(t, err)

err = fakeinformers.vpcInformer.Informer().GetStore().Add(vpc)
require.NoError(t, err)

externalIDs := map[string]string{"vendor": util.CniTypeName}

mockOvnClient.EXPECT().CreateLogicalRouter(vpcName).Return(nil)
mockOvnClient.EXPECT().UpdateLogicalRouter(gomock.Any(), gomock.Any()).Return(nil)
mockOvnClient.EXPECT().ListLogicalRouterStaticRoutes(vpcName, nil, nil, "", externalIDs).Return(nil, nil)
mockOvnClient.EXPECT().GetLogicalRouter(vpcName, false).Return(&ovnnb.LogicalRouter{
Name: vpcName,
Nat: []string{},
}, nil)
mockOvnClient.EXPECT().AddLogicalRouterStaticRoute(
vpcName,
util.MainRouteTable,
"dst-ip",
"192.168.0.0/24",
nil,
externalIDs,
"10.0.0.1",
).Return(nil)
mockOvnClient.EXPECT().ClearLogicalRouterPolicy(vpcName).Return(nil)
mockOvnClient.EXPECT().ListLogicalSwitch(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalSwitch{}, nil).AnyTimes()
mockOvnClient.EXPECT().ListLogicalRouter(gomock.Any(), gomock.Any()).Return([]ovnnb.LogicalRouter{}, nil).AnyTimes()
mockOvnClient.EXPECT().DeleteLogicalRouterPort(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
mockOvnClient.EXPECT().DeleteHAChassisGroup(fmt.Sprintf("bfd@%s", vpcName)).Return(nil)
err = ctrl.handleAddOrUpdateVpc(vpcName)
require.NoError(t, err)
})
}
Loading