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 }