github.com/cilium/cilium@v1.16.2/operator/pkg/lbipam/lbipam_fixture_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package lbipam 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "reflect" 11 "runtime/debug" 12 "testing" 13 "time" 14 15 jsonpatch "github.com/evanphx/json-patch" 16 "github.com/sirupsen/logrus" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/types" 19 "k8s.io/apimachinery/pkg/watch" 20 21 cilium_api_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 22 "github.com/cilium/cilium/pkg/k8s/resource" 23 slim_core_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 24 client_typed_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned/typed/core/v1" 25 ) 26 27 // A list of constants which can be re-used during testing. 28 const ( 29 poolAUID = types.UID("eb84f074-806e-474e-85c5-001f5780906b") 30 poolBUID = types.UID("610a54cf-e0e5-4dc6-ace9-0e6ca9a4aaae") 31 32 serviceAUID = types.UID("b801e1cf-9e71-455c-9bc8-52c0575c22bd") 33 serviceBUID = types.UID("b415933e-524c-4f83-8493-de2157fc736f") 34 serviceCUID = types.UID("8d820ef0-d640-497a-bc67-b05190bddee6") 35 ) 36 37 type fakeIPPoolClient struct { 38 resources map[resource.Key]*cilium_api_v2alpha1.CiliumLoadBalancerIPPool 39 } 40 41 func (fic *fakeIPPoolClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *cilium_api_v2alpha1.CiliumLoadBalancerIPPool, err error) { 42 existing, found := fic.resources[resource.Key{Name: name}] 43 if !found { 44 return nil, fmt.Errorf("No IP pool found with name %q", name) 45 } 46 47 old, err := json.Marshal(existing) 48 if err != nil { 49 panic(err) 50 } 51 52 // reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields 53 // in obj that are removed by patch are cleared 54 value := reflect.ValueOf(existing) 55 value.Elem().Set(reflect.New(value.Type().Elem()).Elem()) 56 57 var obj cilium_api_v2alpha1.CiliumLoadBalancerIPPool 58 59 switch pt { 60 case types.JSONPatchType: 61 patch, err := jsonpatch.DecodePatch(data) 62 if err != nil { 63 panic(err) 64 } 65 modified, err := patch.Apply(old) 66 if err != nil { 67 panic(err) 68 } 69 70 if err = json.Unmarshal(modified, &obj); err != nil { 71 panic(err) 72 } 73 default: 74 panic("Unknown patch type") 75 } 76 77 fic.resources[resource.Key{Name: name}] = &obj 78 79 return &obj, nil 80 } 81 82 type fakeSvcClientGetter struct { 83 resources map[resource.Key]*slim_core_v1.Service 84 } 85 86 func (fscg *fakeSvcClientGetter) Services(namespace string) client_typed_v1.ServiceInterface { 87 return &fakeSvcClient{ 88 namespace: namespace, 89 getter: fscg, 90 } 91 } 92 93 type fakeSvcClient struct { 94 namespace string 95 getter *fakeSvcClientGetter 96 } 97 98 func (fsc *fakeSvcClient) Create(ctx context.Context, service *slim_core_v1.Service, opts metav1.CreateOptions) (*slim_core_v1.Service, error) { 99 return nil, nil 100 } 101 func (fsc *fakeSvcClient) Update(ctx context.Context, service *slim_core_v1.Service, opts metav1.UpdateOptions) (*slim_core_v1.Service, error) { 102 return nil, nil 103 } 104 func (fsc *fakeSvcClient) UpdateStatus(ctx context.Context, service *slim_core_v1.Service, opts metav1.UpdateOptions) (*slim_core_v1.Service, error) { 105 return nil, nil 106 } 107 func (fsc *fakeSvcClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 108 return nil 109 } 110 func (fsc *fakeSvcClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*slim_core_v1.Service, error) { 111 return nil, nil 112 } 113 func (fsc *fakeSvcClient) List(ctx context.Context, opts metav1.ListOptions) (*slim_core_v1.ServiceList, error) { 114 return nil, nil 115 } 116 func (fsc *fakeSvcClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 117 return nil, nil 118 } 119 120 func (fsc *fakeSvcClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *slim_core_v1.Service, err error) { 121 existing, found := fsc.getter.resources[resource.Key{Namespace: fsc.namespace, Name: name}] 122 if !found { 123 return nil, fmt.Errorf("No service found with name %q", name) 124 } 125 126 old, err := json.Marshal(existing) 127 if err != nil { 128 panic(err) 129 } 130 131 // reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields 132 // in obj that are removed by patch are cleared 133 value := reflect.ValueOf(existing) 134 value.Elem().Set(reflect.New(value.Type().Elem()).Elem()) 135 136 var obj slim_core_v1.Service 137 138 switch pt { 139 case types.JSONPatchType: 140 patch, err := jsonpatch.DecodePatch(data) 141 if err != nil { 142 panic(err) 143 } 144 modified, err := patch.Apply(old) 145 if err != nil { 146 panic(err) 147 } 148 149 if err = json.Unmarshal(modified, &obj); err != nil { 150 panic(err) 151 } 152 default: 153 panic("Unknown patch type") 154 } 155 156 fsc.getter.resources[resource.Key{Namespace: fsc.namespace, Name: name}] = &obj 157 158 return &obj, nil 159 } 160 161 type newFixture struct { 162 lbipam *LBIPAM 163 164 // mock clients 165 poolClient *fakeIPPoolClient 166 svcClient *fakeSvcClientGetter 167 } 168 169 func (nf *newFixture) GetPool(name string) *cilium_api_v2alpha1.CiliumLoadBalancerIPPool { 170 return nf.poolClient.resources[resource.Key{Name: name}] 171 } 172 173 func (nf *newFixture) UpsertPool(t *testing.T, pool *cilium_api_v2alpha1.CiliumLoadBalancerIPPool) { 174 key := resource.Key{Name: pool.Name} 175 nf.poolClient.resources[key] = pool 176 nf.lbipam.handlePoolEvent(context.Background(), resource.Event[*cilium_api_v2alpha1.CiliumLoadBalancerIPPool]{ 177 Kind: resource.Upsert, 178 Key: key, 179 Object: pool, 180 Done: func(err error) { 181 if err != nil { 182 t.Fatal(err) 183 } 184 }, 185 }) 186 } 187 188 func (nf *newFixture) DeletePool(t *testing.T, pool *cilium_api_v2alpha1.CiliumLoadBalancerIPPool) { 189 key := resource.Key{Name: pool.Name} 190 delete(nf.poolClient.resources, key) 191 nf.lbipam.handlePoolEvent(context.Background(), resource.Event[*cilium_api_v2alpha1.CiliumLoadBalancerIPPool]{ 192 Kind: resource.Delete, 193 Key: key, 194 Object: pool, 195 Done: func(err error) { 196 if err != nil { 197 t.Fatal(err) 198 } 199 }, 200 }) 201 } 202 203 func (nf *newFixture) UpsertSvc(t *testing.T, svc *slim_core_v1.Service) { 204 key := resource.Key{Name: svc.Name, Namespace: svc.Namespace} 205 nf.svcClient.resources[key] = svc 206 nf.lbipam.handleServiceEvent(context.Background(), resource.Event[*slim_core_v1.Service]{ 207 Kind: resource.Upsert, 208 Key: key, 209 Object: svc, 210 Done: func(err error) { 211 if err != nil { 212 debug.PrintStack() 213 t.Fatal(err) 214 } 215 }, 216 }) 217 } 218 219 func (nf *newFixture) DeleteSvc(t *testing.T, svc *slim_core_v1.Service) { 220 key := resource.Key{Name: svc.Name, Namespace: svc.Namespace} 221 delete(nf.svcClient.resources, key) 222 nf.lbipam.handleServiceEvent(context.Background(), resource.Event[*slim_core_v1.Service]{ 223 Kind: resource.Delete, 224 Key: key, 225 Object: svc, 226 Done: func(err error) { 227 if err != nil { 228 t.Fatal(err) 229 } 230 }, 231 }) 232 } 233 234 func (nf *newFixture) GetSvc(namespace, name string) *slim_core_v1.Service { 235 return nf.svcClient.resources[resource.Key{Namespace: namespace, Name: name}] 236 } 237 238 func mkTestFixture(ipv4Enabled, ipv6Enabled bool) newFixture { 239 log := logrus.New() 240 if testing.Verbose() { 241 log.SetLevel(logrus.DebugLevel) 242 } else { 243 log.SetLevel(logrus.ErrorLevel) 244 } 245 246 poolClient := &fakeIPPoolClient{ 247 resources: make(map[resource.Key]*cilium_api_v2alpha1.CiliumLoadBalancerIPPool), 248 } 249 svcClient := &fakeSvcClientGetter{ 250 resources: make(map[resource.Key]*slim_core_v1.Service), 251 } 252 253 return newFixture{ 254 poolClient: poolClient, 255 svcClient: svcClient, 256 lbipam: newLBIPAM(lbIPAMParams{ 257 logger: log, 258 lbClasses: []string{ 259 cilium_api_v2alpha1.BGPLoadBalancerClass, 260 cilium_api_v2alpha1.L2AnnounceLoadBalancerClass, 261 }, 262 ipv4Enabled: ipv4Enabled, 263 ipv6Enabled: ipv6Enabled, 264 265 metrics: newMetrics(), 266 267 poolClient: poolClient, 268 svcClient: svcClient, 269 }), 270 } 271 } 272 273 // mkPool is a constructor function to assist in the creation of new pool objects. 274 func mkPool(uid types.UID, name string, cidrs []string) *cilium_api_v2alpha1.CiliumLoadBalancerIPPool { 275 var blocks []cilium_api_v2alpha1.CiliumLoadBalancerIPPoolIPBlock 276 for _, cidr := range cidrs { 277 blocks = append(blocks, cilium_api_v2alpha1.CiliumLoadBalancerIPPoolIPBlock{ 278 Cidr: cilium_api_v2alpha1.IPv4orIPv6CIDR(cidr), 279 }) 280 } 281 282 return &cilium_api_v2alpha1.CiliumLoadBalancerIPPool{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Name: name, 285 UID: uid, 286 CreationTimestamp: metav1.Date(2022, 10, 16, 12, 00, 00, 0, time.UTC), 287 }, 288 Spec: cilium_api_v2alpha1.CiliumLoadBalancerIPPoolSpec{ 289 Blocks: blocks, 290 }, 291 } 292 }