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.