github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/pod_cidr_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconcilerv2
     5  
     6  import (
     7  	"context"
     8  	"net/netip"
     9  	"testing"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/require"
    13  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/utils/ptr"
    15  
    16  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    17  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    18  	"github.com/cilium/cilium/pkg/bgpv1/types"
    19  	ipamtypes "github.com/cilium/cilium/pkg/ipam/types"
    20  	v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    21  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    22  	"github.com/cilium/cilium/pkg/option"
    23  )
    24  
    25  var (
    26  	podCIDRTestLogger = logrus.WithField("unit_test", "reconcilerv2_podcidr")
    27  )
    28  
    29  // test fixtures
    30  var (
    31  	podCIDR1v4 = "10.10.1.0/24"
    32  	podCIDR1v6 = "2001:db8:1::/96"
    33  	podCIDR2v4 = "10.10.2.0/24"
    34  	podCIDR2v6 = "2001:db8:2::/96"
    35  	podCIDR3v4 = "10.10.3.0/24"
    36  	podCIDR3v6 = "2001:db8:3::/96"
    37  
    38  	redPeer65001v4PodCIDRRoutePolicy = &types.RoutePolicy{
    39  		Name: "red-peer-65001-ipv4-PodCIDR",
    40  		Type: types.RoutePolicyTypeExport,
    41  		Statements: []*types.RoutePolicyStatement{
    42  			{
    43  				Conditions: types.RoutePolicyConditions{
    44  					MatchNeighbors: []string{"10.10.10.1/32"},
    45  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
    46  						{
    47  							CIDR:         netip.MustParsePrefix(podCIDR1v4),
    48  							PrefixLenMin: netip.MustParsePrefix(podCIDR1v4).Bits(),
    49  							PrefixLenMax: netip.MustParsePrefix(podCIDR1v4).Bits(),
    50  						},
    51  						{
    52  							CIDR:         netip.MustParsePrefix(podCIDR2v4),
    53  							PrefixLenMin: netip.MustParsePrefix(podCIDR2v4).Bits(),
    54  							PrefixLenMax: netip.MustParsePrefix(podCIDR2v4).Bits(),
    55  						},
    56  					},
    57  				},
    58  				Actions: types.RoutePolicyActions{
    59  					RouteAction:    types.RoutePolicyActionAccept,
    60  					AddCommunities: []string{"65000:100"},
    61  				},
    62  			},
    63  		},
    64  	}
    65  
    66  	redPeer65001v6PodCIDRRoutePolicy = &types.RoutePolicy{
    67  		Name: "red-peer-65001-ipv6-PodCIDR",
    68  		Type: types.RoutePolicyTypeExport,
    69  		Statements: []*types.RoutePolicyStatement{
    70  			{
    71  				Conditions: types.RoutePolicyConditions{
    72  					MatchNeighbors: []string{"10.10.10.1/32"},
    73  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
    74  						{
    75  							CIDR:         netip.MustParsePrefix(podCIDR1v6),
    76  							PrefixLenMin: netip.MustParsePrefix(podCIDR1v6).Bits(),
    77  							PrefixLenMax: netip.MustParsePrefix(podCIDR1v6).Bits(),
    78  						},
    79  						{
    80  							CIDR:         netip.MustParsePrefix(podCIDR2v6),
    81  							PrefixLenMin: netip.MustParsePrefix(podCIDR2v6).Bits(),
    82  							PrefixLenMax: netip.MustParsePrefix(podCIDR2v6).Bits(),
    83  						},
    84  					},
    85  				},
    86  				Actions: types.RoutePolicyActions{
    87  					RouteAction:    types.RoutePolicyActionAccept,
    88  					AddCommunities: []string{"65000:100"},
    89  				},
    90  			},
    91  		},
    92  	}
    93  
    94  	bluePeer65001v4PodCIDRRoutePolicy = &types.RoutePolicy{
    95  		Name: "blue-peer-65001-ipv4-PodCIDR",
    96  		Type: types.RoutePolicyTypeExport,
    97  		Statements: []*types.RoutePolicyStatement{
    98  			{
    99  				Conditions: types.RoutePolicyConditions{
   100  					MatchNeighbors: []string{"10.10.10.2/32"},
   101  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   102  						{
   103  							CIDR:         netip.MustParsePrefix(podCIDR1v4),
   104  							PrefixLenMin: netip.MustParsePrefix(podCIDR1v4).Bits(),
   105  							PrefixLenMax: netip.MustParsePrefix(podCIDR1v4).Bits(),
   106  						},
   107  						{
   108  							CIDR:         netip.MustParsePrefix(podCIDR2v4),
   109  							PrefixLenMin: netip.MustParsePrefix(podCIDR2v4).Bits(),
   110  							PrefixLenMax: netip.MustParsePrefix(podCIDR2v4).Bits(),
   111  						},
   112  					},
   113  				},
   114  				Actions: types.RoutePolicyActions{
   115  					RouteAction:    types.RoutePolicyActionAccept,
   116  					AddCommunities: []string{"65355:100"},
   117  				},
   118  			},
   119  		},
   120  	}
   121  
   122  	bluePeer65001v6PodCIDRRoutePolicy = &types.RoutePolicy{
   123  		Name: "blue-peer-65001-ipv6-PodCIDR",
   124  		Type: types.RoutePolicyTypeExport,
   125  		Statements: []*types.RoutePolicyStatement{
   126  			{
   127  				Conditions: types.RoutePolicyConditions{
   128  					MatchNeighbors: []string{"10.10.10.2/32"},
   129  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   130  						{
   131  							CIDR:         netip.MustParsePrefix(podCIDR1v6),
   132  							PrefixLenMin: netip.MustParsePrefix(podCIDR1v6).Bits(),
   133  							PrefixLenMax: netip.MustParsePrefix(podCIDR1v6).Bits(),
   134  						},
   135  						{
   136  							CIDR:         netip.MustParsePrefix(podCIDR2v6),
   137  							PrefixLenMin: netip.MustParsePrefix(podCIDR2v6).Bits(),
   138  							PrefixLenMax: netip.MustParsePrefix(podCIDR2v6).Bits(),
   139  						},
   140  					},
   141  				},
   142  				Actions: types.RoutePolicyActions{
   143  					RouteAction:    types.RoutePolicyActionAccept,
   144  					AddCommunities: []string{"65355:100"},
   145  				},
   146  			},
   147  		},
   148  	}
   149  )
   150  
   151  func Test_PodCIDRAdvertisement(t *testing.T) {
   152  	logrus.SetLevel(logrus.DebugLevel)
   153  
   154  	tests := []struct {
   155  		name                  string
   156  		peerConfig            []*v2alpha1.CiliumBGPPeerConfig
   157  		advertisements        []*v2alpha1.CiliumBGPAdvertisement
   158  		preconfiguredPaths    map[types.Family]map[string]struct{}
   159  		preconfiguredRPs      RoutePolicyMap
   160  		testCiliumNode        *v2api.CiliumNode
   161  		testBGPInstanceConfig *v2alpha1.CiliumBGPNodeInstance
   162  		expectedPaths         map[types.Family]map[string]struct{}
   163  		expectedRPs           RoutePolicyMap
   164  	}{
   165  		{
   166  			name: "pod cidr advertisement with no preconfigured advertisements",
   167  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{
   168  				redPeerConfig,
   169  				bluePeerConfig,
   170  			},
   171  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   172  				redAdvert,
   173  				blueAdvert,
   174  			},
   175  			preconfiguredPaths: map[types.Family]map[string]struct{}{},
   176  			preconfiguredRPs:   map[string]*types.RoutePolicy{},
   177  			testCiliumNode: &v2api.CiliumNode{
   178  				ObjectMeta: meta_v1.ObjectMeta{
   179  					Name: "Test Node",
   180  				},
   181  				Spec: v2api.NodeSpec{
   182  					IPAM: ipamtypes.IPAMSpec{
   183  						PodCIDRs: []string{
   184  							podCIDR1v4,
   185  							podCIDR2v4,
   186  							podCIDR1v6,
   187  							podCIDR2v6,
   188  						},
   189  					},
   190  				},
   191  			},
   192  			testBGPInstanceConfig: &v2alpha1.CiliumBGPNodeInstance{
   193  				Name:     "bgp-65001",
   194  				LocalASN: ptr.To[int64](65001),
   195  				Peers: []v2alpha1.CiliumBGPNodePeer{
   196  					redPeer65001,
   197  				},
   198  			},
   199  			expectedPaths: map[types.Family]map[string]struct{}{
   200  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   201  					podCIDR1v4: struct{}{},
   202  					podCIDR2v4: struct{}{},
   203  				},
   204  				{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   205  					podCIDR1v6: struct{}{},
   206  					podCIDR2v6: struct{}{},
   207  				},
   208  			},
   209  			expectedRPs: map[string]*types.RoutePolicy{
   210  				redPeer65001v4PodCIDRRoutePolicy.Name: redPeer65001v4PodCIDRRoutePolicy,
   211  				redPeer65001v6PodCIDRRoutePolicy.Name: redPeer65001v6PodCIDRRoutePolicy,
   212  			},
   213  		},
   214  		{
   215  			name: "pod cidr advertisement with no preconfigured advertisements - two peers",
   216  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{
   217  				redPeerConfig,
   218  				bluePeerConfig,
   219  			},
   220  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   221  				redAdvert,
   222  				blueAdvert,
   223  			},
   224  			preconfiguredPaths: map[types.Family]map[string]struct{}{},
   225  			testCiliumNode: &v2api.CiliumNode{
   226  				ObjectMeta: meta_v1.ObjectMeta{
   227  					Name: "Test Node",
   228  				},
   229  				Spec: v2api.NodeSpec{
   230  					IPAM: ipamtypes.IPAMSpec{
   231  						PodCIDRs: []string{
   232  							podCIDR1v4,
   233  							podCIDR2v4,
   234  							podCIDR1v6,
   235  							podCIDR2v6,
   236  						},
   237  					},
   238  				},
   239  			},
   240  			testBGPInstanceConfig: &v2alpha1.CiliumBGPNodeInstance{
   241  				Name:     "bgp-65001",
   242  				LocalASN: ptr.To[int64](65001),
   243  				Peers: []v2alpha1.CiliumBGPNodePeer{
   244  					redPeer65001,
   245  					bluePeer65001,
   246  				},
   247  			},
   248  			expectedPaths: map[types.Family]map[string]struct{}{
   249  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   250  					podCIDR1v4: struct{}{},
   251  					podCIDR2v4: struct{}{},
   252  				},
   253  				{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   254  					podCIDR1v6: struct{}{},
   255  					podCIDR2v6: struct{}{},
   256  				},
   257  			},
   258  			expectedRPs: map[string]*types.RoutePolicy{
   259  				redPeer65001v4PodCIDRRoutePolicy.Name:  redPeer65001v4PodCIDRRoutePolicy,
   260  				redPeer65001v6PodCIDRRoutePolicy.Name:  redPeer65001v6PodCIDRRoutePolicy,
   261  				bluePeer65001v4PodCIDRRoutePolicy.Name: bluePeer65001v4PodCIDRRoutePolicy,
   262  				bluePeer65001v6PodCIDRRoutePolicy.Name: bluePeer65001v6PodCIDRRoutePolicy,
   263  			},
   264  		},
   265  		{
   266  			name: "pod cidr advertisement - cleanup old pod cidr",
   267  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{
   268  				redPeerConfig,
   269  				bluePeerConfig,
   270  			},
   271  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   272  				redAdvert,
   273  				blueAdvert,
   274  			},
   275  			preconfiguredPaths: map[types.Family]map[string]struct{}{
   276  				// pod cidr 3 is extra advertisement, reconcile should clean this.
   277  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   278  					podCIDR3v4: struct{}{},
   279  					podCIDR3v6: struct{}{},
   280  				},
   281  			},
   282  			preconfiguredRPs: map[string]*types.RoutePolicy{
   283  				bluePeer65001v4PodCIDRRoutePolicy.Name: bluePeer65001v4PodCIDRRoutePolicy,
   284  			},
   285  			testCiliumNode: &v2api.CiliumNode{
   286  				ObjectMeta: meta_v1.ObjectMeta{
   287  					Name: "Test Node",
   288  				},
   289  				Spec: v2api.NodeSpec{
   290  					IPAM: ipamtypes.IPAMSpec{
   291  						PodCIDRs: []string{podCIDR1v4, podCIDR2v4},
   292  					},
   293  				},
   294  			},
   295  			testBGPInstanceConfig: &v2alpha1.CiliumBGPNodeInstance{
   296  				Name:     "bgp-65001",
   297  				LocalASN: ptr.To[int64](65001),
   298  				Peers: []v2alpha1.CiliumBGPNodePeer{
   299  					redPeer65001,
   300  				},
   301  			},
   302  			expectedPaths: map[types.Family]map[string]struct{}{
   303  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   304  					podCIDR1v4: struct{}{},
   305  					podCIDR2v4: struct{}{},
   306  				},
   307  				{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {},
   308  			},
   309  			expectedRPs: map[string]*types.RoutePolicy{
   310  				redPeer65001v4PodCIDRRoutePolicy.Name: redPeer65001v4PodCIDRRoutePolicy,
   311  			},
   312  		},
   313  		{
   314  			name: "pod cidr advertisement - disable",
   315  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{
   316  				redPeerConfig,
   317  				bluePeerConfig,
   318  			},
   319  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   320  				//no pod cidr advertisement configured
   321  				//redPodCIDRAdvert,
   322  				//bluePodCIDRAdvert,
   323  			},
   324  			preconfiguredPaths: map[types.Family]map[string]struct{}{
   325  				// pod cidr 1,2 already advertised, reconcile should clean this as there is no matching pod cidr advertisement.
   326  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   327  					podCIDR1v4: struct{}{},
   328  					podCIDR2v4: struct{}{},
   329  				},
   330  			},
   331  			preconfiguredRPs: map[string]*types.RoutePolicy{
   332  				redPeer65001v4PodCIDRRoutePolicy.Name:  redPeer65001v4PodCIDRRoutePolicy,
   333  				redPeer65001v6PodCIDRRoutePolicy.Name:  redPeer65001v6PodCIDRRoutePolicy,
   334  				bluePeer65001v4PodCIDRRoutePolicy.Name: bluePeer65001v4PodCIDRRoutePolicy,
   335  				bluePeer65001v6PodCIDRRoutePolicy.Name: bluePeer65001v6PodCIDRRoutePolicy,
   336  			},
   337  			testCiliumNode: &v2api.CiliumNode{
   338  				ObjectMeta: meta_v1.ObjectMeta{
   339  					Name: "Test Node",
   340  				},
   341  				Spec: v2api.NodeSpec{
   342  					IPAM: ipamtypes.IPAMSpec{
   343  						PodCIDRs: []string{podCIDR1v4, podCIDR2v4},
   344  					},
   345  				},
   346  			},
   347  			testBGPInstanceConfig: &v2alpha1.CiliumBGPNodeInstance{
   348  				Name:     "bgp-65001",
   349  				LocalASN: ptr.To[int64](65001),
   350  				Peers: []v2alpha1.CiliumBGPNodePeer{
   351  					redPeer65001,
   352  				},
   353  			},
   354  			expectedPaths: map[types.Family]map[string]struct{}{
   355  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {},
   356  				{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {},
   357  			},
   358  			expectedRPs: map[string]*types.RoutePolicy{},
   359  		},
   360  		{
   361  			name: "pod cidr advertisement - v4 only",
   362  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{
   363  				redPeerConfigV4,
   364  			},
   365  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   366  				redAdvert,
   367  				//bluePodCIDRAdvert,
   368  			},
   369  			preconfiguredPaths: map[types.Family]map[string]struct{}{
   370  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   371  					podCIDR1v4: struct{}{},
   372  					podCIDR2v4: struct{}{},
   373  				},
   374  				{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   375  					podCIDR1v6: struct{}{},
   376  					podCIDR2v6: struct{}{},
   377  				},
   378  			},
   379  			preconfiguredRPs: map[string]*types.RoutePolicy{
   380  				redPeer65001v4PodCIDRRoutePolicy.Name: redPeer65001v4PodCIDRRoutePolicy,
   381  				redPeer65001v6PodCIDRRoutePolicy.Name: redPeer65001v6PodCIDRRoutePolicy,
   382  			},
   383  			testCiliumNode: &v2api.CiliumNode{
   384  				ObjectMeta: meta_v1.ObjectMeta{
   385  					Name: "Test Node",
   386  				},
   387  				Spec: v2api.NodeSpec{
   388  					IPAM: ipamtypes.IPAMSpec{
   389  						PodCIDRs: []string{podCIDR1v4, podCIDR2v4},
   390  					},
   391  				},
   392  			},
   393  			testBGPInstanceConfig: &v2alpha1.CiliumBGPNodeInstance{
   394  				Name:     "bgp-65001",
   395  				LocalASN: ptr.To[int64](65001),
   396  				Peers: []v2alpha1.CiliumBGPNodePeer{
   397  					{
   398  						Name:        "red-peer-65001",
   399  						PeerAddress: ptr.To[string]("10.10.10.1"),
   400  						PeerConfigRef: &v2alpha1.PeerConfigReference{
   401  							Group: "cilium.io",
   402  							Kind:  "CiliumBGPPeerConfig",
   403  							Name:  "peer-config-red-v4",
   404  						},
   405  					},
   406  				},
   407  			},
   408  			expectedPaths: map[types.Family]map[string]struct{}{
   409  				{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   410  					podCIDR1v4: struct{}{},
   411  					podCIDR2v4: struct{}{},
   412  				},
   413  			},
   414  			expectedRPs: map[string]*types.RoutePolicy{
   415  				redPeer65001v4PodCIDRRoutePolicy.Name: redPeer65001v4PodCIDRRoutePolicy,
   416  			},
   417  		},
   418  	}
   419  
   420  	for _, tt := range tests {
   421  		t.Run(tt.name, func(t *testing.T) {
   422  			req := require.New(t)
   423  
   424  			// initialize pod cidr reconciler
   425  			p := PodCIDRReconcilerIn{
   426  				Logger: podCIDRTestLogger,
   427  				PeerAdvert: NewCiliumPeerAdvertisement(
   428  					PeerAdvertisementIn{
   429  						Logger:          podCIDRTestLogger,
   430  						PeerConfigStore: store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](tt.peerConfig),
   431  						AdvertStore:     store.InitMockStore[*v2alpha1.CiliumBGPAdvertisement](tt.advertisements),
   432  					}),
   433  				DaemonConfig: &option.DaemonConfig{IPAM: "Kubernetes"},
   434  			}
   435  			podCIDRReconciler := NewPodCIDRReconciler(p).Reconciler.(*PodCIDRReconciler)
   436  
   437  			// preconfigure advertisements
   438  			testBGPInstance := instance.NewFakeBGPInstance()
   439  
   440  			presetAdverts := make(AFPathsMap)
   441  			for preAdvertFam, preAdverts := range tt.preconfiguredPaths {
   442  				pathSet := make(map[string]*types.Path)
   443  				for preAdvert := range preAdverts {
   444  					path := types.NewPathForPrefix(netip.MustParsePrefix(preAdvert))
   445  					path.Family = preAdvertFam
   446  					pathSet[preAdvert] = path
   447  				}
   448  				presetAdverts[preAdvertFam] = pathSet
   449  			}
   450  			podCIDRReconciler.setMetadata(testBGPInstance, PodCIDRReconcilerMetadata{
   451  				AFPaths:       presetAdverts,
   452  				RoutePolicies: tt.preconfiguredRPs,
   453  			})
   454  
   455  			// reconcile pod cidr
   456  			// run reconciler twice to ensure idempotency
   457  			for i := 0; i < 2; i++ {
   458  				err := podCIDRReconciler.Reconcile(context.Background(), ReconcileParams{
   459  					BGPInstance:   testBGPInstance,
   460  					DesiredConfig: tt.testBGPInstanceConfig,
   461  					CiliumNode:    tt.testCiliumNode,
   462  				})
   463  				req.NoError(err)
   464  			}
   465  
   466  			// check if the advertisements are as expected
   467  			runningFamilyPaths := make(map[types.Family]map[string]struct{})
   468  			for family, paths := range podCIDRReconciler.getMetadata(testBGPInstance).AFPaths {
   469  				pathSet := make(map[string]struct{})
   470  				for pathKey := range paths {
   471  					pathSet[pathKey] = struct{}{}
   472  				}
   473  				runningFamilyPaths[family] = pathSet
   474  			}
   475  
   476  			req.Equal(tt.expectedPaths, runningFamilyPaths)
   477  
   478  			// check if the route policies are as expected
   479  			runningRPs := podCIDRReconciler.getMetadata(testBGPInstance).RoutePolicies
   480  			req.Equal(tt.expectedRPs, runningRPs)
   481  		})
   482  	}
   483  }