...

Source file src/k8s.io/kubernetes/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller_test.go

Documentation: k8s.io/kubernetes/pkg/controller/endpointslicemirroring

     1  /*
     2  Copyright 2020 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 endpointslicemirroring
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	discovery "k8s.io/api/discovery/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	"k8s.io/client-go/informers"
    30  	"k8s.io/client-go/kubernetes/fake"
    31  	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    34  
    35  	"k8s.io/klog/v2"
    36  	"k8s.io/klog/v2/ktesting"
    37  	"k8s.io/kubernetes/pkg/controller"
    38  )
    39  
    40  // Most of the tests related to EndpointSlice allocation can be found in reconciler_test.go
    41  // Tests here primarily focus on unique controller functionality before the reconciler begins
    42  
    43  var alwaysReady = func() bool { return true }
    44  
    45  type endpointSliceMirroringController struct {
    46  	*Controller
    47  	endpointsStore     cache.Store
    48  	endpointSliceStore cache.Store
    49  	serviceStore       cache.Store
    50  }
    51  
    52  func newController(ctx context.Context, batchPeriod time.Duration) (*fake.Clientset, *endpointSliceMirroringController) {
    53  	client := newClientset()
    54  	informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
    55  
    56  	esController := NewController(
    57  		ctx,
    58  		informerFactory.Core().V1().Endpoints(),
    59  		informerFactory.Discovery().V1().EndpointSlices(),
    60  		informerFactory.Core().V1().Services(),
    61  		int32(1000),
    62  		client,
    63  		batchPeriod)
    64  
    65  	// The event processing pipeline is normally started via Run() method.
    66  	// However, since we don't start it in unit tests, we explicitly start it here.
    67  	esController.eventBroadcaster.StartLogging(klog.Infof)
    68  	esController.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
    69  
    70  	esController.endpointsSynced = alwaysReady
    71  	esController.endpointSlicesSynced = alwaysReady
    72  	esController.servicesSynced = alwaysReady
    73  
    74  	return client, &endpointSliceMirroringController{
    75  		esController,
    76  		informerFactory.Core().V1().Endpoints().Informer().GetStore(),
    77  		informerFactory.Discovery().V1().EndpointSlices().Informer().GetStore(),
    78  		informerFactory.Core().V1().Services().Informer().GetStore(),
    79  	}
    80  }
    81  
    82  func TestSyncEndpoints(t *testing.T) {
    83  	endpointsName := "testing-sync-endpoints"
    84  	namespace := metav1.NamespaceDefault
    85  
    86  	testCases := []struct {
    87  		testName           string
    88  		service            *v1.Service
    89  		endpoints          *v1.Endpoints
    90  		endpointSlices     []*discovery.EndpointSlice
    91  		expectedNumActions int
    92  		expectedNumSlices  int
    93  	}{{
    94  		testName: "Endpoints with no addresses",
    95  		service:  &v1.Service{},
    96  		endpoints: &v1.Endpoints{
    97  			Subsets: []v1.EndpointSubset{{
    98  				Ports: []v1.EndpointPort{{Port: 80}},
    99  			}},
   100  		},
   101  		endpointSlices:     []*discovery.EndpointSlice{},
   102  		expectedNumActions: 0,
   103  		expectedNumSlices:  0,
   104  	}, {
   105  		testName: "Endpoints with skip label true",
   106  		service:  &v1.Service{},
   107  		endpoints: &v1.Endpoints{
   108  			ObjectMeta: metav1.ObjectMeta{
   109  				Labels: map[string]string{discovery.LabelSkipMirror: "true"},
   110  			},
   111  			Subsets: []v1.EndpointSubset{{
   112  				Ports:     []v1.EndpointPort{{Port: 80}},
   113  				Addresses: []v1.EndpointAddress{{IP: "10.0.0.1"}},
   114  			}},
   115  		},
   116  		endpointSlices:     []*discovery.EndpointSlice{},
   117  		expectedNumActions: 0,
   118  		expectedNumSlices:  0,
   119  	}, {
   120  		testName: "Endpoints with skip label false",
   121  		service:  &v1.Service{},
   122  		endpoints: &v1.Endpoints{
   123  			ObjectMeta: metav1.ObjectMeta{
   124  				Labels: map[string]string{discovery.LabelSkipMirror: "false"},
   125  			},
   126  			Subsets: []v1.EndpointSubset{{
   127  				Ports:     []v1.EndpointPort{{Port: 80}},
   128  				Addresses: []v1.EndpointAddress{{IP: "10.0.0.1"}},
   129  			}},
   130  		},
   131  		endpointSlices:     []*discovery.EndpointSlice{},
   132  		expectedNumActions: 1,
   133  		expectedNumSlices:  1,
   134  	}, {
   135  		testName: "Endpoints with missing Service",
   136  		service:  nil,
   137  		endpoints: &v1.Endpoints{
   138  			Subsets: []v1.EndpointSubset{{
   139  				Ports:     []v1.EndpointPort{{Port: 80}},
   140  				Addresses: []v1.EndpointAddress{{IP: "10.0.0.1"}},
   141  			}},
   142  		},
   143  		endpointSlices:     []*discovery.EndpointSlice{},
   144  		expectedNumActions: 0,
   145  		expectedNumSlices:  0,
   146  	}, {
   147  		testName: "Endpoints with Service with selector specified",
   148  		service: &v1.Service{
   149  			Spec: v1.ServiceSpec{
   150  				Selector: map[string]string{"foo": "bar"},
   151  			},
   152  		},
   153  		endpoints: &v1.Endpoints{
   154  			Subsets: []v1.EndpointSubset{{
   155  				Ports:     []v1.EndpointPort{{Port: 80}},
   156  				Addresses: []v1.EndpointAddress{{IP: "10.0.0.1"}},
   157  			}},
   158  		},
   159  		endpointSlices:     []*discovery.EndpointSlice{},
   160  		expectedNumActions: 0,
   161  		expectedNumSlices:  0,
   162  	}, {
   163  		testName: "Existing EndpointSlices that need to be cleaned up",
   164  		service:  &v1.Service{},
   165  		endpoints: &v1.Endpoints{
   166  			Subsets: []v1.EndpointSubset{{
   167  				Ports: []v1.EndpointPort{{Port: 80}},
   168  			}},
   169  		},
   170  		endpointSlices: []*discovery.EndpointSlice{{
   171  			ObjectMeta: metav1.ObjectMeta{
   172  				Name: endpointsName + "-1",
   173  				Labels: map[string]string{
   174  					discovery.LabelServiceName: endpointsName,
   175  					discovery.LabelManagedBy:   controllerName,
   176  				},
   177  			},
   178  		}},
   179  		expectedNumActions: 1,
   180  		expectedNumSlices:  0,
   181  	}, {
   182  		testName: "Existing EndpointSlices managed by a different controller, no addresses to sync",
   183  		service:  &v1.Service{},
   184  		endpoints: &v1.Endpoints{
   185  			Subsets: []v1.EndpointSubset{{
   186  				Ports: []v1.EndpointPort{{Port: 80}},
   187  			}},
   188  		},
   189  		endpointSlices: []*discovery.EndpointSlice{{
   190  			ObjectMeta: metav1.ObjectMeta{
   191  				Name: endpointsName + "-1",
   192  				Labels: map[string]string{
   193  					discovery.LabelManagedBy: "something-else",
   194  				},
   195  			},
   196  		}},
   197  		expectedNumActions: 0,
   198  		// This only queries for EndpointSlices managed by this controller.
   199  		expectedNumSlices: 0,
   200  	}, {
   201  		testName: "Endpoints with 1000 addresses",
   202  		service:  &v1.Service{},
   203  		endpoints: &v1.Endpoints{
   204  			Subsets: []v1.EndpointSubset{{
   205  				Ports:     []v1.EndpointPort{{Port: 80}},
   206  				Addresses: generateAddresses(1000),
   207  			}},
   208  		},
   209  		endpointSlices:     []*discovery.EndpointSlice{},
   210  		expectedNumActions: 1,
   211  		expectedNumSlices:  1,
   212  	}, {
   213  		testName: "Endpoints with 1001 addresses - 1 should not be mirrored",
   214  		service:  &v1.Service{},
   215  		endpoints: &v1.Endpoints{
   216  			Subsets: []v1.EndpointSubset{{
   217  				Ports:     []v1.EndpointPort{{Port: 80}},
   218  				Addresses: generateAddresses(1001),
   219  			}},
   220  		},
   221  		endpointSlices:     []*discovery.EndpointSlice{},
   222  		expectedNumActions: 2, // extra action for creating warning event
   223  		expectedNumSlices:  1,
   224  	}}
   225  
   226  	for _, tc := range testCases {
   227  		t.Run(tc.testName, func(t *testing.T) {
   228  			_, ctx := ktesting.NewTestContext(t)
   229  			client, esController := newController(ctx, time.Duration(0))
   230  			tc.endpoints.Name = endpointsName
   231  			tc.endpoints.Namespace = namespace
   232  			esController.endpointsStore.Add(tc.endpoints)
   233  			if tc.service != nil {
   234  				tc.service.Name = endpointsName
   235  				tc.service.Namespace = namespace
   236  				esController.serviceStore.Add(tc.service)
   237  			}
   238  
   239  			for _, epSlice := range tc.endpointSlices {
   240  				epSlice.Namespace = namespace
   241  				esController.endpointSliceStore.Add(epSlice)
   242  				_, err := client.DiscoveryV1().EndpointSlices(namespace).Create(context.TODO(), epSlice, metav1.CreateOptions{})
   243  				if err != nil {
   244  					t.Fatalf("Expected no error creating EndpointSlice, got %v", err)
   245  				}
   246  			}
   247  
   248  			logger, _ := ktesting.NewTestContext(t)
   249  			err := esController.syncEndpoints(logger, fmt.Sprintf("%s/%s", namespace, endpointsName))
   250  			if err != nil {
   251  				t.Fatalf("Unexpected error from syncEndpoints: %v", err)
   252  			}
   253  
   254  			numInitialActions := len(tc.endpointSlices)
   255  			// Wait for the expected event show up in test "Endpoints with 1001 addresses - 1 should not be mirrored"
   256  			err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (done bool, err error) {
   257  				actions := client.Actions()
   258  				numExtraActions := len(actions) - numInitialActions
   259  				if numExtraActions != tc.expectedNumActions {
   260  					t.Logf("Expected %d additional client actions, got %d: %#v. Will retry", tc.expectedNumActions, numExtraActions, actions[numInitialActions:])
   261  					return false, nil
   262  				}
   263  				return true, nil
   264  			})
   265  			if err != nil {
   266  				t.Fatal("Timed out waiting for expected actions")
   267  			}
   268  
   269  			endpointSlices := fetchEndpointSlices(t, client, namespace)
   270  			expectEndpointSlices(t, tc.expectedNumSlices, int(defaultMaxEndpointsPerSubset), *tc.endpoints, endpointSlices)
   271  		})
   272  	}
   273  }
   274  
   275  func TestShouldMirror(t *testing.T) {
   276  	testCases := []struct {
   277  		testName     string
   278  		endpoints    *v1.Endpoints
   279  		shouldMirror bool
   280  	}{{
   281  		testName: "Standard Endpoints",
   282  		endpoints: &v1.Endpoints{
   283  			ObjectMeta: metav1.ObjectMeta{
   284  				Name: "test-endpoints",
   285  			},
   286  		},
   287  		shouldMirror: true,
   288  	}, {
   289  		testName: "Endpoints with skip-mirror=true",
   290  		endpoints: &v1.Endpoints{
   291  			ObjectMeta: metav1.ObjectMeta{
   292  				Name: "test-endpoints",
   293  				Labels: map[string]string{
   294  					discovery.LabelSkipMirror: "true",
   295  				},
   296  			},
   297  		},
   298  		shouldMirror: false,
   299  	}, {
   300  		testName: "Endpoints with skip-mirror=invalid",
   301  		endpoints: &v1.Endpoints{
   302  			ObjectMeta: metav1.ObjectMeta{
   303  				Name: "test-endpoints",
   304  				Labels: map[string]string{
   305  					discovery.LabelSkipMirror: "invalid",
   306  				},
   307  			},
   308  		},
   309  		shouldMirror: true,
   310  	}, {
   311  		testName: "Endpoints with leader election annotation",
   312  		endpoints: &v1.Endpoints{
   313  			ObjectMeta: metav1.ObjectMeta{
   314  				Name: "test-endpoints",
   315  				Annotations: map[string]string{
   316  					resourcelock.LeaderElectionRecordAnnotationKey: "",
   317  				},
   318  			},
   319  		},
   320  		shouldMirror: false,
   321  	}}
   322  
   323  	for _, tc := range testCases {
   324  		t.Run(tc.testName, func(t *testing.T) {
   325  			_, ctx := ktesting.NewTestContext(t)
   326  			_, c := newController(ctx, time.Duration(0))
   327  
   328  			if tc.endpoints != nil {
   329  				err := c.endpointsStore.Add(tc.endpoints)
   330  				if err != nil {
   331  					t.Fatalf("Error adding Endpoints to store: %v", err)
   332  				}
   333  			}
   334  
   335  			shouldMirror := c.shouldMirror(tc.endpoints)
   336  
   337  			if shouldMirror != tc.shouldMirror {
   338  				t.Errorf("Expected %t to be returned, got %t", tc.shouldMirror, shouldMirror)
   339  			}
   340  		})
   341  	}
   342  }
   343  
   344  func TestEndpointSlicesMirroredForService(t *testing.T) {
   345  	testCases := []struct {
   346  		testName       string
   347  		namespace      string
   348  		name           string
   349  		endpointSlice  *discovery.EndpointSlice
   350  		expectedInList bool
   351  	}{{
   352  		testName:  "Service with matching EndpointSlice",
   353  		namespace: "ns1",
   354  		name:      "svc1",
   355  		endpointSlice: &discovery.EndpointSlice{
   356  			ObjectMeta: metav1.ObjectMeta{
   357  				Name:      "example-1",
   358  				Namespace: "ns1",
   359  				Labels: map[string]string{
   360  					discovery.LabelServiceName: "svc1",
   361  					discovery.LabelManagedBy:   controllerName,
   362  				},
   363  			},
   364  		},
   365  		expectedInList: true,
   366  	}, {
   367  		testName:  "Service with EndpointSlice that has different namespace",
   368  		namespace: "ns1",
   369  		name:      "svc1",
   370  		endpointSlice: &discovery.EndpointSlice{
   371  			ObjectMeta: metav1.ObjectMeta{
   372  				Name:      "example-1",
   373  				Namespace: "ns2",
   374  				Labels: map[string]string{
   375  					discovery.LabelServiceName: "svc1",
   376  					discovery.LabelManagedBy:   controllerName,
   377  				},
   378  			},
   379  		},
   380  		expectedInList: false,
   381  	}, {
   382  		testName:  "Service with EndpointSlice that has different service name",
   383  		namespace: "ns1",
   384  		name:      "svc1",
   385  		endpointSlice: &discovery.EndpointSlice{
   386  			ObjectMeta: metav1.ObjectMeta{
   387  				Name:      "example-1",
   388  				Namespace: "ns1",
   389  				Labels: map[string]string{
   390  					discovery.LabelServiceName: "svc2",
   391  					discovery.LabelManagedBy:   controllerName,
   392  				},
   393  			},
   394  		},
   395  		expectedInList: false,
   396  	}, {
   397  		testName:  "Service with EndpointSlice that has different controller name",
   398  		namespace: "ns1",
   399  		name:      "svc1",
   400  		endpointSlice: &discovery.EndpointSlice{
   401  			ObjectMeta: metav1.ObjectMeta{
   402  				Name:      "example-1",
   403  				Namespace: "ns1",
   404  				Labels: map[string]string{
   405  					discovery.LabelServiceName: "svc1",
   406  					discovery.LabelManagedBy:   controllerName + "foo",
   407  				},
   408  			},
   409  		},
   410  		expectedInList: false,
   411  	}, {
   412  		testName:  "Service with EndpointSlice that has missing controller name",
   413  		namespace: "ns1",
   414  		name:      "svc1",
   415  		endpointSlice: &discovery.EndpointSlice{
   416  			ObjectMeta: metav1.ObjectMeta{
   417  				Name:      "example-1",
   418  				Namespace: "ns1",
   419  				Labels: map[string]string{
   420  					discovery.LabelServiceName: "svc1",
   421  				},
   422  			},
   423  		},
   424  		expectedInList: false,
   425  	}, {
   426  		testName:  "Service with EndpointSlice that has missing service name",
   427  		namespace: "ns1",
   428  		name:      "svc1",
   429  		endpointSlice: &discovery.EndpointSlice{
   430  			ObjectMeta: metav1.ObjectMeta{
   431  				Name:      "example-1",
   432  				Namespace: "ns1",
   433  				Labels: map[string]string{
   434  					discovery.LabelManagedBy: controllerName,
   435  				},
   436  			},
   437  		},
   438  		expectedInList: false,
   439  	}}
   440  
   441  	for _, tc := range testCases {
   442  		t.Run(tc.testName, func(t *testing.T) {
   443  			_, ctx := ktesting.NewTestContext(t)
   444  			_, c := newController(ctx, time.Duration(0))
   445  
   446  			err := c.endpointSliceStore.Add(tc.endpointSlice)
   447  			if err != nil {
   448  				t.Fatalf("Error adding EndpointSlice to store: %v", err)
   449  			}
   450  
   451  			endpointSlices, err := endpointSlicesMirroredForService(c.endpointSliceLister, tc.namespace, tc.name)
   452  			if err != nil {
   453  				t.Fatalf("Expected no error, got %v", err)
   454  			}
   455  
   456  			if tc.expectedInList {
   457  				if len(endpointSlices) != 1 {
   458  					t.Fatalf("Expected 1 EndpointSlice to be in list, got %d", len(endpointSlices))
   459  				}
   460  
   461  				if endpointSlices[0].Name != tc.endpointSlice.Name {
   462  					t.Fatalf("Expected %s EndpointSlice to be in list, got %s", tc.endpointSlice.Name, endpointSlices[0].Name)
   463  				}
   464  			} else {
   465  				if len(endpointSlices) != 0 {
   466  					t.Fatalf("Expected no EndpointSlices to be in list, got %d", len(endpointSlices))
   467  				}
   468  			}
   469  		})
   470  	}
   471  }
   472  
   473  func generateAddresses(num int) []v1.EndpointAddress {
   474  	addresses := make([]v1.EndpointAddress, num)
   475  	for i := 0; i < num; i++ {
   476  		part1 := i / 255
   477  		part2 := i % 255
   478  		ip := fmt.Sprintf("10.0.%d.%d", part1, part2)
   479  		addresses[i] = v1.EndpointAddress{IP: ip}
   480  	}
   481  	return addresses
   482  }
   483  

View as plain text