...

Source file src/k8s.io/kubernetes/pkg/controlplane/reconcilers/endpointsadapter_test.go

Documentation: k8s.io/kubernetes/pkg/controlplane/reconcilers

     1  /*
     2  Copyright 2019 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 reconcilers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	discovery "k8s.io/api/discovery/v1"
    26  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    27  	"k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/client-go/kubernetes/fake"
    32  )
    33  
    34  func TestEndpointsAdapterGet(t *testing.T) {
    35  	endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"})
    36  
    37  	testCases := map[string]struct {
    38  		expectedError     error
    39  		expectedEndpoints *corev1.Endpoints
    40  		initialState      []runtime.Object
    41  		namespaceParam    string
    42  		nameParam         string
    43  	}{
    44  		"single-existing-endpoints": {
    45  			expectedError:     nil,
    46  			expectedEndpoints: endpoints1,
    47  			initialState:      []runtime.Object{endpoints1, epSlice1},
    48  			namespaceParam:    "testing",
    49  			nameParam:         "foo",
    50  		},
    51  		"endpoints exists, endpointslice does not": {
    52  			expectedError:     nil,
    53  			expectedEndpoints: endpoints1,
    54  			initialState:      []runtime.Object{endpoints1},
    55  			namespaceParam:    "testing",
    56  			nameParam:         "foo",
    57  		},
    58  		"endpointslice exists, endpoints does not": {
    59  			expectedError:     errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
    60  			expectedEndpoints: nil,
    61  			initialState:      []runtime.Object{epSlice1},
    62  			namespaceParam:    "testing",
    63  			nameParam:         "foo",
    64  		},
    65  		"wrong-namespace": {
    66  			expectedError:     errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
    67  			expectedEndpoints: nil,
    68  			initialState:      []runtime.Object{endpoints1, epSlice1},
    69  			namespaceParam:    "foo",
    70  			nameParam:         "foo",
    71  		},
    72  		"wrong-name": {
    73  			expectedError:     errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
    74  			expectedEndpoints: nil,
    75  			initialState:      []runtime.Object{endpoints1, epSlice1},
    76  			namespaceParam:    "testing",
    77  			nameParam:         "bar",
    78  		},
    79  	}
    80  
    81  	for name, testCase := range testCases {
    82  		t.Run(name, func(t *testing.T) {
    83  			client := fake.NewSimpleClientset(testCase.initialState...)
    84  			epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
    85  
    86  			endpoints, err := epAdapter.Get(testCase.namespaceParam, testCase.nameParam, metav1.GetOptions{})
    87  
    88  			if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
    89  				t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
    90  			}
    91  
    92  			if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedEndpoints) {
    93  				t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedEndpoints, endpoints)
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func TestEndpointsAdapterCreate(t *testing.T) {
   100  	endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"})
   101  
   102  	// even if an Endpoints resource includes an IPv6 address, it should not be
   103  	// included in the corresponding EndpointSlice.
   104  	endpoints2, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6", "1234::5678:0000:0000:9abc:def0"})
   105  	_, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6"})
   106  
   107  	// ensure that Endpoints with only IPv6 addresses result in EndpointSlice
   108  	// with an IPv6 address type.
   109  	endpoints3, epSlice3 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"1234::5678:0000:0000:9abc:def0"})
   110  	epSlice3.AddressType = discovery.AddressTypeIPv6
   111  
   112  	testCases := map[string]struct {
   113  		expectedError  error
   114  		expectedResult *corev1.Endpoints
   115  		expectCreate   []runtime.Object
   116  		expectUpdate   []runtime.Object
   117  		initialState   []runtime.Object
   118  		namespaceParam string
   119  		endpointsParam *corev1.Endpoints
   120  	}{
   121  		"single-endpoint": {
   122  			expectedError:  nil,
   123  			expectedResult: endpoints1,
   124  			expectCreate:   []runtime.Object{endpoints1, epSlice1},
   125  			initialState:   []runtime.Object{},
   126  			namespaceParam: endpoints1.Namespace,
   127  			endpointsParam: endpoints1,
   128  		},
   129  		"single-endpoint-partial-ipv6": {
   130  			expectedError:  nil,
   131  			expectedResult: endpoints2,
   132  			expectCreate:   []runtime.Object{endpoints2, epSlice2},
   133  			initialState:   []runtime.Object{},
   134  			namespaceParam: endpoints2.Namespace,
   135  			endpointsParam: endpoints2,
   136  		},
   137  		"single-endpoint-full-ipv6": {
   138  			expectedError:  nil,
   139  			expectedResult: endpoints3,
   140  			expectCreate:   []runtime.Object{endpoints3, epSlice3},
   141  			initialState:   []runtime.Object{},
   142  			namespaceParam: endpoints3.Namespace,
   143  			endpointsParam: endpoints3,
   144  		},
   145  		"existing-endpoints": {
   146  			expectedError:  errors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
   147  			expectedResult: nil,
   148  			initialState:   []runtime.Object{endpoints1, epSlice1},
   149  			namespaceParam: endpoints1.Namespace,
   150  			endpointsParam: endpoints1,
   151  
   152  			// We expect the create to be attempted, we just also expect it to fail
   153  			expectCreate: []runtime.Object{endpoints1},
   154  		},
   155  		"existing-endpointslice-incorrect": {
   156  			// No error when we need to create the Endpoints but the correct
   157  			// EndpointSlice already exists
   158  			expectedError:  nil,
   159  			expectedResult: endpoints1,
   160  			expectCreate:   []runtime.Object{endpoints1},
   161  			initialState:   []runtime.Object{epSlice1},
   162  			namespaceParam: endpoints1.Namespace,
   163  			endpointsParam: endpoints1,
   164  		},
   165  		"existing-endpointslice-correct": {
   166  			// No error when we need to create the Endpoints but an incorrect
   167  			// EndpointSlice already exists
   168  			expectedError:  nil,
   169  			expectedResult: endpoints2,
   170  			expectCreate:   []runtime.Object{endpoints2},
   171  			expectUpdate:   []runtime.Object{epSlice2},
   172  			initialState:   []runtime.Object{epSlice1},
   173  			namespaceParam: endpoints2.Namespace,
   174  			endpointsParam: endpoints2,
   175  		},
   176  	}
   177  
   178  	for name, testCase := range testCases {
   179  		t.Run(name, func(t *testing.T) {
   180  			client := fake.NewSimpleClientset(testCase.initialState...)
   181  			epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
   182  
   183  			endpoints, err := epAdapter.Create(testCase.namespaceParam, testCase.endpointsParam)
   184  
   185  			if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
   186  				t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
   187  			}
   188  
   189  			if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) {
   190  				t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints)
   191  			}
   192  
   193  			err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate)
   194  			if err != nil {
   195  				t.Errorf("unexpected error in side effects: %v", err)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestEndpointsAdapterUpdate(t *testing.T) {
   202  	endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"})
   203  	endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
   204  	endpoints3, _ := generateEndpointsAndSlice("bar", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
   205  
   206  	// ensure that EndpointSlice with deprecated IP address type is replaced
   207  	// with one that has an IPv4 address type.
   208  	endpoints4, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
   209  	_, epSlice4IP := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
   210  	// "IP" is a deprecated address type, ensuring that it is handled properly.
   211  	epSlice4IP.AddressType = discovery.AddressType("IP")
   212  	_, epSlice4IPv4 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
   213  
   214  	testCases := map[string]struct {
   215  		expectedError  error
   216  		expectedResult *corev1.Endpoints
   217  		expectCreate   []runtime.Object
   218  		expectUpdate   []runtime.Object
   219  		initialState   []runtime.Object
   220  		namespaceParam string
   221  		endpointsParam *corev1.Endpoints
   222  	}{
   223  		"single-existing-endpoints-no-change": {
   224  			expectedError:  nil,
   225  			expectedResult: endpoints1,
   226  			initialState:   []runtime.Object{endpoints1, epSlice1},
   227  			namespaceParam: "testing",
   228  			endpointsParam: endpoints1,
   229  
   230  			// Even though there's no change, we still expect Update() to be
   231  			// called, because this unit test ALWAYS calls Update().
   232  			expectUpdate: []runtime.Object{endpoints1},
   233  		},
   234  		"existing-endpointslice-replaced-with-updated-ipv4-address-type": {
   235  			expectedError:  nil,
   236  			expectedResult: endpoints4,
   237  			initialState:   []runtime.Object{endpoints4, epSlice4IP},
   238  			namespaceParam: "testing",
   239  			endpointsParam: endpoints4,
   240  
   241  			// When AddressType changes, we Delete+Create the EndpointSlice,
   242  			// so that shows up in expectCreate, not expectUpdate.
   243  			expectUpdate: []runtime.Object{endpoints4},
   244  			expectCreate: []runtime.Object{epSlice4IPv4},
   245  		},
   246  		"add-ports-and-ips": {
   247  			expectedError:  nil,
   248  			expectedResult: endpoints2,
   249  			expectUpdate:   []runtime.Object{endpoints2, epSlice2},
   250  			initialState:   []runtime.Object{endpoints1, epSlice1},
   251  			namespaceParam: "testing",
   252  			endpointsParam: endpoints2,
   253  		},
   254  		"endpoints-correct-endpointslice-wrong": {
   255  			expectedError:  nil,
   256  			expectedResult: endpoints2,
   257  			expectUpdate:   []runtime.Object{endpoints2, epSlice2},
   258  			initialState:   []runtime.Object{endpoints2, epSlice1},
   259  			namespaceParam: "testing",
   260  			endpointsParam: endpoints2,
   261  		},
   262  		"endpointslice-correct-endpoints-wrong": {
   263  			expectedError:  nil,
   264  			expectedResult: endpoints2,
   265  			expectUpdate:   []runtime.Object{endpoints2},
   266  			initialState:   []runtime.Object{endpoints1, epSlice2},
   267  			namespaceParam: "testing",
   268  			endpointsParam: endpoints2,
   269  		},
   270  		"wrong-endpoints": {
   271  			expectedError:  errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
   272  			expectedResult: nil,
   273  			expectUpdate:   []runtime.Object{endpoints3},
   274  			initialState:   []runtime.Object{endpoints1, epSlice1},
   275  			namespaceParam: "testing",
   276  			endpointsParam: endpoints3,
   277  		},
   278  		"missing-endpoints": {
   279  			expectedError:  errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
   280  			expectedResult: nil,
   281  			initialState:   []runtime.Object{endpoints1, epSlice1},
   282  			namespaceParam: "testing",
   283  			endpointsParam: endpoints3,
   284  
   285  			// We expect the update to be attempted, we just also expect it to fail
   286  			expectUpdate: []runtime.Object{endpoints3},
   287  		},
   288  		"missing-endpointslice": {
   289  			// No error when we need to update the Endpoints but the
   290  			// EndpointSlice doesn't exist
   291  			expectedError:  nil,
   292  			expectedResult: endpoints1,
   293  			expectUpdate:   []runtime.Object{endpoints1},
   294  			expectCreate:   []runtime.Object{epSlice1},
   295  			initialState:   []runtime.Object{endpoints2},
   296  			namespaceParam: "testing",
   297  			endpointsParam: endpoints1,
   298  		},
   299  	}
   300  
   301  	for name, testCase := range testCases {
   302  		t.Run(name, func(t *testing.T) {
   303  			client := fake.NewSimpleClientset(testCase.initialState...)
   304  			epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
   305  
   306  			endpoints, err := epAdapter.Update(testCase.namespaceParam, testCase.endpointsParam)
   307  
   308  			if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
   309  				t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
   310  			}
   311  
   312  			if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) {
   313  				t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints)
   314  			}
   315  
   316  			err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate)
   317  			if err != nil {
   318  				t.Errorf("unexpected error in side effects: %v", err)
   319  			}
   320  		})
   321  	}
   322  }
   323  
   324  func generateEndpointsAndSlice(name, namespace string, ports []int, addresses []string) (*corev1.Endpoints, *discovery.EndpointSlice) {
   325  	trueBool := true
   326  
   327  	epSlice := &discovery.EndpointSlice{
   328  		ObjectMeta:  metav1.ObjectMeta{Name: name, Namespace: namespace},
   329  		AddressType: discovery.AddressTypeIPv4,
   330  	}
   331  	epSlice.Labels = map[string]string{discovery.LabelServiceName: name}
   332  	subset := corev1.EndpointSubset{}
   333  
   334  	for i, port := range ports {
   335  		endpointPort := corev1.EndpointPort{
   336  			Name:     fmt.Sprintf("port-%d", i),
   337  			Port:     int32(port),
   338  			Protocol: corev1.ProtocolTCP,
   339  		}
   340  		subset.Ports = append(subset.Ports, endpointPort)
   341  		epSlice.Ports = append(epSlice.Ports, discovery.EndpointPort{
   342  			Name:     &endpointPort.Name,
   343  			Port:     &endpointPort.Port,
   344  			Protocol: &endpointPort.Protocol,
   345  		})
   346  	}
   347  
   348  	for i, address := range addresses {
   349  		endpointAddress := corev1.EndpointAddress{
   350  			IP: address,
   351  			TargetRef: &corev1.ObjectReference{
   352  				Kind: "Pod",
   353  				Name: fmt.Sprintf("pod-%d", i),
   354  			},
   355  		}
   356  
   357  		subset.Addresses = append(subset.Addresses, endpointAddress)
   358  
   359  		epSlice.Endpoints = append(epSlice.Endpoints, discovery.Endpoint{
   360  			Addresses:  []string{endpointAddress.IP},
   361  			TargetRef:  endpointAddress.TargetRef,
   362  			Conditions: discovery.EndpointConditions{Ready: &trueBool},
   363  		})
   364  	}
   365  
   366  	return &corev1.Endpoints{
   367  		ObjectMeta: metav1.ObjectMeta{
   368  			Name:      name,
   369  			Namespace: namespace,
   370  			Labels: map[string]string{
   371  				discovery.LabelSkipMirror: "true",
   372  			},
   373  		},
   374  		Subsets: []corev1.EndpointSubset{subset},
   375  	}, epSlice
   376  }
   377  
   378  func TestEndpointManagerEnsureEndpointSliceFromEndpoints(t *testing.T) {
   379  	endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"})
   380  	endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
   381  
   382  	testCases := map[string]struct {
   383  		expectedError         error
   384  		expectedEndpointSlice *discovery.EndpointSlice
   385  		initialState          []runtime.Object
   386  		namespaceParam        string
   387  		endpointsParam        *corev1.Endpoints
   388  	}{
   389  		"existing-endpointslice-no-change": {
   390  			expectedError:         nil,
   391  			expectedEndpointSlice: epSlice1,
   392  			initialState:          []runtime.Object{epSlice1},
   393  			namespaceParam:        "testing",
   394  			endpointsParam:        endpoints1,
   395  		},
   396  		"existing-endpointslice-change": {
   397  			expectedError:         nil,
   398  			expectedEndpointSlice: epSlice2,
   399  			initialState:          []runtime.Object{epSlice1},
   400  			namespaceParam:        "testing",
   401  			endpointsParam:        endpoints2,
   402  		},
   403  		"missing-endpointslice": {
   404  			expectedError:         nil,
   405  			expectedEndpointSlice: epSlice1,
   406  			initialState:          []runtime.Object{},
   407  			namespaceParam:        "testing",
   408  			endpointsParam:        endpoints1,
   409  		},
   410  	}
   411  
   412  	for name, testCase := range testCases {
   413  		t.Run(name, func(t *testing.T) {
   414  			client := fake.NewSimpleClientset(testCase.initialState...)
   415  			epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
   416  
   417  			err := epAdapter.EnsureEndpointSliceFromEndpoints(testCase.namespaceParam, testCase.endpointsParam)
   418  			if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
   419  				t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
   420  			}
   421  
   422  			endpointSlice, err := client.DiscoveryV1().EndpointSlices(testCase.namespaceParam).Get(context.TODO(), testCase.endpointsParam.Name, metav1.GetOptions{})
   423  			if err != nil && !errors.IsNotFound(err) {
   424  				t.Fatalf("Error getting Endpoint Slice: %v", err)
   425  			}
   426  
   427  			if !apiequality.Semantic.DeepEqual(endpointSlice, testCase.expectedEndpointSlice) {
   428  				t.Errorf("Expected Endpoint Slice: %v, got: %v", testCase.expectedEndpointSlice, endpointSlice)
   429  			}
   430  		})
   431  	}
   432  }
   433  

View as plain text