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 }