github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconciler/pod_ip_pool_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  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    14  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    15  	"github.com/cilium/cilium/pkg/bgpv1/types"
    16  	ipamtypes "github.com/cilium/cilium/pkg/ipam/types"
    17  	v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    18  	v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    19  	"github.com/cilium/cilium/pkg/k8s/resource"
    20  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    21  )
    22  
    23  func TestPodIPPoolReconciler(t *testing.T) {
    24  	blueSelector := slim_metav1.LabelSelector{MatchLabels: map[string]string{"color": "blue"}}
    25  
    26  	pool1Key := resource.Key{Name: "pool-1", Namespace: "default"}
    27  	pool1 := &v2alpha1api.CiliumPodIPPool{
    28  		ObjectMeta: meta_v1.ObjectMeta{
    29  			Name:      pool1Key.Name,
    30  			Namespace: pool1Key.Namespace,
    31  			Labels:    blueSelector.MatchLabels,
    32  		},
    33  		Spec: v2alpha1api.IPPoolSpec{
    34  			IPv4: &v2alpha1api.IPv4PoolSpec{
    35  				CIDRs:    []v2alpha1api.PoolCIDR{"10.0.0.0/16"},
    36  				MaskSize: 24,
    37  			},
    38  			IPv6: &v2alpha1api.IPv6PoolSpec{
    39  				CIDRs:    []v2alpha1api.PoolCIDR{"2001:0:0:1234::/64"},
    40  				MaskSize: 96,
    41  			},
    42  		},
    43  	}
    44  
    45  	nsNameSelector := slim_metav1.LabelSelector{
    46  		MatchLabels: map[string]string{
    47  			podIPPoolNamespaceLabel: pool1.Namespace,
    48  			podIPPoolNameLabel:      pool1.Name,
    49  		},
    50  	}
    51  
    52  	pool2Key := resource.Key{Name: "pool-2", Namespace: "default"}
    53  	pool2 := &v2alpha1api.CiliumPodIPPool{
    54  		ObjectMeta: meta_v1.ObjectMeta{
    55  			Name:      pool2Key.Name,
    56  			Namespace: pool2Key.Namespace,
    57  			Labels:    blueSelector.MatchLabels,
    58  		},
    59  		Spec: v2alpha1api.IPPoolSpec{
    60  			IPv4: &v2alpha1api.IPv4PoolSpec{
    61  				CIDRs: []v2alpha1api.PoolCIDR{
    62  					"20.0.0.0/16",
    63  					"30.0.0.0/16",
    64  					"40.0.0.0/16",
    65  				},
    66  				MaskSize: 24,
    67  			},
    68  			IPv6: &v2alpha1api.IPv6PoolSpec{
    69  				CIDRs: []v2alpha1api.PoolCIDR{
    70  					"2002:0:0:1234::/64",
    71  					"2003:0:0:1234::/64",
    72  					"2004:0:0:1234::/64",
    73  				},
    74  				MaskSize: 96,
    75  			},
    76  		},
    77  	}
    78  
    79  	var table = []struct {
    80  		// name of the test case
    81  		name string
    82  		// The pod IP pools allocated to the local node
    83  		nodeAllocs []ipamtypes.IPAMPoolAllocation
    84  		// The pool selector of the vRouter
    85  		poolSelector *slim_metav1.LabelSelector
    86  		// the pools which will be "upserted" in the diffstore
    87  		upsertedPools []*v2alpha1api.CiliumPodIPPool
    88  		// the updated pool CIDR blocks to reconcile
    89  		updated map[resource.Key][]string
    90  		// error nil or not
    91  		err error
    92  	}{
    93  		{
    94  			name:          "no matching node cidrs from pool",
    95  			nodeAllocs:    nil,
    96  			poolSelector:  &blueSelector,
    97  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1},
    98  			updated:       map[resource.Key][]string{},
    99  		},
   100  		{
   101  			name: "match one ipv4 cidr from one pool using special purpose selector",
   102  			nodeAllocs: []ipamtypes.IPAMPoolAllocation{
   103  				{
   104  					Pool:  pool1.Name,
   105  					CIDRs: []ipamtypes.IPAMPodCIDR{"10.0.1.0/24"},
   106  				},
   107  			},
   108  			poolSelector:  &nsNameSelector,
   109  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1},
   110  			updated:       map[resource.Key][]string{pool1Key: {"10.0.1.0/24"}},
   111  		},
   112  		{
   113  			name: "match one ipv4 cidr from one pool",
   114  			nodeAllocs: []ipamtypes.IPAMPoolAllocation{
   115  				{
   116  					Pool:  pool1.Name,
   117  					CIDRs: []ipamtypes.IPAMPodCIDR{"10.0.1.0/24"},
   118  				},
   119  			},
   120  			poolSelector:  &blueSelector,
   121  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1},
   122  			updated:       map[resource.Key][]string{pool1Key: {"10.0.1.0/24"}},
   123  		},
   124  		{
   125  			name: "match one ipv6 cidr from one pool",
   126  			nodeAllocs: []ipamtypes.IPAMPoolAllocation{
   127  				{
   128  					Pool: pool1.Name,
   129  					CIDRs: []ipamtypes.IPAMPodCIDR{
   130  						"2001:0:0:1234:5678::/96",
   131  					},
   132  				},
   133  			},
   134  			poolSelector:  &blueSelector,
   135  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1},
   136  			updated:       map[resource.Key][]string{pool1Key: {"2001:0:0:1234:5678::/96"}},
   137  		},
   138  		{
   139  			name: "match multiple ipv4 and ipv6 cidrs from one pool",
   140  			nodeAllocs: []ipamtypes.IPAMPoolAllocation{
   141  				{
   142  					Pool: pool1.Name,
   143  					CIDRs: []ipamtypes.IPAMPodCIDR{
   144  						"10.0.1.0/24",
   145  						"10.0.2.0/24",
   146  						"10.0.3.0/24",
   147  						"2001:0:0:1234:5678::/96",
   148  						"2001:0:0:1234:5679::/96",
   149  						"2001:0:0:1234:5680::/96",
   150  					},
   151  				},
   152  			},
   153  			poolSelector:  &blueSelector,
   154  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1},
   155  			updated: map[resource.Key][]string{
   156  				pool1Key: {
   157  					"10.0.1.0/24",
   158  					"10.0.2.0/24",
   159  					"10.0.3.0/24",
   160  					"2001:0:0:1234:5678::/96",
   161  					"2001:0:0:1234:5679::/96",
   162  					"2001:0:0:1234:5680::/96",
   163  				},
   164  			},
   165  		},
   166  		{
   167  			name: "match multiple ipv4 and ipv6 cidrs from two pools",
   168  			nodeAllocs: []ipamtypes.IPAMPoolAllocation{
   169  				{
   170  					Pool: pool1.Name,
   171  					CIDRs: []ipamtypes.IPAMPodCIDR{
   172  						"10.0.1.0/24",
   173  						"10.0.2.0/24",
   174  						"2001:0:0:1234:5678::/96",
   175  						"2001:0:0:1234:5679::/96",
   176  					},
   177  				},
   178  				{
   179  					Pool: pool2.Name,
   180  					CIDRs: []ipamtypes.IPAMPodCIDR{
   181  						"20.0.1.0/24",
   182  						"30.0.1.0/24",
   183  						"2002:0:0:1234:5678::/96",
   184  						"2003:0:0:1234:5678::/96",
   185  					},
   186  				},
   187  			},
   188  			poolSelector:  &blueSelector,
   189  			upsertedPools: []*v2alpha1api.CiliumPodIPPool{pool1, pool2},
   190  			updated: map[resource.Key][]string{
   191  				pool1Key: {
   192  					"10.0.1.0/24",
   193  					"10.0.2.0/24",
   194  					"2001:0:0:1234:5678::/96",
   195  					"2001:0:0:1234:5679::/96",
   196  				},
   197  				pool2Key: {
   198  					"20.0.1.0/24",
   199  					"30.0.1.0/24",
   200  					"2002:0:0:1234:5678::/96",
   201  					"2003:0:0:1234:5678::/96",
   202  				},
   203  			},
   204  		},
   205  	}
   206  	for _, tt := range table {
   207  		t.Run(tt.name, func(t *testing.T) {
   208  			// Setup the test server, create a bgp virtual router, and upsert the test pools
   209  			// into the diff store.
   210  			srvParams := types.ServerParameters{
   211  				Global: types.BGPGlobal{
   212  					ASN:        64125,
   213  					RouterID:   "127.0.0.1",
   214  					ListenPort: -1,
   215  				},
   216  			}
   217  			testSC, err := instance.NewServerWithConfig(context.Background(), log, srvParams)
   218  			if err != nil {
   219  				t.Fatalf("failed to create test bgp server: %v", err)
   220  			}
   221  			testSC.Config = &v2alpha1api.CiliumBGPVirtualRouter{
   222  				LocalASN:          64125,
   223  				Neighbors:         []v2alpha1api.CiliumBGPNeighbor{},
   224  				PodIPPoolSelector: tt.poolSelector,
   225  			}
   226  
   227  			// Setup the pool reconciler, local node, CiliumNode, and assign test
   228  			// pools to CiliumNode.
   229  			store := store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumPodIPPool]()
   230  			for _, obj := range tt.upsertedPools {
   231  				store.Upsert(obj)
   232  			}
   233  			reconciler := NewPodIPPoolReconciler(store).Reconciler.(*PodIPPoolReconciler)
   234  
   235  			node := &v2api.CiliumNode{
   236  				ObjectMeta: meta_v1.ObjectMeta{
   237  					Name:      "node1",
   238  					Namespace: "default",
   239  				},
   240  			}
   241  
   242  			if tt.nodeAllocs != nil {
   243  				node.Spec.IPAM.Pools.Allocated = append(node.Spec.IPAM.Pools.Allocated, tt.nodeAllocs...)
   244  			}
   245  
   246  			// Run the reconciler twice to ensure idempotency. This
   247  			// simulates the retrying behavior of the controller.
   248  			for i := 0; i < 2; i++ {
   249  				t.Run(tt.name, func(t *testing.T) {
   250  					err = reconciler.Reconcile(context.Background(), ReconcileParams{
   251  						CurrentServer: testSC,
   252  						DesiredConfig: testSC.Config,
   253  						CiliumNode:    node,
   254  					})
   255  					if err != nil {
   256  						t.Fatalf("failed to reconcile pool cidr advertisements: %v", err)
   257  					}
   258  				})
   259  			}
   260  
   261  			podIPPoolAnnouncements := reconciler.getMetadata(testSC)
   262  
   263  			// If the pool selector is disabled, ensure no advertisements are still present.
   264  			if tt.poolSelector == nil && tt.upsertedPools != nil {
   265  				if len(podIPPoolAnnouncements) > 0 {
   266  					t.Fatal("disabled pool selector but pool cidr advertisements still present")
   267  				}
   268  			}
   269  
   270  			log.Printf("%+v %+v", podIPPoolAnnouncements, tt.updated)
   271  
   272  			// Ensure we see tt.updated in testSC.PodIPPoolAnnouncements
   273  			for poolKey, cidrs := range tt.updated {
   274  				for _, cidr := range cidrs {
   275  					prefix := netip.MustParsePrefix(cidr)
   276  					var seen bool
   277  					for _, advrt := range podIPPoolAnnouncements[poolKey] {
   278  						if advrt.NLRI.String() == prefix.String() {
   279  							seen = true
   280  						}
   281  					}
   282  					if !seen {
   283  						t.Fatalf("failed to advertise %v", cidr)
   284  					}
   285  				}
   286  			}
   287  
   288  			// ensure testSC.PodIPPoolAnnouncements does not contain advertisements
   289  			// not in tt.updated
   290  			for poolKey, advrts := range podIPPoolAnnouncements {
   291  				for _, advrt := range advrts {
   292  					var seen bool
   293  					for _, cidr := range tt.updated[poolKey] {
   294  						if advrt.NLRI.String() == cidr {
   295  							seen = true
   296  						}
   297  					}
   298  					if !seen {
   299  						t.Fatalf("unwanted advert %+v", advrt)
   300  					}
   301  				}
   302  			}
   303  
   304  		})
   305  	}
   306  }