k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/proxy/config/config_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "reflect" 21 "sort" 22 "sync" 23 "testing" 24 "time" 25 26 "k8s.io/api/core/v1" 27 discoveryv1 "k8s.io/api/discovery/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apimachinery/pkg/util/wait" 31 "k8s.io/apimachinery/pkg/watch" 32 informers "k8s.io/client-go/informers" 33 "k8s.io/client-go/kubernetes/fake" 34 ktesting "k8s.io/client-go/testing" 35 klogtesting "k8s.io/klog/v2/ktesting" 36 "k8s.io/utils/ptr" 37 ) 38 39 type sortedServices []*v1.Service 40 41 func (s sortedServices) Len() int { 42 return len(s) 43 } 44 func (s sortedServices) Swap(i, j int) { 45 s[i], s[j] = s[j], s[i] 46 } 47 func (s sortedServices) Less(i, j int) bool { 48 return s[i].Name < s[j].Name 49 } 50 51 type ServiceHandlerMock struct { 52 lock sync.Mutex 53 54 state map[types.NamespacedName]*v1.Service 55 synced bool 56 updated chan []*v1.Service 57 process func([]*v1.Service) 58 } 59 60 func NewServiceHandlerMock() *ServiceHandlerMock { 61 shm := &ServiceHandlerMock{ 62 state: make(map[types.NamespacedName]*v1.Service), 63 updated: make(chan []*v1.Service, 5), 64 } 65 shm.process = func(services []*v1.Service) { 66 shm.updated <- services 67 } 68 return shm 69 } 70 71 func (h *ServiceHandlerMock) OnServiceAdd(service *v1.Service) { 72 h.lock.Lock() 73 defer h.lock.Unlock() 74 namespacedName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} 75 h.state[namespacedName] = service 76 h.sendServices() 77 } 78 79 func (h *ServiceHandlerMock) OnServiceUpdate(oldService, service *v1.Service) { 80 h.lock.Lock() 81 defer h.lock.Unlock() 82 namespacedName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} 83 h.state[namespacedName] = service 84 h.sendServices() 85 } 86 87 func (h *ServiceHandlerMock) OnServiceDelete(service *v1.Service) { 88 h.lock.Lock() 89 defer h.lock.Unlock() 90 namespacedName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} 91 delete(h.state, namespacedName) 92 h.sendServices() 93 } 94 95 func (h *ServiceHandlerMock) OnServiceSynced() { 96 h.lock.Lock() 97 defer h.lock.Unlock() 98 h.synced = true 99 h.sendServices() 100 } 101 102 func (h *ServiceHandlerMock) sendServices() { 103 if !h.synced { 104 return 105 } 106 services := make([]*v1.Service, 0, len(h.state)) 107 for _, svc := range h.state { 108 services = append(services, svc) 109 } 110 sort.Sort(sortedServices(services)) 111 h.process(services) 112 } 113 114 func (h *ServiceHandlerMock) ValidateServices(t *testing.T, expectedServices []*v1.Service) { 115 // We might get 1 or more updates for N service updates, because we 116 // over write older snapshots of services from the producer go-routine 117 // if the consumer falls behind. 118 var services []*v1.Service 119 for { 120 select { 121 case services = <-h.updated: 122 if reflect.DeepEqual(services, expectedServices) { 123 return 124 } 125 // Unittests will hard timeout in 5m with a stack trace, prevent that 126 // and surface a clearer reason for failure. 127 case <-time.After(wait.ForeverTestTimeout): 128 t.Errorf("Timed out. Expected %#v, Got %#v", expectedServices, services) 129 return 130 } 131 } 132 } 133 134 type sortedEndpointSlices []*discoveryv1.EndpointSlice 135 136 func (s sortedEndpointSlices) Len() int { 137 return len(s) 138 } 139 func (s sortedEndpointSlices) Swap(i, j int) { 140 s[i], s[j] = s[j], s[i] 141 } 142 func (s sortedEndpointSlices) Less(i, j int) bool { 143 return s[i].Name < s[j].Name 144 } 145 146 type EndpointSliceHandlerMock struct { 147 lock sync.Mutex 148 149 state map[types.NamespacedName]*discoveryv1.EndpointSlice 150 synced bool 151 updated chan []*discoveryv1.EndpointSlice 152 process func([]*discoveryv1.EndpointSlice) 153 } 154 155 func NewEndpointSliceHandlerMock() *EndpointSliceHandlerMock { 156 ehm := &EndpointSliceHandlerMock{ 157 state: make(map[types.NamespacedName]*discoveryv1.EndpointSlice), 158 updated: make(chan []*discoveryv1.EndpointSlice, 5), 159 } 160 ehm.process = func(endpoints []*discoveryv1.EndpointSlice) { 161 ehm.updated <- endpoints 162 } 163 return ehm 164 } 165 166 func (h *EndpointSliceHandlerMock) OnEndpointSliceAdd(slice *discoveryv1.EndpointSlice) { 167 h.lock.Lock() 168 defer h.lock.Unlock() 169 namespacedName := types.NamespacedName{Namespace: slice.Namespace, Name: slice.Name} 170 h.state[namespacedName] = slice 171 h.sendEndpointSlices() 172 } 173 174 func (h *EndpointSliceHandlerMock) OnEndpointSliceUpdate(oldSlice, slice *discoveryv1.EndpointSlice) { 175 h.lock.Lock() 176 defer h.lock.Unlock() 177 namespacedName := types.NamespacedName{Namespace: slice.Namespace, Name: slice.Name} 178 h.state[namespacedName] = slice 179 h.sendEndpointSlices() 180 } 181 182 func (h *EndpointSliceHandlerMock) OnEndpointSliceDelete(slice *discoveryv1.EndpointSlice) { 183 h.lock.Lock() 184 defer h.lock.Unlock() 185 namespacedName := types.NamespacedName{Namespace: slice.Namespace, Name: slice.Name} 186 delete(h.state, namespacedName) 187 h.sendEndpointSlices() 188 } 189 190 func (h *EndpointSliceHandlerMock) OnEndpointSlicesSynced() { 191 h.lock.Lock() 192 defer h.lock.Unlock() 193 h.synced = true 194 h.sendEndpointSlices() 195 } 196 197 func (h *EndpointSliceHandlerMock) sendEndpointSlices() { 198 if !h.synced { 199 return 200 } 201 slices := make([]*discoveryv1.EndpointSlice, 0, len(h.state)) 202 for _, eps := range h.state { 203 slices = append(slices, eps) 204 } 205 sort.Sort(sortedEndpointSlices(slices)) 206 h.process(slices) 207 } 208 209 func (h *EndpointSliceHandlerMock) ValidateEndpointSlices(t *testing.T, expectedSlices []*discoveryv1.EndpointSlice) { 210 // We might get 1 or more updates for N endpointslice updates, because we 211 // over write older snapshots of endpointslices from the producer go-routine 212 // if the consumer falls behind. Unittests will hard timeout in 5m. 213 var slices []*discoveryv1.EndpointSlice 214 for { 215 select { 216 case slices = <-h.updated: 217 if reflect.DeepEqual(slices, expectedSlices) { 218 return 219 } 220 // Unittests will hard timeout in 5m with a stack trace, prevent that 221 // and surface a clearer reason for failure. 222 case <-time.After(wait.ForeverTestTimeout): 223 t.Errorf("Timed out. Expected %#v, Got %#v", expectedSlices, slices) 224 return 225 } 226 } 227 } 228 229 func TestNewServiceAddedAndNotified(t *testing.T) { 230 _, ctx := klogtesting.NewTestContext(t) 231 client := fake.NewSimpleClientset() 232 fakeWatch := watch.NewFake() 233 client.PrependWatchReactor("services", ktesting.DefaultWatchReactor(fakeWatch, nil)) 234 235 stopCh := make(chan struct{}) 236 defer close(stopCh) 237 238 sharedInformers := informers.NewSharedInformerFactory(client, time.Minute) 239 240 config := NewServiceConfig(ctx, sharedInformers.Core().V1().Services(), time.Minute) 241 handler := NewServiceHandlerMock() 242 config.RegisterEventHandler(handler) 243 go sharedInformers.Start(stopCh) 244 go config.Run(stopCh) 245 246 service := &v1.Service{ 247 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 248 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Protocol: "TCP", Port: 10}}}, 249 } 250 fakeWatch.Add(service) 251 handler.ValidateServices(t, []*v1.Service{service}) 252 } 253 254 func TestServiceAddedRemovedSetAndNotified(t *testing.T) { 255 _, ctx := klogtesting.NewTestContext(t) 256 client := fake.NewSimpleClientset() 257 fakeWatch := watch.NewFake() 258 client.PrependWatchReactor("services", ktesting.DefaultWatchReactor(fakeWatch, nil)) 259 260 stopCh := make(chan struct{}) 261 defer close(stopCh) 262 263 sharedInformers := informers.NewSharedInformerFactory(client, time.Minute) 264 265 config := NewServiceConfig(ctx, sharedInformers.Core().V1().Services(), time.Minute) 266 handler := NewServiceHandlerMock() 267 config.RegisterEventHandler(handler) 268 go sharedInformers.Start(stopCh) 269 go config.Run(stopCh) 270 271 service1 := &v1.Service{ 272 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 273 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Protocol: "TCP", Port: 10}}}, 274 } 275 fakeWatch.Add(service1) 276 handler.ValidateServices(t, []*v1.Service{service1}) 277 278 service2 := &v1.Service{ 279 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, 280 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Protocol: "TCP", Port: 20}}}, 281 } 282 fakeWatch.Add(service2) 283 services := []*v1.Service{service2, service1} 284 handler.ValidateServices(t, services) 285 286 fakeWatch.Delete(service1) 287 services = []*v1.Service{service2} 288 handler.ValidateServices(t, services) 289 } 290 291 func TestNewServicesMultipleHandlersAddedAndNotified(t *testing.T) { 292 _, ctx := klogtesting.NewTestContext(t) 293 client := fake.NewSimpleClientset() 294 fakeWatch := watch.NewFake() 295 client.PrependWatchReactor("services", ktesting.DefaultWatchReactor(fakeWatch, nil)) 296 297 stopCh := make(chan struct{}) 298 defer close(stopCh) 299 300 sharedInformers := informers.NewSharedInformerFactory(client, time.Minute) 301 302 config := NewServiceConfig(ctx, sharedInformers.Core().V1().Services(), time.Minute) 303 handler := NewServiceHandlerMock() 304 handler2 := NewServiceHandlerMock() 305 config.RegisterEventHandler(handler) 306 config.RegisterEventHandler(handler2) 307 go sharedInformers.Start(stopCh) 308 go config.Run(stopCh) 309 310 service1 := &v1.Service{ 311 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 312 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Protocol: "TCP", Port: 10}}}, 313 } 314 service2 := &v1.Service{ 315 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, 316 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Protocol: "TCP", Port: 20}}}, 317 } 318 fakeWatch.Add(service1) 319 fakeWatch.Add(service2) 320 321 services := []*v1.Service{service2, service1} 322 handler.ValidateServices(t, services) 323 handler2.ValidateServices(t, services) 324 } 325 326 func TestNewEndpointsMultipleHandlersAddedAndNotified(t *testing.T) { 327 _, ctx := klogtesting.NewTestContext(t) 328 client := fake.NewSimpleClientset() 329 fakeWatch := watch.NewFake() 330 client.PrependWatchReactor("endpointslices", ktesting.DefaultWatchReactor(fakeWatch, nil)) 331 332 stopCh := make(chan struct{}) 333 defer close(stopCh) 334 335 sharedInformers := informers.NewSharedInformerFactory(client, time.Minute) 336 337 config := NewEndpointSliceConfig(ctx, sharedInformers.Discovery().V1().EndpointSlices(), time.Minute) 338 handler := NewEndpointSliceHandlerMock() 339 handler2 := NewEndpointSliceHandlerMock() 340 config.RegisterEventHandler(handler) 341 config.RegisterEventHandler(handler2) 342 go sharedInformers.Start(stopCh) 343 go config.Run(stopCh) 344 345 endpoints1 := &discoveryv1.EndpointSlice{ 346 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 347 AddressType: discoveryv1.AddressTypeIPv4, 348 Endpoints: []discoveryv1.Endpoint{{ 349 Addresses: []string{"1.1.1.1"}, 350 }, { 351 Addresses: []string{"2.2.2.2"}, 352 }}, 353 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 354 } 355 endpoints2 := &discoveryv1.EndpointSlice{ 356 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, 357 AddressType: discoveryv1.AddressTypeIPv4, 358 Endpoints: []discoveryv1.Endpoint{{ 359 Addresses: []string{"3.3.3.3"}, 360 }, { 361 Addresses: []string{"4.4.4.4"}, 362 }}, 363 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 364 } 365 fakeWatch.Add(endpoints1) 366 fakeWatch.Add(endpoints2) 367 368 endpoints := []*discoveryv1.EndpointSlice{endpoints2, endpoints1} 369 handler.ValidateEndpointSlices(t, endpoints) 370 handler2.ValidateEndpointSlices(t, endpoints) 371 } 372 373 func TestNewEndpointsMultipleHandlersAddRemoveSetAndNotified(t *testing.T) { 374 _, ctx := klogtesting.NewTestContext(t) 375 client := fake.NewSimpleClientset() 376 fakeWatch := watch.NewFake() 377 client.PrependWatchReactor("endpointslices", ktesting.DefaultWatchReactor(fakeWatch, nil)) 378 379 stopCh := make(chan struct{}) 380 defer close(stopCh) 381 382 sharedInformers := informers.NewSharedInformerFactory(client, time.Minute) 383 384 config := NewEndpointSliceConfig(ctx, sharedInformers.Discovery().V1().EndpointSlices(), time.Minute) 385 handler := NewEndpointSliceHandlerMock() 386 handler2 := NewEndpointSliceHandlerMock() 387 config.RegisterEventHandler(handler) 388 config.RegisterEventHandler(handler2) 389 go sharedInformers.Start(stopCh) 390 go config.Run(stopCh) 391 392 endpoints1 := &discoveryv1.EndpointSlice{ 393 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 394 AddressType: discoveryv1.AddressTypeIPv4, 395 Endpoints: []discoveryv1.Endpoint{{ 396 Addresses: []string{"1.1.1.1"}, 397 }, { 398 Addresses: []string{"2.2.2.2"}, 399 }}, 400 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 401 } 402 endpoints2 := &discoveryv1.EndpointSlice{ 403 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, 404 AddressType: discoveryv1.AddressTypeIPv4, 405 Endpoints: []discoveryv1.Endpoint{{ 406 Addresses: []string{"3.3.3.3"}, 407 }, { 408 Addresses: []string{"4.4.4.4"}, 409 }}, 410 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 411 } 412 fakeWatch.Add(endpoints1) 413 fakeWatch.Add(endpoints2) 414 415 endpoints := []*discoveryv1.EndpointSlice{endpoints2, endpoints1} 416 handler.ValidateEndpointSlices(t, endpoints) 417 handler2.ValidateEndpointSlices(t, endpoints) 418 419 // Add one more 420 endpoints3 := &discoveryv1.EndpointSlice{ 421 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foobar"}, 422 AddressType: discoveryv1.AddressTypeIPv4, 423 Endpoints: []discoveryv1.Endpoint{{ 424 Addresses: []string{"5.5.5.5"}, 425 }, { 426 Addresses: []string{"6.6.6.6"}, 427 }}, 428 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 429 } 430 fakeWatch.Add(endpoints3) 431 endpoints = []*discoveryv1.EndpointSlice{endpoints2, endpoints1, endpoints3} 432 handler.ValidateEndpointSlices(t, endpoints) 433 handler2.ValidateEndpointSlices(t, endpoints) 434 435 // Update the "foo" service with new endpoints 436 endpoints1v2 := &discoveryv1.EndpointSlice{ 437 ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, 438 AddressType: discoveryv1.AddressTypeIPv4, 439 Endpoints: []discoveryv1.Endpoint{{ 440 Addresses: []string{"7.7.7.7"}, 441 }}, 442 Ports: []discoveryv1.EndpointPort{{Port: ptr.To[int32](80)}}, 443 } 444 fakeWatch.Modify(endpoints1v2) 445 endpoints = []*discoveryv1.EndpointSlice{endpoints2, endpoints1v2, endpoints3} 446 handler.ValidateEndpointSlices(t, endpoints) 447 handler2.ValidateEndpointSlices(t, endpoints) 448 449 // Remove "bar" endpoints 450 fakeWatch.Delete(endpoints2) 451 endpoints = []*discoveryv1.EndpointSlice{endpoints1v2, endpoints3} 452 handler.ValidateEndpointSlices(t, endpoints) 453 handler2.ValidateEndpointSlices(t, endpoints) 454 } 455 456 // TODO: Add a unittest for interrupts getting processed in a timely manner. 457 // Currently this module has a circular dependency with config, and so it's 458 // named config_test, which means even test methods need to be public. This 459 // is refactoring that we can avoid by resolving the dependency.