github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconciler/route_policy_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package reconciler 5 6 import ( 7 "context" 8 "net/netip" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/utils/ptr" 14 15 "github.com/cilium/cilium/pkg/bgpv1/manager/instance" 16 "github.com/cilium/cilium/pkg/bgpv1/manager/store" 17 "github.com/cilium/cilium/pkg/bgpv1/types" 18 ipamTypes "github.com/cilium/cilium/pkg/ipam/types" 19 v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 20 v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 21 slimv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 22 ) 23 24 var ( 25 podCIDR = "10.0.0.0/24" 26 podCIDRPrefix = netip.MustParsePrefix(podCIDR) 27 28 lbPool = &v2alpha1api.CiliumLoadBalancerIPPool{ 29 ObjectMeta: metav1.ObjectMeta{ 30 Labels: map[string]string{ 31 "label1": "value1", 32 }, 33 }, 34 Spec: v2alpha1api.CiliumLoadBalancerIPPoolSpec{ 35 Blocks: []v2alpha1api.CiliumLoadBalancerIPPoolIPBlock{ 36 { 37 Cidr: "192.168.0.0/24", 38 }, 39 }, 40 }, 41 } 42 43 lbPoolUpdated = &v2alpha1api.CiliumLoadBalancerIPPool{ 44 ObjectMeta: metav1.ObjectMeta{ 45 Labels: map[string]string{ 46 "label1": "value1", 47 }, 48 }, 49 Spec: v2alpha1api.CiliumLoadBalancerIPPoolSpec{ 50 Blocks: []v2alpha1api.CiliumLoadBalancerIPPoolIPBlock{ 51 { 52 Cidr: "10.100.99.0/24", // UPDATED 53 }, 54 }, 55 }, 56 } 57 58 podPool = &v2alpha1api.CiliumPodIPPool{ 59 ObjectMeta: metav1.ObjectMeta{ 60 Name: "test", 61 Labels: map[string]string{ 62 "label1": "value1", 63 }}, 64 Spec: v2alpha1api.IPPoolSpec{ 65 IPv4: &v2alpha1api.IPv4PoolSpec{ 66 CIDRs: []v2alpha1api.PoolCIDR{"100.0.0.0/16"}, 67 MaskSize: 24, 68 }, 69 IPv6: &v2alpha1api.IPv6PoolSpec{ 70 CIDRs: []v2alpha1api.PoolCIDR{"2001:0:0:1234::/64"}, 71 MaskSize: 96, 72 }, 73 }, 74 } 75 76 podPoolUpdated = &v2alpha1api.CiliumPodIPPool{ 77 ObjectMeta: metav1.ObjectMeta{ 78 Labels: map[string]string{ 79 "label1": "value1", 80 }}, 81 Spec: v2alpha1api.IPPoolSpec{ 82 IPv4: &v2alpha1api.IPv4PoolSpec{ 83 CIDRs: []v2alpha1api.PoolCIDR{"100.0.0.0/16", "100.1.0.0/16"}, 84 MaskSize: 24, 85 }, 86 IPv6: &v2alpha1api.IPv6PoolSpec{ 87 CIDRs: []v2alpha1api.PoolCIDR{"2001:0:0:1234::/64", "2002:0:0:1234::/64"}, 88 MaskSize: 96, 89 }, 90 }, 91 } 92 93 nodePool = ipamTypes.IPAMPoolAllocation{ 94 Pool: podPool.Name, 95 CIDRs: []ipamTypes.IPAMPodCIDR{ 96 "100.0.0.0/16", 97 "2001:0:0:1234::/64", 98 }, 99 } 100 101 nodePoolUpdated = ipamTypes.IPAMPoolAllocation{ 102 Pool: podPool.Name, 103 CIDRs: []ipamTypes.IPAMPodCIDR{ 104 "100.0.0.0/16", 105 "100.1.0.0/16", 106 "2001:0:0:1234::/64", 107 "2002:0:0:1234::/64", 108 }, 109 } 110 111 peerAddress = "172.16.0.1/32" 112 113 standardCommunity = "64125:100" 114 wellKnownCommunity = "no-advertise" 115 largeCommunity = "64125:4294967295:100" 116 117 attrSelectLBPool = v2alpha1api.CiliumBGPPathAttributes{ 118 SelectorType: v2alpha1api.CiliumLoadBalancerIPPoolSelectorName, 119 Selector: &slimv1.LabelSelector{ 120 MatchLabels: map[string]slimv1.MatchLabelsValue{ 121 "label1": "value1", 122 }, 123 }, 124 Communities: &v2alpha1api.BGPCommunities{ 125 Standard: []v2alpha1api.BGPStandardCommunity{v2alpha1api.BGPStandardCommunity(standardCommunity)}, 126 WellKnown: []v2alpha1api.BGPWellKnownCommunity{v2alpha1api.BGPWellKnownCommunity(wellKnownCommunity)}, 127 Large: []v2alpha1api.BGPLargeCommunity{v2alpha1api.BGPLargeCommunity(largeCommunity)}, 128 }, 129 } 130 131 attrSelectPodPool = v2alpha1api.CiliumBGPPathAttributes{ 132 SelectorType: v2alpha1api.CiliumPodIPPoolSelectorName, 133 Selector: &slimv1.LabelSelector{ 134 MatchLabels: map[string]slimv1.MatchLabelsValue{ 135 "label1": "value1", 136 }, 137 }, 138 Communities: &v2alpha1api.BGPCommunities{ 139 Standard: []v2alpha1api.BGPStandardCommunity{v2alpha1api.BGPStandardCommunity(standardCommunity)}, 140 WellKnown: []v2alpha1api.BGPWellKnownCommunity{v2alpha1api.BGPWellKnownCommunity(wellKnownCommunity)}, 141 Large: []v2alpha1api.BGPLargeCommunity{v2alpha1api.BGPLargeCommunity(largeCommunity)}, 142 }, 143 } 144 145 attrSelectAnyNode = v2alpha1api.CiliumBGPPathAttributes{ 146 SelectorType: v2alpha1api.PodCIDRSelectorName, 147 LocalPreference: ptr.To[int64](150), 148 } 149 150 attrSelectNonExistingNode = v2alpha1api.CiliumBGPPathAttributes{ 151 SelectorType: v2alpha1api.PodCIDRSelectorName, 152 Selector: &slimv1.LabelSelector{ 153 MatchLabels: map[string]slimv1.MatchLabelsValue{ 154 "node": "non-existing", 155 }, 156 }, 157 LocalPreference: ptr.To[int64](150), 158 } 159 160 attrSelectInvalid = v2alpha1api.CiliumBGPPathAttributes{ 161 SelectorType: "INVALID", 162 Selector: &slimv1.LabelSelector{ 163 MatchLabels: map[string]slimv1.MatchLabelsValue{ 164 "env": "dev", 165 }, 166 }, 167 } 168 ) 169 170 type routePolicyTestInputs struct { 171 podCIDRs []string 172 LBPools []*v2alpha1api.CiliumLoadBalancerIPPool 173 NodePools []ipamTypes.IPAMPoolAllocation 174 PodPools []*v2alpha1api.CiliumPodIPPool 175 neighbors []v2alpha1api.CiliumBGPNeighbor 176 expectedPolicies []*types.RoutePolicy 177 } 178 179 func TestRoutePolicyReconciler(t *testing.T) { 180 var table = []struct { 181 name string 182 initial *routePolicyTestInputs 183 updated *routePolicyTestInputs 184 expectError bool 185 }{ 186 { 187 name: "add complex policy (pod CIDR + LB pool + Pod pool)", 188 initial: &routePolicyTestInputs{ 189 podCIDRs: []string{ 190 podCIDR, 191 }, 192 LBPools: []*v2alpha1api.CiliumLoadBalancerIPPool{ 193 lbPool, 194 }, 195 PodPools: []*v2alpha1api.CiliumPodIPPool{ 196 podPool, 197 }, 198 NodePools: []ipamTypes.IPAMPoolAllocation{ 199 nodePool, 200 }, 201 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 202 { 203 PeerAddress: peerAddress, 204 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 205 attrSelectLBPool, 206 attrSelectPodPool, 207 attrSelectAnyNode, 208 }, 209 }, 210 }, 211 expectedPolicies: []*types.RoutePolicy{ 212 { 213 Name: pathAttributesPolicyName(attrSelectLBPool, peerAddress), 214 Type: types.RoutePolicyTypeExport, 215 Statements: []*types.RoutePolicyStatement{ 216 { 217 Conditions: types.RoutePolicyConditions{ 218 MatchNeighbors: []string{peerAddress}, 219 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 220 { 221 CIDR: netip.MustParsePrefix(string(lbPool.Spec.Blocks[0].Cidr)), 222 PrefixLenMin: maxPrefixLenIPv4, 223 PrefixLenMax: maxPrefixLenIPv4, 224 }, 225 }, 226 }, 227 Actions: types.RoutePolicyActions{ 228 RouteAction: types.RoutePolicyActionNone, 229 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 230 AddLargeCommunities: []string{largeCommunity}, 231 }, 232 }, 233 }, 234 }, 235 { 236 Name: pathAttributesPolicyName(attrSelectPodPool, peerAddress), 237 Type: types.RoutePolicyTypeExport, 238 Statements: []*types.RoutePolicyStatement{ 239 { 240 Conditions: types.RoutePolicyConditions{ 241 MatchNeighbors: []string{peerAddress}, 242 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 243 { 244 CIDR: netip.MustParsePrefix(string(podPool.Spec.IPv4.CIDRs[0])), 245 PrefixLenMin: int(podPool.Spec.IPv4.MaskSize), 246 PrefixLenMax: int(podPool.Spec.IPv4.MaskSize), 247 }, 248 }, 249 }, 250 Actions: types.RoutePolicyActions{ 251 RouteAction: types.RoutePolicyActionNone, 252 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 253 AddLargeCommunities: []string{largeCommunity}, 254 }, 255 }, 256 { 257 Conditions: types.RoutePolicyConditions{ 258 MatchNeighbors: []string{peerAddress}, 259 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 260 { 261 CIDR: netip.MustParsePrefix(string(podPool.Spec.IPv6.CIDRs[0])), 262 PrefixLenMin: int(podPool.Spec.IPv6.MaskSize), 263 PrefixLenMax: int(podPool.Spec.IPv6.MaskSize), 264 }, 265 }, 266 }, 267 Actions: types.RoutePolicyActions{ 268 RouteAction: types.RoutePolicyActionNone, 269 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 270 AddLargeCommunities: []string{largeCommunity}, 271 }, 272 }, 273 }, 274 }, 275 { 276 Name: pathAttributesPolicyName(attrSelectAnyNode, peerAddress), 277 Type: types.RoutePolicyTypeExport, 278 Statements: []*types.RoutePolicyStatement{ 279 { 280 Conditions: types.RoutePolicyConditions{ 281 MatchNeighbors: []string{peerAddress}, 282 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 283 { 284 CIDR: podCIDRPrefix, 285 PrefixLenMin: podCIDRPrefix.Bits(), 286 PrefixLenMax: podCIDRPrefix.Bits(), 287 }, 288 }, 289 }, 290 Actions: types.RoutePolicyActions{ 291 RouteAction: types.RoutePolicyActionNone, 292 SetLocalPreference: attrSelectAnyNode.LocalPreference, 293 }, 294 }, 295 }, 296 }, 297 }, 298 }, 299 expectError: false, 300 }, 301 { 302 name: "update policy - lb pool change", 303 initial: &routePolicyTestInputs{ 304 LBPools: []*v2alpha1api.CiliumLoadBalancerIPPool{ 305 lbPool, 306 }, 307 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 308 { 309 PeerAddress: peerAddress, 310 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 311 attrSelectLBPool, 312 }, 313 }, 314 }, 315 expectedPolicies: []*types.RoutePolicy{ 316 { 317 Name: pathAttributesPolicyName(attrSelectLBPool, peerAddress), 318 Type: types.RoutePolicyTypeExport, 319 Statements: []*types.RoutePolicyStatement{ 320 { 321 Conditions: types.RoutePolicyConditions{ 322 MatchNeighbors: []string{peerAddress}, 323 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 324 { 325 CIDR: netip.MustParsePrefix(string(lbPool.Spec.Blocks[0].Cidr)), 326 PrefixLenMin: maxPrefixLenIPv4, 327 PrefixLenMax: maxPrefixLenIPv4, 328 }, 329 }, 330 }, 331 Actions: types.RoutePolicyActions{ 332 RouteAction: types.RoutePolicyActionNone, 333 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 334 AddLargeCommunities: []string{largeCommunity}, 335 }, 336 }, 337 }, 338 }, 339 }, 340 }, 341 updated: &routePolicyTestInputs{ 342 LBPools: []*v2alpha1api.CiliumLoadBalancerIPPool{ 343 lbPoolUpdated, // UPDATED - modified CIDR 344 }, 345 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 346 { 347 PeerAddress: peerAddress, 348 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 349 attrSelectLBPool, 350 }, 351 }, 352 }, 353 expectedPolicies: []*types.RoutePolicy{ 354 { 355 Name: pathAttributesPolicyName(attrSelectLBPool, peerAddress), 356 Type: types.RoutePolicyTypeExport, 357 Statements: []*types.RoutePolicyStatement{ 358 { 359 Conditions: types.RoutePolicyConditions{ 360 MatchNeighbors: []string{peerAddress}, 361 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 362 { 363 CIDR: netip.MustParsePrefix(string(lbPoolUpdated.Spec.Blocks[0].Cidr)), 364 PrefixLenMin: maxPrefixLenIPv4, 365 PrefixLenMax: maxPrefixLenIPv4, 366 }, 367 }, 368 }, 369 Actions: types.RoutePolicyActions{ 370 RouteAction: types.RoutePolicyActionNone, 371 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 372 AddLargeCommunities: []string{largeCommunity}, 373 }, 374 }, 375 }, 376 }, 377 }, 378 }, 379 expectError: false, 380 }, 381 { 382 name: "update policy - pod pool change", 383 initial: &routePolicyTestInputs{ 384 PodPools: []*v2alpha1api.CiliumPodIPPool{ 385 podPool, 386 }, 387 NodePools: []ipamTypes.IPAMPoolAllocation{ 388 nodePool, 389 }, 390 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 391 { 392 PeerAddress: peerAddress, 393 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 394 attrSelectPodPool, 395 }, 396 }, 397 }, 398 expectedPolicies: []*types.RoutePolicy{ 399 { 400 Name: pathAttributesPolicyName(attrSelectPodPool, peerAddress), 401 Type: types.RoutePolicyTypeExport, 402 Statements: []*types.RoutePolicyStatement{ 403 { 404 Conditions: types.RoutePolicyConditions{ 405 MatchNeighbors: []string{peerAddress}, 406 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 407 { 408 CIDR: netip.MustParsePrefix(string(podPool.Spec.IPv4.CIDRs[0])), 409 PrefixLenMin: int(podPool.Spec.IPv4.MaskSize), 410 PrefixLenMax: int(podPool.Spec.IPv4.MaskSize), 411 }, 412 }, 413 }, 414 Actions: types.RoutePolicyActions{ 415 RouteAction: types.RoutePolicyActionNone, 416 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 417 AddLargeCommunities: []string{largeCommunity}, 418 }, 419 }, 420 { 421 Conditions: types.RoutePolicyConditions{ 422 MatchNeighbors: []string{peerAddress}, 423 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 424 { 425 CIDR: netip.MustParsePrefix(string(podPool.Spec.IPv6.CIDRs[0])), 426 PrefixLenMin: int(podPool.Spec.IPv6.MaskSize), 427 PrefixLenMax: int(podPool.Spec.IPv6.MaskSize), 428 }, 429 }, 430 }, 431 Actions: types.RoutePolicyActions{ 432 RouteAction: types.RoutePolicyActionNone, 433 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 434 AddLargeCommunities: []string{largeCommunity}, 435 }, 436 }, 437 }, 438 }, 439 }, 440 }, 441 updated: &routePolicyTestInputs{ 442 PodPools: []*v2alpha1api.CiliumPodIPPool{ 443 podPoolUpdated, 444 }, 445 NodePools: []ipamTypes.IPAMPoolAllocation{ 446 nodePoolUpdated, 447 }, 448 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 449 { 450 PeerAddress: peerAddress, 451 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 452 attrSelectPodPool, 453 }, 454 }, 455 }, 456 expectedPolicies: []*types.RoutePolicy{ 457 { 458 Name: pathAttributesPolicyName(attrSelectPodPool, peerAddress), 459 Type: types.RoutePolicyTypeExport, 460 Statements: []*types.RoutePolicyStatement{ 461 { 462 Conditions: types.RoutePolicyConditions{ 463 MatchNeighbors: []string{peerAddress}, 464 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 465 { 466 CIDR: netip.MustParsePrefix(string(podPoolUpdated.Spec.IPv4.CIDRs[0])), 467 PrefixLenMin: int(podPoolUpdated.Spec.IPv4.MaskSize), 468 PrefixLenMax: int(podPoolUpdated.Spec.IPv4.MaskSize), 469 }, 470 { 471 CIDR: netip.MustParsePrefix(string(podPoolUpdated.Spec.IPv4.CIDRs[1])), 472 PrefixLenMin: int(podPoolUpdated.Spec.IPv4.MaskSize), 473 PrefixLenMax: int(podPoolUpdated.Spec.IPv4.MaskSize), 474 }, 475 }, 476 }, 477 Actions: types.RoutePolicyActions{ 478 RouteAction: types.RoutePolicyActionNone, 479 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 480 AddLargeCommunities: []string{largeCommunity}, 481 }, 482 }, 483 { 484 Conditions: types.RoutePolicyConditions{ 485 MatchNeighbors: []string{peerAddress}, 486 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 487 { 488 CIDR: netip.MustParsePrefix(string(podPoolUpdated.Spec.IPv6.CIDRs[0])), 489 PrefixLenMin: int(podPoolUpdated.Spec.IPv6.MaskSize), 490 PrefixLenMax: int(podPoolUpdated.Spec.IPv6.MaskSize), 491 }, 492 { 493 CIDR: netip.MustParsePrefix(string(podPoolUpdated.Spec.IPv6.CIDRs[1])), 494 PrefixLenMin: int(podPoolUpdated.Spec.IPv6.MaskSize), 495 PrefixLenMax: int(podPoolUpdated.Spec.IPv6.MaskSize), 496 }, 497 }, 498 }, 499 Actions: types.RoutePolicyActions{ 500 RouteAction: types.RoutePolicyActionNone, 501 AddCommunities: []string{standardCommunity, wellKnownCommunity}, 502 AddLargeCommunities: []string{largeCommunity}, 503 }, 504 }, 505 }, 506 }, 507 }, 508 }, 509 expectError: false, 510 }, 511 { 512 name: "delete policy - non-matching selector", 513 initial: &routePolicyTestInputs{ 514 podCIDRs: []string{ 515 podCIDR, 516 }, 517 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 518 { 519 PeerAddress: peerAddress, 520 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 521 attrSelectAnyNode, 522 }, 523 }, 524 }, 525 expectedPolicies: []*types.RoutePolicy{ 526 { 527 Name: pathAttributesPolicyName(attrSelectAnyNode, peerAddress), 528 Type: types.RoutePolicyTypeExport, 529 Statements: []*types.RoutePolicyStatement{ 530 { 531 Conditions: types.RoutePolicyConditions{ 532 MatchNeighbors: []string{peerAddress}, 533 MatchPrefixes: []*types.RoutePolicyPrefixMatch{ 534 { 535 CIDR: podCIDRPrefix, 536 PrefixLenMin: podCIDRPrefix.Bits(), 537 PrefixLenMax: podCIDRPrefix.Bits(), 538 }, 539 }, 540 }, 541 Actions: types.RoutePolicyActions{ 542 RouteAction: types.RoutePolicyActionNone, 543 SetLocalPreference: attrSelectAnyNode.LocalPreference, 544 }, 545 }, 546 }, 547 }, 548 }, 549 }, 550 updated: &routePolicyTestInputs{ 551 podCIDRs: []string{ 552 podCIDR, 553 }, 554 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 555 { 556 PeerAddress: peerAddress, 557 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 558 attrSelectNonExistingNode, // UPDATED - not matching the node 559 }, 560 }, 561 }, 562 expectedPolicies: nil, 563 }, 564 expectError: false, 565 }, 566 { 567 name: "error - invalid selector", 568 initial: &routePolicyTestInputs{ 569 podCIDRs: []string{ 570 podCIDR, 571 }, 572 neighbors: []v2alpha1api.CiliumBGPNeighbor{ 573 { 574 PeerAddress: peerAddress, 575 AdvertisedPathAttributes: []v2alpha1api.CiliumBGPPathAttributes{ 576 attrSelectInvalid, 577 }, 578 }, 579 }, 580 expectedPolicies: nil, 581 }, 582 expectError: true, 583 }, 584 } 585 586 for _, tt := range table { 587 t.Run(tt.name, func(t *testing.T) { 588 srvParams := types.ServerParameters{ 589 Global: types.BGPGlobal{ 590 ASN: 64125, 591 RouterID: "127.0.0.1", 592 ListenPort: -1, 593 }, 594 } 595 testSC, err := instance.NewServerWithConfig(context.Background(), log, srvParams) 596 require.NoError(t, err) 597 598 testSC.Config = &v2alpha1api.CiliumBGPVirtualRouter{ 599 LocalASN: 64125, 600 ExportPodCIDR: ptr.To[bool](true), 601 Neighbors: tt.initial.neighbors, 602 } 603 604 lbStore := store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumLoadBalancerIPPool]() 605 for _, obj := range tt.initial.LBPools { 606 lbStore.Upsert(obj) 607 } 608 609 podStore := store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumPodIPPool]() 610 for _, obj := range tt.initial.PodPools { 611 podStore.Upsert(obj) 612 } 613 614 policyReconciler := NewRoutePolicyReconciler(lbStore, podStore).Reconciler.(*RoutePolicyReconciler) 615 params := ReconcileParams{ 616 CurrentServer: testSC, 617 DesiredConfig: testSC.Config, 618 CiliumNode: &v2.CiliumNode{ 619 Spec: v2.NodeSpec{ 620 IPAM: ipamTypes.IPAMSpec{ 621 PodCIDRs: tt.initial.podCIDRs, 622 Pools: ipamTypes.IPAMPoolSpec{ 623 Allocated: tt.initial.NodePools, 624 }, 625 }, 626 }, 627 }, 628 } 629 630 // Run the reconciler twice to ensure idempotency. This 631 // simulates the retrying behavior of the controller. 632 for i := 0; i < 2; i++ { 633 t.Run(tt.name+"-init", func(t *testing.T) { 634 err = policyReconciler.Reconcile(context.Background(), params) 635 if tt.expectError { 636 require.Error(t, err) 637 return 638 } 639 require.NoError(t, err) 640 }) 641 } 642 643 // validate cached vs. expected policies 644 validatePoliciesMatch(t, policyReconciler.getMetadata(testSC), tt.initial.expectedPolicies) 645 646 if tt.updated == nil { 647 return // not testing update / remove 648 } 649 650 // follow-up reconcile - update: 651 params.DesiredConfig.Neighbors = tt.updated.neighbors 652 params.CiliumNode.Spec.IPAM.Pools.Allocated = tt.updated.NodePools 653 params.CiliumNode.Spec.IPAM.PodCIDRs = tt.updated.podCIDRs 654 for _, obj := range tt.updated.LBPools { 655 lbStore.Upsert(obj) 656 } 657 for _, obj := range tt.updated.PodPools { 658 podStore.Upsert(obj) 659 } 660 // Run the reconciler twice to ensure idempotency. This 661 // simulates the retrying behavior of the controller. 662 for i := 0; i < 2; i++ { 663 t.Run(tt.name+"-follow-up", func(t *testing.T) { 664 err = policyReconciler.Reconcile(context.Background(), params) 665 require.NoError(t, err) 666 }) 667 } 668 669 // validate cached vs. expected policies 670 validatePoliciesMatch(t, policyReconciler.getMetadata(testSC), tt.updated.expectedPolicies) 671 }) 672 } 673 } 674 675 func validatePoliciesMatch(t *testing.T, actual map[string]*types.RoutePolicy, expected []*types.RoutePolicy) { 676 require.Len(t, actual, len(expected)) 677 678 for _, expPolicy := range expected { 679 policy := actual[expPolicy.Name] 680 require.NotNil(t, policy) 681 require.EqualValues(t, policy, expPolicy) 682 } 683 } 684 685 func TestCommunityDeduplication(t *testing.T) { 686 var table = []struct { 687 name string 688 standard []v2alpha1api.BGPStandardCommunity 689 wellKnown []v2alpha1api.BGPWellKnownCommunity 690 large []v2alpha1api.BGPLargeCommunity 691 expected []string 692 expectErr bool 693 }{ 694 { 695 name: "single standard", 696 standard: []v2alpha1api.BGPStandardCommunity{"64125:100"}, 697 expected: []string{"64125:100"}, 698 }, 699 { 700 name: "single well-known", 701 wellKnown: []v2alpha1api.BGPWellKnownCommunity{"no-advertise"}, 702 expected: []string{"no-advertise"}, 703 }, 704 { 705 name: "single large", 706 large: []v2alpha1api.BGPLargeCommunity{"64125:4294967295:100"}, 707 expected: []string{"64125:4294967295:100"}, 708 }, 709 { 710 name: "duplicate standard", 711 standard: []v2alpha1api.BGPStandardCommunity{"0:100", "100", "0:101", "0:100"}, 712 expected: []string{"0:100", "0:101"}, 713 }, 714 { 715 name: "duplicate well-known", 716 wellKnown: []v2alpha1api.BGPWellKnownCommunity{"no-export", "no-advertise", "no-export"}, 717 expected: []string{"no-export", "no-advertise"}, 718 }, 719 { 720 name: "invalid standard", 721 standard: []v2alpha1api.BGPStandardCommunity{"64125:100", "999999:999999", "64125:101"}, 722 expectErr: true, 723 }, 724 { 725 name: "invalid well-known", 726 wellKnown: []v2alpha1api.BGPWellKnownCommunity{"no-export", "NON-EXISTING"}, 727 expectErr: true, 728 }, 729 { 730 name: "duplicate large", 731 large: []v2alpha1api.BGPLargeCommunity{"64125:4294967295:100", "64125:4294967295:200", "64125:4294967295:200"}, 732 expected: []string{"64125:4294967295:100", "64125:4294967295:200"}, 733 }, 734 { 735 name: "standard + well-known duplicated", 736 standard: []v2alpha1api.BGPStandardCommunity{"64125:100", "64125:101", "65535:65282"}, 737 wellKnown: []v2alpha1api.BGPWellKnownCommunity{"no-export", "no-advertise"}, // no-advertise = "65535:65282" 738 expected: []string{"64125:100", "64125:101", "65535:65282", "no-export"}, 739 }, 740 } 741 for _, tt := range table { 742 t.Run(tt.name, func(t *testing.T) { 743 res, err := mergeAndDedupCommunities(tt.standard, tt.wellKnown) 744 if tt.expectErr { 745 require.Error(t, err) 746 return 747 } 748 require.NoError(t, err) 749 750 res2 := dedupLargeCommunities(tt.large) 751 752 require.EqualValues(t, tt.expected, append(res, res2...)) 753 }) 754 } 755 }