...

Source file src/k8s.io/kubernetes/pkg/proxy/endpointslicecache_test.go

Documentation: k8s.io/kubernetes/pkg/proxy

     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 proxy
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	discovery "k8s.io/api/discovery/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/utils/ptr"
    30  )
    31  
    32  func TestEndpointsMapFromESC(t *testing.T) {
    33  	testCases := map[string]struct {
    34  		endpointSlices []*discovery.EndpointSlice
    35  		hostname       string
    36  		namespacedName types.NamespacedName
    37  		expectedMap    map[ServicePortName][]*BaseEndpointInfo
    38  	}{
    39  		"1 slice, 2 hosts, ports 80,443": {
    40  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
    41  			hostname:       "host1",
    42  			endpointSlices: []*discovery.EndpointSlice{
    43  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
    44  			},
    45  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
    46  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
    47  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
    48  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
    49  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
    50  				},
    51  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
    52  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: false, ready: true, serving: true, terminating: false},
    53  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
    54  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: false, ready: true, serving: true, terminating: false},
    55  				},
    56  			},
    57  		},
    58  		"2 slices, same port": {
    59  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
    60  			endpointSlices: []*discovery.EndpointSlice{
    61  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
    62  				generateEndpointSlice("svc1", "ns1", 2, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
    63  			},
    64  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
    65  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
    66  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
    67  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
    68  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
    69  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false},
    70  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: false, ready: true, serving: true, terminating: false},
    71  					&BaseEndpointInfo{ip: "10.0.2.3", port: 80, endpoint: "10.0.2.3:80", isLocal: false, ready: true, serving: true, terminating: false},
    72  				},
    73  			},
    74  		},
    75  		// 2 slices, with some overlapping endpoints, result should be a union
    76  		// of the 2.
    77  		"2 overlapping slices, same port": {
    78  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
    79  			endpointSlices: []*discovery.EndpointSlice{
    80  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
    81  				generateEndpointSlice("svc1", "ns1", 1, 4, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
    82  			},
    83  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
    84  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
    85  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
    86  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
    87  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
    88  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
    89  				},
    90  			},
    91  		},
    92  		// 2 slices with all endpoints overlapping, more unready in first than
    93  		// second. If an endpoint is marked ready, we add it to the
    94  		// EndpointsMap, even if conditions.Ready isn't true for another
    95  		// matching endpoint
    96  		"2 slices, overlapping endpoints, some endpoints unready in 1 or both": {
    97  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
    98  			endpointSlices: []*discovery.EndpointSlice{
    99  				generateEndpointSlice("svc1", "ns1", 1, 10, 3, 999, []string{}, []*int32{ptr.To[int32](80)}),
   100  				generateEndpointSlice("svc1", "ns1", 1, 10, 6, 999, []string{}, []*int32{ptr.To[int32](80)}),
   101  			},
   102  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
   103  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   104  					&BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: true, serving: true, terminating: false},
   105  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
   106  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
   107  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
   108  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
   109  					&BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: true, serving: true, terminating: false},
   110  					&BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
   111  					&BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: true, serving: true, terminating: false},
   112  					&BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: true, serving: true, terminating: false},
   113  					&BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: true, serving: true, terminating: false},
   114  				},
   115  			},
   116  		},
   117  		// 2 slices with all endpoints overlapping, some unready and terminating.
   118  		"2 slices, overlapping endpoints, some endpoints unready and some endpoints terminating": {
   119  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   120  			endpointSlices: []*discovery.EndpointSlice{
   121  				generateEndpointSlice("svc1", "ns1", 1, 10, 3, 5, []string{}, []*int32{ptr.To[int32](80)}),
   122  				generateEndpointSlice("svc1", "ns1", 1, 10, 6, 5, []string{}, []*int32{ptr.To[int32](80)}),
   123  			},
   124  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
   125  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   126  					&BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: false, serving: true, terminating: true},
   127  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
   128  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
   129  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
   130  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
   131  					&BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: false, serving: true, terminating: true},
   132  					&BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
   133  					&BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: true, serving: true, terminating: false},
   134  					&BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: true, serving: true, terminating: false},
   135  					&BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: true, serving: true, terminating: false},
   136  				},
   137  			},
   138  		},
   139  		"2 slices, overlapping endpoints, all unready": {
   140  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   141  			endpointSlices: []*discovery.EndpointSlice{
   142  				generateEndpointSlice("svc1", "ns1", 1, 10, 1, 999, []string{}, []*int32{ptr.To[int32](80)}),
   143  				generateEndpointSlice("svc1", "ns1", 1, 10, 1, 999, []string{}, []*int32{ptr.To[int32](80)}),
   144  			},
   145  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
   146  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   147  					&BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: false, serving: false, terminating: false},
   148  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: false, serving: false, terminating: false},
   149  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: false, serving: false, terminating: false},
   150  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: false, serving: false, terminating: false},
   151  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: false, serving: false, terminating: false},
   152  					&BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: false, serving: false, terminating: false},
   153  					&BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
   154  					&BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: false, serving: false, terminating: false},
   155  					&BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: false, serving: false, terminating: false},
   156  					&BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: false, serving: false, terminating: false},
   157  				},
   158  			},
   159  		},
   160  		"3 slices with different services and namespaces": {
   161  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   162  			endpointSlices: []*discovery.EndpointSlice{
   163  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
   164  				generateEndpointSlice("svc2", "ns1", 2, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
   165  				generateEndpointSlice("svc1", "ns2", 3, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
   166  			},
   167  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
   168  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   169  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
   170  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
   171  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
   172  				},
   173  			},
   174  		},
   175  		// Ensuring that nil port value will not break things. This will
   176  		// represent all ports in the future, but that has not been implemented
   177  		// yet.
   178  		"Nil port should not break anything": {
   179  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   180  			hostname:       "host1",
   181  			endpointSlices: []*discovery.EndpointSlice{
   182  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{nil}),
   183  			},
   184  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{},
   185  		},
   186  		// Make sure that different endpoints with duplicate IPs are returned correctly.
   187  		"Different endpoints with duplicate IPs should not be filtered": {
   188  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   189  			hostname:       "host1",
   190  			endpointSlices: []*discovery.EndpointSlice{
   191  				generateEndpointSliceWithOffset("svc1", "ns1", 1, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
   192  				generateEndpointSliceWithOffset("svc1", "ns1", 2, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](8080)}),
   193  			},
   194  			expectedMap: map[ServicePortName][]*BaseEndpointInfo{
   195  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   196  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
   197  					&BaseEndpointInfo{ip: "10.0.1.1", port: 8080, endpoint: "10.0.1.1:8080", isLocal: false, ready: true, serving: true, terminating: false},
   198  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
   199  					&BaseEndpointInfo{ip: "10.0.1.2", port: 8080, endpoint: "10.0.1.2:8080", isLocal: true, ready: true, serving: true, terminating: false},
   200  				},
   201  			},
   202  		},
   203  	}
   204  
   205  	for name, tc := range testCases {
   206  		t.Run(name, func(t *testing.T) {
   207  			esCache := NewEndpointSliceCache(tc.hostname, v1.IPv4Protocol, nil, nil)
   208  
   209  			cmc := newCacheMutationCheck(tc.endpointSlices)
   210  			for _, endpointSlice := range tc.endpointSlices {
   211  				esCache.updatePending(endpointSlice, false)
   212  			}
   213  
   214  			compareEndpointsMapsStr(t, esCache.getEndpointsMap(tc.namespacedName, esCache.trackerByServiceMap[tc.namespacedName].pending), tc.expectedMap)
   215  			cmc.Check(t)
   216  		})
   217  	}
   218  }
   219  
   220  func TestEndpointInfoByServicePort(t *testing.T) {
   221  	testCases := map[string]struct {
   222  		namespacedName types.NamespacedName
   223  		endpointSlices []*discovery.EndpointSlice
   224  		hostname       string
   225  		expectedMap    spToEndpointMap
   226  	}{
   227  		"simple use case with 3 endpoints": {
   228  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   229  			hostname:       "host1",
   230  			endpointSlices: []*discovery.EndpointSlice{
   231  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
   232  			},
   233  			expectedMap: spToEndpointMap{
   234  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   235  					"10.0.1.1:80": &BaseEndpointInfo{
   236  						ip:          "10.0.1.1",
   237  						port:        80,
   238  						endpoint:    "10.0.1.1:80",
   239  						isLocal:     false,
   240  						ready:       true,
   241  						serving:     true,
   242  						terminating: false,
   243  					},
   244  					"10.0.1.2:80": &BaseEndpointInfo{
   245  						ip:          "10.0.1.2",
   246  						port:        80,
   247  						endpoint:    "10.0.1.2:80",
   248  						isLocal:     true,
   249  						ready:       true,
   250  						serving:     true,
   251  						terminating: false,
   252  					},
   253  					"10.0.1.3:80": &BaseEndpointInfo{
   254  						ip:          "10.0.1.3",
   255  						port:        80,
   256  						endpoint:    "10.0.1.3:80",
   257  						isLocal:     false,
   258  						ready:       true,
   259  						serving:     true,
   260  						terminating: false,
   261  					},
   262  				},
   263  			},
   264  		},
   265  		"4 different slices with duplicate IPs": {
   266  			namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
   267  			hostname:       "host1",
   268  			endpointSlices: []*discovery.EndpointSlice{
   269  				generateEndpointSliceWithOffset("svc1", "ns1", 1, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
   270  				generateEndpointSliceWithOffset("svc1", "ns1", 2, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](8080)}),
   271  			},
   272  			expectedMap: spToEndpointMap{
   273  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
   274  					"10.0.1.1:80": &BaseEndpointInfo{
   275  						ip:          "10.0.1.1",
   276  						port:        80,
   277  						endpoint:    "10.0.1.1:80",
   278  						isLocal:     false,
   279  						ready:       true,
   280  						serving:     true,
   281  						terminating: false,
   282  					},
   283  					"10.0.1.2:80": &BaseEndpointInfo{
   284  						ip:          "10.0.1.2",
   285  						port:        80,
   286  						endpoint:    "10.0.1.2:80",
   287  						isLocal:     true,
   288  						ready:       true,
   289  						serving:     true,
   290  						terminating: false,
   291  					},
   292  					"10.0.1.1:8080": &BaseEndpointInfo{
   293  						ip:          "10.0.1.1",
   294  						port:        8080,
   295  						endpoint:    "10.0.1.1:8080",
   296  						isLocal:     false,
   297  						ready:       true,
   298  						serving:     true,
   299  						terminating: false,
   300  					},
   301  					"10.0.1.2:8080": &BaseEndpointInfo{
   302  						ip:          "10.0.1.2",
   303  						port:        8080,
   304  						endpoint:    "10.0.1.2:8080",
   305  						isLocal:     true,
   306  						ready:       true,
   307  						serving:     true,
   308  						terminating: false,
   309  					},
   310  				},
   311  			},
   312  		},
   313  	}
   314  
   315  	for name, tc := range testCases {
   316  		t.Run(name, func(t *testing.T) {
   317  			esCache := NewEndpointSliceCache(tc.hostname, v1.IPv4Protocol, nil, nil)
   318  
   319  			for _, endpointSlice := range tc.endpointSlices {
   320  				esCache.updatePending(endpointSlice, false)
   321  			}
   322  
   323  			got := esCache.endpointInfoByServicePort(tc.namespacedName, esCache.trackerByServiceMap[tc.namespacedName].pending)
   324  			if !reflect.DeepEqual(got, tc.expectedMap) {
   325  				t.Errorf("endpointInfoByServicePort does not match. Want: %+v, Got: %+v", tc.expectedMap, got)
   326  			}
   327  		})
   328  	}
   329  }
   330  
   331  func TestEsDataChanged(t *testing.T) {
   332  	p80 := int32(80)
   333  	p443 := int32(443)
   334  	port80 := discovery.EndpointPort{Port: &p80, Name: ptr.To("http"), Protocol: ptr.To(v1.ProtocolTCP)}
   335  	port443 := discovery.EndpointPort{Port: &p443, Name: ptr.To("https"), Protocol: ptr.To(v1.ProtocolTCP)}
   336  	endpoint1 := discovery.Endpoint{Addresses: []string{"10.0.1.0"}}
   337  	endpoint2 := discovery.Endpoint{Addresses: []string{"10.0.1.1"}}
   338  
   339  	objMeta := metav1.ObjectMeta{
   340  		Name:      "foo",
   341  		Namespace: "bar",
   342  		Labels:    map[string]string{discovery.LabelServiceName: "svc1"},
   343  	}
   344  
   345  	testCases := map[string]struct {
   346  		cache         *EndpointSliceCache
   347  		initialSlice  *discovery.EndpointSlice
   348  		updatedSlice  *discovery.EndpointSlice
   349  		expectChanged bool
   350  	}{
   351  		"identical slices, ports only": {
   352  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   353  			initialSlice: &discovery.EndpointSlice{
   354  				ObjectMeta: objMeta,
   355  				Ports:      []discovery.EndpointPort{port80},
   356  			},
   357  			updatedSlice: &discovery.EndpointSlice{
   358  				ObjectMeta: objMeta,
   359  				Ports:      []discovery.EndpointPort{port80},
   360  			},
   361  			expectChanged: false,
   362  		},
   363  		"identical slices, ports out of order": {
   364  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   365  			initialSlice: &discovery.EndpointSlice{
   366  				ObjectMeta: objMeta,
   367  				Ports:      []discovery.EndpointPort{port443, port80},
   368  			},
   369  			updatedSlice: &discovery.EndpointSlice{
   370  				ObjectMeta: objMeta,
   371  				Ports:      []discovery.EndpointPort{port80, port443},
   372  			},
   373  			expectChanged: false,
   374  		},
   375  		"port removed": {
   376  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   377  			initialSlice: &discovery.EndpointSlice{
   378  				ObjectMeta: objMeta,
   379  				Ports:      []discovery.EndpointPort{port443, port80},
   380  			},
   381  			updatedSlice: &discovery.EndpointSlice{
   382  				ObjectMeta: objMeta,
   383  				Ports:      []discovery.EndpointPort{port443},
   384  			},
   385  			expectChanged: true,
   386  		},
   387  		"port added": {
   388  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   389  			initialSlice: &discovery.EndpointSlice{
   390  				ObjectMeta: objMeta,
   391  				Ports:      []discovery.EndpointPort{port443},
   392  			},
   393  			updatedSlice: &discovery.EndpointSlice{
   394  				ObjectMeta: objMeta,
   395  				Ports:      []discovery.EndpointPort{port443, port80},
   396  			},
   397  			expectChanged: true,
   398  		},
   399  		"identical with endpoints": {
   400  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   401  			initialSlice: &discovery.EndpointSlice{
   402  				ObjectMeta: objMeta,
   403  				Ports:      []discovery.EndpointPort{port443},
   404  				Endpoints:  []discovery.Endpoint{endpoint1, endpoint2},
   405  			},
   406  			updatedSlice: &discovery.EndpointSlice{
   407  				ObjectMeta: objMeta,
   408  				Ports:      []discovery.EndpointPort{port443},
   409  				Endpoints:  []discovery.Endpoint{endpoint1, endpoint2},
   410  			},
   411  			expectChanged: false,
   412  		},
   413  		"identical with endpoints out of order": {
   414  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   415  			initialSlice: &discovery.EndpointSlice{
   416  				ObjectMeta: objMeta,
   417  				Ports:      []discovery.EndpointPort{port443},
   418  				Endpoints:  []discovery.Endpoint{endpoint1, endpoint2},
   419  			},
   420  			updatedSlice: &discovery.EndpointSlice{
   421  				ObjectMeta: objMeta,
   422  				Ports:      []discovery.EndpointPort{port443},
   423  				Endpoints:  []discovery.Endpoint{endpoint2, endpoint1},
   424  			},
   425  			expectChanged: false,
   426  		},
   427  		"identical with endpoint added": {
   428  			cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
   429  			initialSlice: &discovery.EndpointSlice{
   430  				ObjectMeta: objMeta,
   431  				Ports:      []discovery.EndpointPort{port443},
   432  				Endpoints:  []discovery.Endpoint{endpoint1},
   433  			},
   434  			updatedSlice: &discovery.EndpointSlice{
   435  				ObjectMeta: objMeta,
   436  				Ports:      []discovery.EndpointPort{port443},
   437  				Endpoints:  []discovery.Endpoint{endpoint2, endpoint1},
   438  			},
   439  			expectChanged: true,
   440  		},
   441  	}
   442  
   443  	for name, tc := range testCases {
   444  		t.Run(name, func(t *testing.T) {
   445  			cmc := newCacheMutationCheck([]*discovery.EndpointSlice{tc.initialSlice})
   446  
   447  			if tc.initialSlice != nil {
   448  				tc.cache.updatePending(tc.initialSlice, false)
   449  				tc.cache.checkoutChanges()
   450  			}
   451  
   452  			serviceKey, sliceKey, err := endpointSliceCacheKeys(tc.updatedSlice)
   453  			if err != nil {
   454  				t.Fatalf("Expected no error calling endpointSliceCacheKeys(): %v", err)
   455  			}
   456  
   457  			esData := newEndpointSliceData(tc.updatedSlice, false)
   458  			changed := tc.cache.esDataChanged(serviceKey, sliceKey, esData)
   459  
   460  			if tc.expectChanged != changed {
   461  				t.Errorf("Expected esDataChanged() to return %t, got %t", tc.expectChanged, changed)
   462  			}
   463  
   464  			cmc.Check(t)
   465  		})
   466  	}
   467  }
   468  
   469  func generateEndpointSliceWithOffset(serviceName, namespace string, sliceNum, offset, numEndpoints, unreadyMod int, terminatingMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice {
   470  	endpointSlice := &discovery.EndpointSlice{
   471  		ObjectMeta: metav1.ObjectMeta{
   472  			Name:      fmt.Sprintf("%s-%d", serviceName, sliceNum),
   473  			Namespace: namespace,
   474  			Labels:    map[string]string{discovery.LabelServiceName: serviceName},
   475  		},
   476  		Ports:       []discovery.EndpointPort{},
   477  		AddressType: discovery.AddressTypeIPv4,
   478  		Endpoints:   []discovery.Endpoint{},
   479  	}
   480  
   481  	for i, portNum := range portNums {
   482  		endpointSlice.Ports = append(endpointSlice.Ports, discovery.EndpointPort{
   483  			Name:     ptr.To(fmt.Sprintf("port-%d", i)),
   484  			Port:     portNum,
   485  			Protocol: ptr.To(v1.ProtocolTCP),
   486  		})
   487  	}
   488  
   489  	for i := 1; i <= numEndpoints; i++ {
   490  		readyCondition := i%unreadyMod != 0
   491  		terminatingCondition := i%terminatingMod == 0
   492  
   493  		ready := ptr.To(readyCondition && !terminatingCondition)
   494  		serving := ptr.To(readyCondition)
   495  		terminating := ptr.To(terminatingCondition)
   496  
   497  		endpoint := discovery.Endpoint{
   498  			Addresses: []string{fmt.Sprintf("10.0.%d.%d", offset, i)},
   499  			Conditions: discovery.EndpointConditions{
   500  				Ready:       ready,
   501  				Serving:     serving,
   502  				Terminating: terminating,
   503  			},
   504  		}
   505  
   506  		if len(hosts) > 0 {
   507  			hostname := hosts[i%len(hosts)]
   508  			endpoint.NodeName = &hostname
   509  		}
   510  
   511  		endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpoint)
   512  	}
   513  
   514  	return endpointSlice
   515  }
   516  
   517  func generateEndpointSlice(serviceName, namespace string, sliceNum, numEndpoints, unreadyMod int, terminatingMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice {
   518  	return generateEndpointSliceWithOffset(serviceName, namespace, sliceNum, sliceNum, numEndpoints, unreadyMod, terminatingMod, hosts, portNums)
   519  }
   520  
   521  // cacheMutationCheck helps ensure that cached objects have not been changed
   522  // in any way throughout a test run.
   523  type cacheMutationCheck struct {
   524  	objects []cacheObject
   525  }
   526  
   527  // cacheObject stores a reference to an original object as well as a deep copy
   528  // of that object to track any mutations in the original object.
   529  type cacheObject struct {
   530  	original runtime.Object
   531  	deepCopy runtime.Object
   532  }
   533  
   534  // newCacheMutationCheck initializes a cacheMutationCheck with EndpointSlices.
   535  func newCacheMutationCheck(endpointSlices []*discovery.EndpointSlice) cacheMutationCheck {
   536  	cmc := cacheMutationCheck{}
   537  	for _, endpointSlice := range endpointSlices {
   538  		cmc.Add(endpointSlice)
   539  	}
   540  	return cmc
   541  }
   542  
   543  // Add appends a runtime.Object and a deep copy of that object into the
   544  // cacheMutationCheck.
   545  func (cmc *cacheMutationCheck) Add(o runtime.Object) {
   546  	cmc.objects = append(cmc.objects, cacheObject{
   547  		original: o,
   548  		deepCopy: o.DeepCopyObject(),
   549  	})
   550  }
   551  
   552  // Check verifies that no objects in the cacheMutationCheck have been mutated.
   553  func (cmc *cacheMutationCheck) Check(t *testing.T) {
   554  	for _, o := range cmc.objects {
   555  		if !reflect.DeepEqual(o.original, o.deepCopy) {
   556  			// Cached objects can't be safely mutated and instead should be deep
   557  			// copied before changed in any way.
   558  			t.Errorf("Cached object was unexpectedly mutated. Original: %+v, Mutated: %+v", o.deepCopy, o.original)
   559  		}
   560  	}
   561  }
   562  

View as plain text