...

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

Documentation: k8s.io/kubernetes/pkg/proxy

     1  /*
     2  Copyright 2017 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  	"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/types"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/utils/ptr"
    31  )
    32  
    33  func (proxier *FakeProxier) addEndpointSlice(slice *discovery.EndpointSlice) {
    34  	proxier.endpointsChanges.EndpointSliceUpdate(slice, false)
    35  }
    36  
    37  func (proxier *FakeProxier) updateEndpointSlice(oldSlice, slice *discovery.EndpointSlice) {
    38  	proxier.endpointsChanges.EndpointSliceUpdate(slice, false)
    39  }
    40  
    41  func (proxier *FakeProxier) deleteEndpointSlice(slice *discovery.EndpointSlice) {
    42  	proxier.endpointsChanges.EndpointSliceUpdate(slice, true)
    43  }
    44  
    45  func TestGetLocalEndpointIPs(t *testing.T) {
    46  	testCases := []struct {
    47  		endpointsMap EndpointsMap
    48  		expected     map[types.NamespacedName]sets.Set[string]
    49  	}{{
    50  		// Case[0]: nothing
    51  		endpointsMap: EndpointsMap{},
    52  		expected:     map[types.NamespacedName]sets.Set[string]{},
    53  	}, {
    54  		// Case[1]: unnamed port
    55  		endpointsMap: EndpointsMap{
    56  			makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): []Endpoint{
    57  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
    58  			},
    59  		},
    60  		expected: map[types.NamespacedName]sets.Set[string]{},
    61  	}, {
    62  		// Case[2]: unnamed port local
    63  		endpointsMap: EndpointsMap{
    64  			makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): []Endpoint{
    65  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
    66  			},
    67  		},
    68  		expected: map[types.NamespacedName]sets.Set[string]{
    69  			{Namespace: "ns1", Name: "ep1"}: sets.New[string]("1.1.1.1"),
    70  		},
    71  	}, {
    72  		// Case[3]: named local and non-local ports for the same IP.
    73  		endpointsMap: EndpointsMap{
    74  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolTCP): []Endpoint{
    75  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
    76  				&BaseEndpointInfo{ip: "1.1.1.2", port: 11, endpoint: "1.1.1.2:11", isLocal: true, ready: true, serving: true, terminating: false},
    77  			},
    78  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolTCP): []Endpoint{
    79  				&BaseEndpointInfo{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: false, ready: true, serving: true, terminating: false},
    80  				&BaseEndpointInfo{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
    81  			},
    82  		},
    83  		expected: map[types.NamespacedName]sets.Set[string]{
    84  			{Namespace: "ns1", Name: "ep1"}: sets.New[string]("1.1.1.2"),
    85  		},
    86  	}, {
    87  		// Case[4]: named local and non-local ports for different IPs.
    88  		endpointsMap: EndpointsMap{
    89  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolTCP): []Endpoint{
    90  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
    91  			},
    92  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolTCP): []Endpoint{
    93  				&BaseEndpointInfo{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: true, serving: true, terminating: false},
    94  				&BaseEndpointInfo{ip: "2.2.2.22", port: 22, endpoint: "2.2.2.22:22", isLocal: true, ready: true, serving: true, terminating: false},
    95  			},
    96  			makeServicePortName("ns2", "ep2", "p23", v1.ProtocolTCP): []Endpoint{
    97  				&BaseEndpointInfo{ip: "2.2.2.3", port: 23, endpoint: "2.2.2.3:23", isLocal: true, ready: true, serving: true, terminating: false},
    98  			},
    99  			makeServicePortName("ns4", "ep4", "p44", v1.ProtocolTCP): []Endpoint{
   100  				&BaseEndpointInfo{ip: "4.4.4.4", port: 44, endpoint: "4.4.4.4:44", isLocal: true, ready: true, serving: true, terminating: false},
   101  				&BaseEndpointInfo{ip: "4.4.4.5", port: 44, endpoint: "4.4.4.5:44", isLocal: false, ready: true, serving: true, terminating: false},
   102  			},
   103  			makeServicePortName("ns4", "ep4", "p45", v1.ProtocolTCP): []Endpoint{
   104  				&BaseEndpointInfo{ip: "4.4.4.6", port: 45, endpoint: "4.4.4.6:45", isLocal: true, ready: true, serving: true, terminating: false},
   105  			},
   106  		},
   107  		expected: map[types.NamespacedName]sets.Set[string]{
   108  			{Namespace: "ns2", Name: "ep2"}: sets.New[string]("2.2.2.2", "2.2.2.22", "2.2.2.3"),
   109  			{Namespace: "ns4", Name: "ep4"}: sets.New[string]("4.4.4.4", "4.4.4.6"),
   110  		},
   111  	}, {
   112  		// Case[5]: named local and non-local ports for different IPs, some not ready.
   113  		endpointsMap: EndpointsMap{
   114  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolTCP): []Endpoint{
   115  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   116  			},
   117  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolTCP): []Endpoint{
   118  				&BaseEndpointInfo{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: true, serving: true, terminating: false},
   119  				&BaseEndpointInfo{ip: "2.2.2.22", port: 22, endpoint: "2.2.2.22:22", isLocal: true, ready: true, serving: true, terminating: false},
   120  			},
   121  			makeServicePortName("ns2", "ep2", "p23", v1.ProtocolTCP): []Endpoint{
   122  				&BaseEndpointInfo{ip: "2.2.2.3", port: 23, endpoint: "2.2.2.3:23", isLocal: true, ready: false, serving: true, terminating: true},
   123  			},
   124  			makeServicePortName("ns4", "ep4", "p44", v1.ProtocolTCP): []Endpoint{
   125  				&BaseEndpointInfo{ip: "4.4.4.4", port: 44, endpoint: "4.4.4.4:44", isLocal: true, ready: true, serving: true, terminating: false},
   126  				&BaseEndpointInfo{ip: "4.4.4.5", port: 44, endpoint: "4.4.4.5:44", isLocal: false, ready: true, serving: true, terminating: false},
   127  			},
   128  			makeServicePortName("ns4", "ep4", "p45", v1.ProtocolTCP): []Endpoint{
   129  				&BaseEndpointInfo{ip: "4.4.4.6", port: 45, endpoint: "4.4.4.6:45", isLocal: true, ready: true, serving: true, terminating: false},
   130  			},
   131  		},
   132  		expected: map[types.NamespacedName]sets.Set[string]{
   133  			{Namespace: "ns2", Name: "ep2"}: sets.New[string]("2.2.2.2", "2.2.2.22"),
   134  			{Namespace: "ns4", Name: "ep4"}: sets.New[string]("4.4.4.4", "4.4.4.6"),
   135  		},
   136  	}, {
   137  		// Case[6]: all endpoints are terminating,, so getLocalReadyEndpointIPs should return 0 ready endpoints
   138  		endpointsMap: EndpointsMap{
   139  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolTCP): []Endpoint{
   140  				&BaseEndpointInfo{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: false, serving: true, terminating: true},
   141  			},
   142  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolTCP): []Endpoint{
   143  				&BaseEndpointInfo{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: false, serving: true, terminating: true},
   144  				&BaseEndpointInfo{ip: "2.2.2.22", port: 22, endpoint: "2.2.2.22:22", isLocal: true, ready: false, serving: true, terminating: true},
   145  			},
   146  			makeServicePortName("ns2", "ep2", "p23", v1.ProtocolTCP): []Endpoint{
   147  				&BaseEndpointInfo{ip: "2.2.2.3", port: 23, endpoint: "2.2.2.3:23", isLocal: true, ready: false, serving: true, terminating: true},
   148  			},
   149  			makeServicePortName("ns4", "ep4", "p44", v1.ProtocolTCP): []Endpoint{
   150  				&BaseEndpointInfo{ip: "4.4.4.4", port: 44, endpoint: "4.4.4.4:44", isLocal: true, ready: false, serving: true, terminating: true},
   151  				&BaseEndpointInfo{ip: "4.4.4.5", port: 44, endpoint: "4.4.4.5:44", isLocal: false, ready: false, serving: true, terminating: true},
   152  			},
   153  			makeServicePortName("ns4", "ep4", "p45", v1.ProtocolTCP): []Endpoint{
   154  				&BaseEndpointInfo{ip: "4.4.4.6", port: 45, endpoint: "4.4.4.6:45", isLocal: true, ready: false, serving: true, terminating: true},
   155  			},
   156  		},
   157  		expected: make(map[types.NamespacedName]sets.Set[string], 0),
   158  	}}
   159  
   160  	for tci, tc := range testCases {
   161  		// outputs
   162  		localIPs := tc.endpointsMap.getLocalReadyEndpointIPs()
   163  
   164  		if !reflect.DeepEqual(localIPs, tc.expected) {
   165  			t.Errorf("[%d] expected %#v, got %#v", tci, tc.expected, localIPs)
   166  		}
   167  	}
   168  }
   169  
   170  func makeTestEndpointSlice(namespace, name string, slice int, epsFunc func(*discovery.EndpointSlice)) *discovery.EndpointSlice {
   171  	eps := &discovery.EndpointSlice{
   172  		ObjectMeta: metav1.ObjectMeta{
   173  			Name:        fmt.Sprintf("%s-%d", name, slice),
   174  			Namespace:   namespace,
   175  			Annotations: map[string]string{},
   176  			Labels: map[string]string{
   177  				discovery.LabelServiceName: name,
   178  			},
   179  		},
   180  		AddressType: discovery.AddressTypeIPv4,
   181  	}
   182  	epsFunc(eps)
   183  	return eps
   184  }
   185  
   186  func TestUpdateEndpointsMap(t *testing.T) {
   187  	emptyEndpoint := func(eps *discovery.EndpointSlice) {
   188  		eps.Endpoints = []discovery.Endpoint{}
   189  	}
   190  	unnamedPort := func(eps *discovery.EndpointSlice) {
   191  		eps.Endpoints = []discovery.Endpoint{{
   192  			Addresses: []string{"1.1.1.1"},
   193  		}}
   194  		eps.Ports = []discovery.EndpointPort{{
   195  			Name:     ptr.To(""),
   196  			Port:     ptr.To[int32](11),
   197  			Protocol: ptr.To(v1.ProtocolUDP),
   198  		}}
   199  	}
   200  	unnamedPortReady := func(eps *discovery.EndpointSlice) {
   201  		eps.Endpoints = []discovery.Endpoint{{
   202  			Addresses: []string{"1.1.1.1"},
   203  			Conditions: discovery.EndpointConditions{
   204  				Ready:       ptr.To(true),
   205  				Serving:     ptr.To(true),
   206  				Terminating: ptr.To(false),
   207  			},
   208  		}}
   209  		eps.Ports = []discovery.EndpointPort{{
   210  			Name:     ptr.To(""),
   211  			Port:     ptr.To[int32](11),
   212  			Protocol: ptr.To(v1.ProtocolUDP),
   213  		}}
   214  	}
   215  	unnamedPortTerminating := func(eps *discovery.EndpointSlice) {
   216  		eps.Endpoints = []discovery.Endpoint{{
   217  			Addresses: []string{"1.1.1.1"},
   218  			Conditions: discovery.EndpointConditions{
   219  				Ready:       ptr.To(false),
   220  				Serving:     ptr.To(true),
   221  				Terminating: ptr.To(true),
   222  			},
   223  		}}
   224  		eps.Ports = []discovery.EndpointPort{{
   225  			Name:     ptr.To(""),
   226  			Port:     ptr.To[int32](11),
   227  			Protocol: ptr.To(v1.ProtocolUDP),
   228  		}}
   229  	}
   230  	unnamedPortLocal := func(eps *discovery.EndpointSlice) {
   231  		eps.Endpoints = []discovery.Endpoint{{
   232  			Addresses: []string{"1.1.1.1"},
   233  			NodeName:  ptr.To(testHostname),
   234  		}}
   235  		eps.Ports = []discovery.EndpointPort{{
   236  			Name:     ptr.To(""),
   237  			Port:     ptr.To[int32](11),
   238  			Protocol: ptr.To(v1.ProtocolUDP),
   239  		}}
   240  	}
   241  	namedPortLocal := func(eps *discovery.EndpointSlice) {
   242  		eps.Endpoints = []discovery.Endpoint{{
   243  			Addresses: []string{"1.1.1.1"},
   244  			NodeName:  ptr.To(testHostname),
   245  		}}
   246  		eps.Ports = []discovery.EndpointPort{{
   247  			Name:     ptr.To("p11"),
   248  			Port:     ptr.To[int32](11),
   249  			Protocol: ptr.To(v1.ProtocolUDP),
   250  		}}
   251  	}
   252  	namedPort := func(eps *discovery.EndpointSlice) {
   253  		eps.Endpoints = []discovery.Endpoint{{
   254  			Addresses: []string{"1.1.1.1"},
   255  		}}
   256  		eps.Ports = []discovery.EndpointPort{{
   257  			Name:     ptr.To("p11"),
   258  			Port:     ptr.To[int32](11),
   259  			Protocol: ptr.To(v1.ProtocolUDP),
   260  		}}
   261  	}
   262  	namedPortRenamed := func(eps *discovery.EndpointSlice) {
   263  		eps.Endpoints = []discovery.Endpoint{{
   264  			Addresses: []string{"1.1.1.1"},
   265  		}}
   266  		eps.Ports = []discovery.EndpointPort{{
   267  			Name:     ptr.To("p11-2"),
   268  			Port:     ptr.To[int32](11),
   269  			Protocol: ptr.To(v1.ProtocolUDP),
   270  		}}
   271  	}
   272  	namedPortRenumbered := func(eps *discovery.EndpointSlice) {
   273  		eps.Endpoints = []discovery.Endpoint{{
   274  			Addresses: []string{"1.1.1.1"},
   275  		}}
   276  		eps.Ports = []discovery.EndpointPort{{
   277  			Name:     ptr.To("p11"),
   278  			Port:     ptr.To[int32](22),
   279  			Protocol: ptr.To(v1.ProtocolUDP),
   280  		}}
   281  	}
   282  	namedPortsLocalNoLocal := func(eps *discovery.EndpointSlice) {
   283  		eps.Endpoints = []discovery.Endpoint{{
   284  			Addresses: []string{"1.1.1.1"},
   285  		}, {
   286  			Addresses: []string{"1.1.1.2"},
   287  			NodeName:  ptr.To(testHostname),
   288  		}}
   289  		eps.Ports = []discovery.EndpointPort{{
   290  			Name:     ptr.To("p11"),
   291  			Port:     ptr.To[int32](11),
   292  			Protocol: ptr.To(v1.ProtocolUDP),
   293  		}, {
   294  			Name:     ptr.To("p12"),
   295  			Port:     ptr.To[int32](12),
   296  			Protocol: ptr.To(v1.ProtocolUDP),
   297  		}}
   298  	}
   299  	multipleSubsets_s1 := func(eps *discovery.EndpointSlice) {
   300  		eps.Endpoints = []discovery.Endpoint{{
   301  			Addresses: []string{"1.1.1.1"},
   302  		}}
   303  		eps.Ports = []discovery.EndpointPort{{
   304  			Name:     ptr.To("p11"),
   305  			Port:     ptr.To[int32](11),
   306  			Protocol: ptr.To(v1.ProtocolUDP),
   307  		}}
   308  	}
   309  	multipleSubsets_s2 := func(eps *discovery.EndpointSlice) {
   310  		eps.Endpoints = []discovery.Endpoint{{
   311  			Addresses: []string{"1.1.1.2"},
   312  		}}
   313  		eps.Ports = []discovery.EndpointPort{{
   314  			Name:     ptr.To("p12"),
   315  			Port:     ptr.To[int32](12),
   316  			Protocol: ptr.To(v1.ProtocolUDP),
   317  		}}
   318  	}
   319  	multipleSubsetsWithLocal_s1 := func(eps *discovery.EndpointSlice) {
   320  		eps.Endpoints = []discovery.Endpoint{{
   321  			Addresses: []string{"1.1.1.1"},
   322  		}}
   323  		eps.Ports = []discovery.EndpointPort{{
   324  			Name:     ptr.To("p11"),
   325  			Port:     ptr.To[int32](11),
   326  			Protocol: ptr.To(v1.ProtocolUDP),
   327  		}}
   328  	}
   329  	multipleSubsetsWithLocal_s2 := func(eps *discovery.EndpointSlice) {
   330  		eps.Endpoints = []discovery.Endpoint{{
   331  			Addresses: []string{"1.1.1.2"},
   332  			NodeName:  ptr.To(testHostname),
   333  		}}
   334  		eps.Ports = []discovery.EndpointPort{{
   335  			Name:     ptr.To("p12"),
   336  			Port:     ptr.To[int32](12),
   337  			Protocol: ptr.To(v1.ProtocolUDP),
   338  		}}
   339  	}
   340  	multipleSubsetsMultiplePortsLocal_s1 := func(eps *discovery.EndpointSlice) {
   341  		eps.Endpoints = []discovery.Endpoint{{
   342  			Addresses: []string{"1.1.1.1"},
   343  			NodeName:  ptr.To(testHostname),
   344  		}}
   345  		eps.Ports = []discovery.EndpointPort{{
   346  			Name:     ptr.To("p11"),
   347  			Port:     ptr.To[int32](11),
   348  			Protocol: ptr.To(v1.ProtocolUDP),
   349  		}, {
   350  			Name:     ptr.To("p12"),
   351  			Port:     ptr.To[int32](12),
   352  			Protocol: ptr.To(v1.ProtocolUDP),
   353  		}}
   354  	}
   355  	multipleSubsetsMultiplePortsLocal_s2 := func(eps *discovery.EndpointSlice) {
   356  		eps.Endpoints = []discovery.Endpoint{{
   357  			Addresses: []string{"1.1.1.3"},
   358  		}}
   359  		eps.Ports = []discovery.EndpointPort{{
   360  			Name:     ptr.To("p13"),
   361  			Port:     ptr.To[int32](13),
   362  			Protocol: ptr.To(v1.ProtocolUDP),
   363  		}}
   364  	}
   365  	multipleSubsetsIPsPorts1_s1 := func(eps *discovery.EndpointSlice) {
   366  		eps.Endpoints = []discovery.Endpoint{{
   367  			Addresses: []string{"1.1.1.1"},
   368  		}, {
   369  			Addresses: []string{"1.1.1.2"},
   370  			NodeName:  ptr.To(testHostname),
   371  		}}
   372  		eps.Ports = []discovery.EndpointPort{{
   373  			Name:     ptr.To("p11"),
   374  			Port:     ptr.To[int32](11),
   375  			Protocol: ptr.To(v1.ProtocolUDP),
   376  		}, {
   377  			Name:     ptr.To("p12"),
   378  			Port:     ptr.To[int32](12),
   379  			Protocol: ptr.To(v1.ProtocolUDP),
   380  		}}
   381  	}
   382  	multipleSubsetsIPsPorts1_s2 := func(eps *discovery.EndpointSlice) {
   383  		eps.Endpoints = []discovery.Endpoint{{
   384  			Addresses: []string{"1.1.1.3"},
   385  		}, {
   386  			Addresses: []string{"1.1.1.4"},
   387  			NodeName:  ptr.To(testHostname),
   388  		}}
   389  		eps.Ports = []discovery.EndpointPort{{
   390  			Name:     ptr.To("p13"),
   391  			Port:     ptr.To[int32](13),
   392  			Protocol: ptr.To(v1.ProtocolUDP),
   393  		}, {
   394  			Name:     ptr.To("p14"),
   395  			Port:     ptr.To[int32](14),
   396  			Protocol: ptr.To(v1.ProtocolUDP),
   397  		}}
   398  	}
   399  	multipleSubsetsIPsPorts2 := func(eps *discovery.EndpointSlice) {
   400  		eps.Endpoints = []discovery.Endpoint{{
   401  			Addresses: []string{"2.2.2.1"},
   402  		}, {
   403  			Addresses: []string{"2.2.2.2"},
   404  			NodeName:  ptr.To(testHostname),
   405  		}}
   406  		eps.Ports = []discovery.EndpointPort{{
   407  			Name:     ptr.To("p21"),
   408  			Port:     ptr.To[int32](21),
   409  			Protocol: ptr.To(v1.ProtocolUDP),
   410  		}, {
   411  			Name:     ptr.To("p22"),
   412  			Port:     ptr.To[int32](22),
   413  			Protocol: ptr.To(v1.ProtocolUDP),
   414  		}}
   415  	}
   416  	complexBefore1 := func(eps *discovery.EndpointSlice) {
   417  		eps.Endpoints = []discovery.Endpoint{{
   418  			Addresses: []string{"1.1.1.1"},
   419  		}}
   420  		eps.Ports = []discovery.EndpointPort{{
   421  			Name:     ptr.To("p11"),
   422  			Port:     ptr.To[int32](11),
   423  			Protocol: ptr.To(v1.ProtocolUDP),
   424  		}}
   425  	}
   426  	complexBefore2_s1 := func(eps *discovery.EndpointSlice) {
   427  		eps.Endpoints = []discovery.Endpoint{{
   428  			Addresses: []string{"2.2.2.2"},
   429  			NodeName:  ptr.To(testHostname),
   430  		}, {
   431  			Addresses: []string{"2.2.2.22"},
   432  			NodeName:  ptr.To(testHostname),
   433  		}}
   434  		eps.Ports = []discovery.EndpointPort{{
   435  			Name:     ptr.To("p22"),
   436  			Port:     ptr.To[int32](22),
   437  			Protocol: ptr.To(v1.ProtocolUDP),
   438  		}}
   439  	}
   440  	complexBefore2_s2 := func(eps *discovery.EndpointSlice) {
   441  		eps.Endpoints = []discovery.Endpoint{{
   442  			Addresses: []string{"2.2.2.3"},
   443  			NodeName:  ptr.To(testHostname),
   444  		}}
   445  		eps.Ports = []discovery.EndpointPort{{
   446  			Name:     ptr.To("p23"),
   447  			Port:     ptr.To[int32](23),
   448  			Protocol: ptr.To(v1.ProtocolUDP),
   449  		}}
   450  	}
   451  	complexBefore4_s1 := func(eps *discovery.EndpointSlice) {
   452  		eps.Endpoints = []discovery.Endpoint{{
   453  			Addresses: []string{"4.4.4.4"},
   454  			NodeName:  ptr.To(testHostname),
   455  		}, {
   456  			Addresses: []string{"4.4.4.5"},
   457  			NodeName:  ptr.To(testHostname),
   458  		}}
   459  		eps.Ports = []discovery.EndpointPort{{
   460  			Name:     ptr.To("p44"),
   461  			Port:     ptr.To[int32](44),
   462  			Protocol: ptr.To(v1.ProtocolUDP),
   463  		}}
   464  	}
   465  	complexBefore4_s2 := func(eps *discovery.EndpointSlice) {
   466  		eps.Endpoints = []discovery.Endpoint{{
   467  			Addresses: []string{"4.4.4.6"},
   468  			NodeName:  ptr.To(testHostname),
   469  		}}
   470  		eps.Ports = []discovery.EndpointPort{{
   471  			Name:     ptr.To("p45"),
   472  			Port:     ptr.To[int32](45),
   473  			Protocol: ptr.To(v1.ProtocolUDP),
   474  		}}
   475  	}
   476  	complexAfter1_s1 := func(eps *discovery.EndpointSlice) {
   477  		eps.Endpoints = []discovery.Endpoint{{
   478  			Addresses: []string{"1.1.1.1"},
   479  		}, {
   480  			Addresses: []string{"1.1.1.11"},
   481  		}}
   482  		eps.Ports = []discovery.EndpointPort{{
   483  			Name:     ptr.To("p11"),
   484  			Port:     ptr.To[int32](11),
   485  			Protocol: ptr.To(v1.ProtocolUDP),
   486  		}}
   487  	}
   488  	complexAfter1_s2 := func(eps *discovery.EndpointSlice) {
   489  		eps.Endpoints = []discovery.Endpoint{{
   490  			Addresses: []string{"1.1.1.2"},
   491  		}}
   492  		eps.Ports = []discovery.EndpointPort{{
   493  			Name:     ptr.To("p12"),
   494  			Port:     ptr.To[int32](12),
   495  			Protocol: ptr.To(v1.ProtocolUDP),
   496  		}, {
   497  			Name:     ptr.To("p122"),
   498  			Port:     ptr.To[int32](122),
   499  			Protocol: ptr.To(v1.ProtocolUDP),
   500  		}}
   501  	}
   502  	complexAfter3 := func(eps *discovery.EndpointSlice) {
   503  		eps.Endpoints = []discovery.Endpoint{{
   504  			Addresses: []string{"3.3.3.3"},
   505  		}}
   506  		eps.Ports = []discovery.EndpointPort{{
   507  			Name:     ptr.To("p33"),
   508  			Port:     ptr.To[int32](33),
   509  			Protocol: ptr.To(v1.ProtocolUDP),
   510  		}}
   511  	}
   512  	complexAfter4 := func(eps *discovery.EndpointSlice) {
   513  		eps.Endpoints = []discovery.Endpoint{{
   514  			Addresses: []string{"4.4.4.4"},
   515  			NodeName:  ptr.To(testHostname),
   516  		}}
   517  		eps.Ports = []discovery.EndpointPort{{
   518  			Name:     ptr.To("p44"),
   519  			Port:     ptr.To[int32](44),
   520  			Protocol: ptr.To(v1.ProtocolUDP),
   521  		}}
   522  	}
   523  
   524  	testCases := []struct {
   525  		// previousEndpointSlices and currentEndpointSlices are used to call appropriate
   526  		// handlers OnEndpointSlice* (based on whether corresponding values are nil
   527  		// or non-nil) and must be of equal length.
   528  		name                           string
   529  		previousEndpointSlices         []*discovery.EndpointSlice
   530  		currentEndpointSlices          []*discovery.EndpointSlice
   531  		previousEndpointsMap           map[ServicePortName][]*BaseEndpointInfo
   532  		expectedResult                 map[ServicePortName][]*BaseEndpointInfo
   533  		expectedDeletedUDPEndpoints    []ServiceEndpoint
   534  		expectedNewlyActiveUDPServices map[ServicePortName]bool
   535  		expectedLocalEndpoints         map[types.NamespacedName]int
   536  		expectedChangedEndpoints       sets.Set[types.NamespacedName]
   537  	}{{
   538  		name:                           "empty",
   539  		previousEndpointsMap:           map[ServicePortName][]*BaseEndpointInfo{},
   540  		expectedResult:                 map[ServicePortName][]*BaseEndpointInfo{},
   541  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   542  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   543  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   544  		expectedChangedEndpoints:       sets.New[types.NamespacedName](),
   545  	}, {
   546  		name: "no change, unnamed port",
   547  		previousEndpointSlices: []*discovery.EndpointSlice{
   548  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPort),
   549  		},
   550  		currentEndpointSlices: []*discovery.EndpointSlice{
   551  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPort),
   552  		},
   553  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   554  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
   555  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   556  			},
   557  		},
   558  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   559  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
   560  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   561  			},
   562  		},
   563  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   564  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   565  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   566  		expectedChangedEndpoints:       sets.New[types.NamespacedName](),
   567  	}, {
   568  		name: "no change, named port, local",
   569  		previousEndpointSlices: []*discovery.EndpointSlice{
   570  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortLocal),
   571  		},
   572  		currentEndpointSlices: []*discovery.EndpointSlice{
   573  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortLocal),
   574  		},
   575  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   576  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   577  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   578  			},
   579  		},
   580  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   581  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   582  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   583  			},
   584  		},
   585  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   586  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   587  		expectedLocalEndpoints: map[types.NamespacedName]int{
   588  			makeNSN("ns1", "ep1"): 1,
   589  		},
   590  		expectedChangedEndpoints: sets.New[types.NamespacedName](),
   591  	}, {
   592  		name: "no change, multiple slices",
   593  		previousEndpointSlices: []*discovery.EndpointSlice{
   594  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsets_s1),
   595  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsets_s2),
   596  		},
   597  		currentEndpointSlices: []*discovery.EndpointSlice{
   598  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsets_s1),
   599  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsets_s2),
   600  		},
   601  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   602  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   603  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   604  			},
   605  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   606  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: false, ready: true, serving: true, terminating: false},
   607  			},
   608  		},
   609  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   610  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   611  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   612  			},
   613  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   614  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: false, ready: true, serving: true, terminating: false},
   615  			},
   616  		},
   617  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   618  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   619  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   620  		expectedChangedEndpoints:       sets.New[types.NamespacedName](),
   621  	}, {
   622  		name: "no change, multiple slices, multiple ports, local",
   623  		previousEndpointSlices: []*discovery.EndpointSlice{
   624  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsetsMultiplePortsLocal_s1),
   625  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsetsMultiplePortsLocal_s2),
   626  		},
   627  		currentEndpointSlices: []*discovery.EndpointSlice{
   628  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsetsMultiplePortsLocal_s1),
   629  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsetsMultiplePortsLocal_s2),
   630  		},
   631  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   632  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   633  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   634  			},
   635  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   636  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: true, ready: true, serving: true, terminating: false},
   637  			},
   638  			makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
   639  				{ip: "1.1.1.3", port: 13, endpoint: "1.1.1.3:13", isLocal: false, ready: true, serving: true, terminating: false},
   640  			},
   641  		},
   642  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   643  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   644  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   645  			},
   646  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   647  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: true, ready: true, serving: true, terminating: false},
   648  			},
   649  			makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
   650  				{ip: "1.1.1.3", port: 13, endpoint: "1.1.1.3:13", isLocal: false, ready: true, serving: true, terminating: false},
   651  			},
   652  		},
   653  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   654  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   655  		expectedLocalEndpoints: map[types.NamespacedName]int{
   656  			makeNSN("ns1", "ep1"): 1,
   657  		},
   658  		expectedChangedEndpoints: sets.New[types.NamespacedName](),
   659  	}, {
   660  		name: "no change, multiple services, slices, IPs, and ports",
   661  		previousEndpointSlices: []*discovery.EndpointSlice{
   662  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsetsIPsPorts1_s1),
   663  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsetsIPsPorts1_s2),
   664  			makeTestEndpointSlice("ns2", "ep2", 1, multipleSubsetsIPsPorts2),
   665  		},
   666  		currentEndpointSlices: []*discovery.EndpointSlice{
   667  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsetsIPsPorts1_s1),
   668  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsetsIPsPorts1_s2),
   669  			makeTestEndpointSlice("ns2", "ep2", 1, multipleSubsetsIPsPorts2),
   670  		},
   671  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   672  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   673  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   674  				{ip: "1.1.1.2", port: 11, endpoint: "1.1.1.2:11", isLocal: true, ready: true, serving: true, terminating: false},
   675  			},
   676  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   677  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: false, ready: true, serving: true, terminating: false},
   678  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
   679  			},
   680  			makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
   681  				{ip: "1.1.1.3", port: 13, endpoint: "1.1.1.3:13", isLocal: false, ready: true, serving: true, terminating: false},
   682  				{ip: "1.1.1.4", port: 13, endpoint: "1.1.1.4:13", isLocal: true, ready: true, serving: true, terminating: false},
   683  			},
   684  			makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): {
   685  				{ip: "1.1.1.3", port: 14, endpoint: "1.1.1.3:14", isLocal: false, ready: true, serving: true, terminating: false},
   686  				{ip: "1.1.1.4", port: 14, endpoint: "1.1.1.4:14", isLocal: true, ready: true, serving: true, terminating: false},
   687  			},
   688  			makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): {
   689  				{ip: "2.2.2.1", port: 21, endpoint: "2.2.2.1:21", isLocal: false, ready: true, serving: true, terminating: false},
   690  				{ip: "2.2.2.2", port: 21, endpoint: "2.2.2.2:21", isLocal: true, ready: true, serving: true, terminating: false},
   691  			},
   692  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
   693  				{ip: "2.2.2.1", port: 22, endpoint: "2.2.2.1:22", isLocal: false, ready: true, serving: true, terminating: false},
   694  				{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: true, serving: true, terminating: false},
   695  			},
   696  		},
   697  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   698  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   699  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   700  				{ip: "1.1.1.2", port: 11, endpoint: "1.1.1.2:11", isLocal: true, ready: true, serving: true, terminating: false},
   701  			},
   702  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   703  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: false, ready: true, serving: true, terminating: false},
   704  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
   705  			},
   706  			makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
   707  				{ip: "1.1.1.3", port: 13, endpoint: "1.1.1.3:13", isLocal: false, ready: true, serving: true, terminating: false},
   708  				{ip: "1.1.1.4", port: 13, endpoint: "1.1.1.4:13", isLocal: true, ready: true, serving: true, terminating: false},
   709  			},
   710  			makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): {
   711  				{ip: "1.1.1.3", port: 14, endpoint: "1.1.1.3:14", isLocal: false, ready: true, serving: true, terminating: false},
   712  				{ip: "1.1.1.4", port: 14, endpoint: "1.1.1.4:14", isLocal: true, ready: true, serving: true, terminating: false},
   713  			},
   714  			makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): {
   715  				{ip: "2.2.2.1", port: 21, endpoint: "2.2.2.1:21", isLocal: false, ready: true, serving: true, terminating: false},
   716  				{ip: "2.2.2.2", port: 21, endpoint: "2.2.2.2:21", isLocal: true, ready: true, serving: true, terminating: false},
   717  			},
   718  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
   719  				{ip: "2.2.2.1", port: 22, endpoint: "2.2.2.1:22", isLocal: false, ready: true, serving: true, terminating: false},
   720  				{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: true, serving: true, terminating: false},
   721  			},
   722  		},
   723  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
   724  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   725  		expectedLocalEndpoints: map[types.NamespacedName]int{
   726  			makeNSN("ns1", "ep1"): 2,
   727  			makeNSN("ns2", "ep2"): 1,
   728  		},
   729  		expectedChangedEndpoints: sets.New[types.NamespacedName](),
   730  	}, {
   731  		name: "add an EndpointSlice",
   732  		previousEndpointSlices: []*discovery.EndpointSlice{
   733  			nil,
   734  		},
   735  		currentEndpointSlices: []*discovery.EndpointSlice{
   736  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPortLocal),
   737  		},
   738  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{},
   739  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   740  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
   741  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   742  			},
   743  		},
   744  		expectedDeletedUDPEndpoints: []ServiceEndpoint{},
   745  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
   746  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): true,
   747  		},
   748  		expectedLocalEndpoints: map[types.NamespacedName]int{
   749  			makeNSN("ns1", "ep1"): 1,
   750  		},
   751  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1")),
   752  	}, {
   753  		name: "remove an EndpointSlice",
   754  		previousEndpointSlices: []*discovery.EndpointSlice{
   755  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPortLocal),
   756  		},
   757  		currentEndpointSlices: []*discovery.EndpointSlice{
   758  			nil,
   759  		},
   760  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   761  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
   762  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: true, ready: true, serving: true, terminating: false},
   763  			},
   764  		},
   765  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{},
   766  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
   767  			Endpoint:        "1.1.1.1:11",
   768  			ServicePortName: makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP),
   769  		}},
   770  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   771  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   772  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
   773  	}, {
   774  		name: "add an IP and port",
   775  		previousEndpointSlices: []*discovery.EndpointSlice{
   776  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   777  		},
   778  		currentEndpointSlices: []*discovery.EndpointSlice{
   779  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortsLocalNoLocal),
   780  		},
   781  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   782  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   783  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   784  			},
   785  		},
   786  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   787  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   788  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   789  				{ip: "1.1.1.2", port: 11, endpoint: "1.1.1.2:11", isLocal: true, ready: true, serving: true, terminating: false},
   790  			},
   791  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   792  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: false, ready: true, serving: true, terminating: false},
   793  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
   794  			},
   795  		},
   796  		expectedDeletedUDPEndpoints: []ServiceEndpoint{},
   797  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
   798  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): true,
   799  		},
   800  		expectedLocalEndpoints: map[types.NamespacedName]int{
   801  			makeNSN("ns1", "ep1"): 1,
   802  		},
   803  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1")),
   804  	}, {
   805  		name: "remove an IP and port",
   806  		previousEndpointSlices: []*discovery.EndpointSlice{
   807  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortsLocalNoLocal),
   808  		},
   809  		currentEndpointSlices: []*discovery.EndpointSlice{
   810  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   811  		},
   812  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   813  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   814  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   815  				{ip: "1.1.1.2", port: 11, endpoint: "1.1.1.2:11", isLocal: true, ready: true, serving: true, terminating: false},
   816  			},
   817  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   818  				{ip: "1.1.1.1", port: 12, endpoint: "1.1.1.1:12", isLocal: false, ready: true, serving: true, terminating: false},
   819  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
   820  			},
   821  		},
   822  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   823  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   824  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   825  			},
   826  		},
   827  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
   828  			Endpoint:        "1.1.1.2:11",
   829  			ServicePortName: makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP),
   830  		}, {
   831  			Endpoint:        "1.1.1.1:12",
   832  			ServicePortName: makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP),
   833  		}, {
   834  			Endpoint:        "1.1.1.2:12",
   835  			ServicePortName: makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP),
   836  		}},
   837  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   838  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   839  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
   840  	}, {
   841  		name: "add a slice to an endpoint",
   842  		previousEndpointSlices: []*discovery.EndpointSlice{
   843  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   844  			nil,
   845  		},
   846  		currentEndpointSlices: []*discovery.EndpointSlice{
   847  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsetsWithLocal_s1),
   848  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsetsWithLocal_s2),
   849  		},
   850  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   851  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   852  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   853  			},
   854  		},
   855  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   856  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   857  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   858  			},
   859  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   860  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: true, ready: true, serving: true, terminating: false},
   861  			},
   862  		},
   863  		expectedDeletedUDPEndpoints: []ServiceEndpoint{},
   864  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
   865  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): true,
   866  		},
   867  		expectedLocalEndpoints: map[types.NamespacedName]int{
   868  			makeNSN("ns1", "ep1"): 1,
   869  		},
   870  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1")),
   871  	}, {
   872  		name: "remove a slice from an endpoint",
   873  		previousEndpointSlices: []*discovery.EndpointSlice{
   874  			makeTestEndpointSlice("ns1", "ep1", 1, multipleSubsets_s1),
   875  			makeTestEndpointSlice("ns1", "ep1", 2, multipleSubsets_s2),
   876  		},
   877  		currentEndpointSlices: []*discovery.EndpointSlice{
   878  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   879  			nil,
   880  		},
   881  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   882  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   883  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   884  			},
   885  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
   886  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: false, ready: true, serving: true, terminating: false},
   887  			},
   888  		},
   889  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   890  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   891  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   892  			},
   893  		},
   894  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
   895  			Endpoint:        "1.1.1.2:12",
   896  			ServicePortName: makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP),
   897  		}},
   898  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   899  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   900  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
   901  	}, {
   902  		name: "rename a port",
   903  		previousEndpointSlices: []*discovery.EndpointSlice{
   904  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   905  		},
   906  		currentEndpointSlices: []*discovery.EndpointSlice{
   907  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortRenamed),
   908  		},
   909  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   910  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   911  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   912  			},
   913  		},
   914  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   915  			makeServicePortName("ns1", "ep1", "p11-2", v1.ProtocolUDP): {
   916  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   917  			},
   918  		},
   919  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
   920  			Endpoint:        "1.1.1.1:11",
   921  			ServicePortName: makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP),
   922  		}},
   923  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
   924  			makeServicePortName("ns1", "ep1", "p11-2", v1.ProtocolUDP): true,
   925  		},
   926  		expectedLocalEndpoints:   map[types.NamespacedName]int{},
   927  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1")),
   928  	}, {
   929  		name: "renumber a port",
   930  		previousEndpointSlices: []*discovery.EndpointSlice{
   931  			makeTestEndpointSlice("ns1", "ep1", 1, namedPort),
   932  		},
   933  		currentEndpointSlices: []*discovery.EndpointSlice{
   934  			makeTestEndpointSlice("ns1", "ep1", 1, namedPortRenumbered),
   935  		},
   936  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   937  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   938  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   939  			},
   940  		},
   941  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
   942  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   943  				{ip: "1.1.1.1", port: 22, endpoint: "1.1.1.1:22", isLocal: false, ready: true, serving: true, terminating: false},
   944  			},
   945  		},
   946  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
   947  			Endpoint:        "1.1.1.1:11",
   948  			ServicePortName: makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP),
   949  		}},
   950  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
   951  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
   952  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
   953  	}, {
   954  		name: "complex add and remove",
   955  		previousEndpointSlices: []*discovery.EndpointSlice{
   956  			makeTestEndpointSlice("ns1", "ep1", 1, complexBefore1),
   957  			nil,
   958  
   959  			makeTestEndpointSlice("ns2", "ep2", 1, complexBefore2_s1),
   960  			makeTestEndpointSlice("ns2", "ep2", 2, complexBefore2_s2),
   961  
   962  			nil,
   963  			nil,
   964  
   965  			makeTestEndpointSlice("ns4", "ep4", 1, complexBefore4_s1),
   966  			makeTestEndpointSlice("ns4", "ep4", 2, complexBefore4_s2),
   967  		},
   968  		currentEndpointSlices: []*discovery.EndpointSlice{
   969  			makeTestEndpointSlice("ns1", "ep1", 1, complexAfter1_s1),
   970  			makeTestEndpointSlice("ns1", "ep1", 2, complexAfter1_s2),
   971  
   972  			nil,
   973  			nil,
   974  
   975  			makeTestEndpointSlice("ns3", "ep3", 1, complexAfter3),
   976  			nil,
   977  
   978  			makeTestEndpointSlice("ns4", "ep4", 1, complexAfter4),
   979  			nil,
   980  		},
   981  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
   982  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
   983  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
   984  			},
   985  			makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
   986  				{ip: "2.2.2.22", port: 22, endpoint: "2.2.2.22:22", isLocal: true, ready: true, serving: true, terminating: false},
   987  				{ip: "2.2.2.2", port: 22, endpoint: "2.2.2.2:22", isLocal: true, ready: true, serving: true, terminating: false},
   988  			},
   989  			makeServicePortName("ns2", "ep2", "p23", v1.ProtocolUDP): {
   990  				{ip: "2.2.2.3", port: 23, endpoint: "2.2.2.3:23", isLocal: true, ready: true, serving: true, terminating: false},
   991  			},
   992  			makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): {
   993  				{ip: "4.4.4.4", port: 44, endpoint: "4.4.4.4:44", isLocal: true, ready: true, serving: true, terminating: false},
   994  				{ip: "4.4.4.5", port: 44, endpoint: "4.4.4.5:44", isLocal: true, ready: true, serving: true, terminating: false},
   995  			},
   996  			makeServicePortName("ns4", "ep4", "p45", v1.ProtocolUDP): {
   997  				{ip: "4.4.4.6", port: 45, endpoint: "4.4.4.6:45", isLocal: true, ready: true, serving: true, terminating: false},
   998  			},
   999  		},
  1000  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
  1001  			makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
  1002  				{ip: "1.1.1.11", port: 11, endpoint: "1.1.1.11:11", isLocal: false, ready: true, serving: true, terminating: false},
  1003  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
  1004  			},
  1005  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
  1006  				{ip: "1.1.1.2", port: 12, endpoint: "1.1.1.2:12", isLocal: false, ready: true, serving: true, terminating: false},
  1007  			},
  1008  			makeServicePortName("ns1", "ep1", "p122", v1.ProtocolUDP): {
  1009  				{ip: "1.1.1.2", port: 122, endpoint: "1.1.1.2:122", isLocal: false, ready: true, serving: true, terminating: false},
  1010  			},
  1011  			makeServicePortName("ns3", "ep3", "p33", v1.ProtocolUDP): {
  1012  				{ip: "3.3.3.3", port: 33, endpoint: "3.3.3.3:33", isLocal: false, ready: true, serving: true, terminating: false},
  1013  			},
  1014  			makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): {
  1015  				{ip: "4.4.4.4", port: 44, endpoint: "4.4.4.4:44", isLocal: true, ready: true, serving: true, terminating: false},
  1016  			},
  1017  		},
  1018  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
  1019  			Endpoint:        "2.2.2.2:22",
  1020  			ServicePortName: makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP),
  1021  		}, {
  1022  			Endpoint:        "2.2.2.22:22",
  1023  			ServicePortName: makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP),
  1024  		}, {
  1025  			Endpoint:        "2.2.2.3:23",
  1026  			ServicePortName: makeServicePortName("ns2", "ep2", "p23", v1.ProtocolUDP),
  1027  		}, {
  1028  			Endpoint:        "4.4.4.5:44",
  1029  			ServicePortName: makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP),
  1030  		}, {
  1031  			Endpoint:        "4.4.4.6:45",
  1032  			ServicePortName: makeServicePortName("ns4", "ep4", "p45", v1.ProtocolUDP),
  1033  		}},
  1034  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
  1035  			makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP):  true,
  1036  			makeServicePortName("ns1", "ep1", "p122", v1.ProtocolUDP): true,
  1037  			makeServicePortName("ns3", "ep3", "p33", v1.ProtocolUDP):  true,
  1038  		},
  1039  		expectedLocalEndpoints: map[types.NamespacedName]int{
  1040  			makeNSN("ns4", "ep4"): 1,
  1041  		},
  1042  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1"), makeNSN("ns2", "ep2"), makeNSN("ns3", "ep3"), makeNSN("ns4", "ep4")),
  1043  	}, {
  1044  		name: "change from 0 endpoint address to 1 unnamed port",
  1045  		previousEndpointSlices: []*discovery.EndpointSlice{
  1046  			makeTestEndpointSlice("ns1", "ep1", 1, emptyEndpoint),
  1047  		},
  1048  		currentEndpointSlices: []*discovery.EndpointSlice{
  1049  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPort),
  1050  		},
  1051  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{},
  1052  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
  1053  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
  1054  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
  1055  			},
  1056  		},
  1057  		expectedDeletedUDPEndpoints: []ServiceEndpoint{},
  1058  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{
  1059  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): true,
  1060  		},
  1061  		expectedLocalEndpoints:   map[types.NamespacedName]int{},
  1062  		expectedChangedEndpoints: sets.New(makeNSN("ns1", "ep1")),
  1063  	}, {
  1064  		name: "change from ready to terminating pod",
  1065  		previousEndpointSlices: []*discovery.EndpointSlice{
  1066  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPortReady),
  1067  		},
  1068  		currentEndpointSlices: []*discovery.EndpointSlice{
  1069  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPortTerminating),
  1070  		},
  1071  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
  1072  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
  1073  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: true, serving: true, terminating: false},
  1074  			},
  1075  		},
  1076  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{
  1077  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
  1078  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: false, serving: true, terminating: true},
  1079  			},
  1080  		},
  1081  		expectedDeletedUDPEndpoints:    []ServiceEndpoint{},
  1082  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
  1083  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
  1084  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
  1085  	}, {
  1086  		name: "change from terminating to empty pod",
  1087  		previousEndpointSlices: []*discovery.EndpointSlice{
  1088  			makeTestEndpointSlice("ns1", "ep1", 1, unnamedPortTerminating),
  1089  		},
  1090  		currentEndpointSlices: []*discovery.EndpointSlice{
  1091  			makeTestEndpointSlice("ns1", "ep1", 1, emptyEndpoint),
  1092  		},
  1093  		previousEndpointsMap: map[ServicePortName][]*BaseEndpointInfo{
  1094  			makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
  1095  				{ip: "1.1.1.1", port: 11, endpoint: "1.1.1.1:11", isLocal: false, ready: false, serving: true, terminating: true},
  1096  			},
  1097  		},
  1098  		expectedResult: map[ServicePortName][]*BaseEndpointInfo{},
  1099  		expectedDeletedUDPEndpoints: []ServiceEndpoint{{
  1100  			Endpoint:        "1.1.1.1:11",
  1101  			ServicePortName: makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP),
  1102  		}},
  1103  		expectedNewlyActiveUDPServices: map[ServicePortName]bool{},
  1104  		expectedLocalEndpoints:         map[types.NamespacedName]int{},
  1105  		expectedChangedEndpoints:       sets.New(makeNSN("ns1", "ep1")),
  1106  	},
  1107  	}
  1108  
  1109  	for tci, tc := range testCases {
  1110  		t.Run(tc.name, func(t *testing.T) {
  1111  			fp := newFakeProxier(v1.IPv4Protocol, time.Time{})
  1112  			fp.hostname = testHostname
  1113  
  1114  			// First check that after adding all previous versions of endpoints,
  1115  			// the fp.previousEndpointsMap is as we expect.
  1116  			for i := range tc.previousEndpointSlices {
  1117  				if tc.previousEndpointSlices[i] != nil {
  1118  					fp.addEndpointSlice(tc.previousEndpointSlices[i])
  1119  				}
  1120  			}
  1121  			fp.endpointsMap.Update(fp.endpointsChanges)
  1122  			compareEndpointsMapsStr(t, fp.endpointsMap, tc.previousEndpointsMap)
  1123  
  1124  			// Now let's call appropriate handlers to get to state we want to be.
  1125  			if len(tc.previousEndpointSlices) != len(tc.currentEndpointSlices) {
  1126  				t.Fatalf("[%d] different lengths of previous and current endpoints", tci)
  1127  				return
  1128  			}
  1129  
  1130  			for i := range tc.previousEndpointSlices {
  1131  				prev, curr := tc.previousEndpointSlices[i], tc.currentEndpointSlices[i]
  1132  				switch {
  1133  				case prev == nil && curr == nil:
  1134  					continue
  1135  				case prev == nil:
  1136  					fp.addEndpointSlice(curr)
  1137  				case curr == nil:
  1138  					fp.deleteEndpointSlice(prev)
  1139  				default:
  1140  					fp.updateEndpointSlice(prev, curr)
  1141  				}
  1142  			}
  1143  
  1144  			result := fp.endpointsMap.Update(fp.endpointsChanges)
  1145  			newMap := fp.endpointsMap
  1146  			compareEndpointsMapsStr(t, newMap, tc.expectedResult)
  1147  			if !result.UpdatedServices.Equal(tc.expectedChangedEndpoints) {
  1148  				t.Errorf("[%d] expected changed endpoints %q, got %q", tci, tc.expectedChangedEndpoints.UnsortedList(), result.UpdatedServices.UnsortedList())
  1149  			}
  1150  			if len(result.DeletedUDPEndpoints) != len(tc.expectedDeletedUDPEndpoints) {
  1151  				t.Errorf("[%d] expected %d staleEndpoints, got %d: %v", tci, len(tc.expectedDeletedUDPEndpoints), len(result.DeletedUDPEndpoints), result.DeletedUDPEndpoints)
  1152  			}
  1153  			for _, x := range tc.expectedDeletedUDPEndpoints {
  1154  				found := false
  1155  				for _, stale := range result.DeletedUDPEndpoints {
  1156  					if stale == x {
  1157  						found = true
  1158  						break
  1159  					}
  1160  				}
  1161  				if !found {
  1162  					t.Errorf("[%d] expected staleEndpoints[%v], but didn't find it: %v", tci, x, result.DeletedUDPEndpoints)
  1163  				}
  1164  			}
  1165  			if len(result.NewlyActiveUDPServices) != len(tc.expectedNewlyActiveUDPServices) {
  1166  				t.Errorf("[%d] expected %d newlyActiveUDPServices, got %d: %v", tci, len(tc.expectedNewlyActiveUDPServices), len(result.NewlyActiveUDPServices), result.NewlyActiveUDPServices)
  1167  			}
  1168  			for svcName := range tc.expectedNewlyActiveUDPServices {
  1169  				found := false
  1170  				for _, newSvcName := range result.NewlyActiveUDPServices {
  1171  					if newSvcName == svcName {
  1172  						found = true
  1173  					}
  1174  				}
  1175  				if !found {
  1176  					t.Errorf("[%d] expected newlyActiveUDPServices[%v], but didn't find it: %v", tci, svcName, result.NewlyActiveUDPServices)
  1177  				}
  1178  			}
  1179  
  1180  			localReadyEndpoints := fp.endpointsMap.LocalReadyEndpoints()
  1181  			if !reflect.DeepEqual(localReadyEndpoints, tc.expectedLocalEndpoints) {
  1182  				t.Errorf("[%d] expected local ready endpoints %v, got %v", tci, tc.expectedLocalEndpoints, localReadyEndpoints)
  1183  			}
  1184  		})
  1185  	}
  1186  }
  1187  
  1188  func TestLastChangeTriggerTime(t *testing.T) {
  1189  	startTime := time.Date(2018, 01, 01, 0, 0, 0, 0, time.UTC)
  1190  	t_1 := startTime.Add(-time.Second)
  1191  	t0 := startTime.Add(time.Second)
  1192  	t1 := t0.Add(time.Second)
  1193  	t2 := t1.Add(time.Second)
  1194  	t3 := t2.Add(time.Second)
  1195  
  1196  	createEndpoints := func(namespace, name string, triggerTime time.Time) *discovery.EndpointSlice {
  1197  		return &discovery.EndpointSlice{
  1198  			ObjectMeta: metav1.ObjectMeta{
  1199  				Name:      name,
  1200  				Namespace: namespace,
  1201  				Annotations: map[string]string{
  1202  					v1.EndpointsLastChangeTriggerTime: triggerTime.Format(time.RFC3339Nano),
  1203  				},
  1204  				Labels: map[string]string{
  1205  					discovery.LabelServiceName: name,
  1206  				},
  1207  			},
  1208  			AddressType: discovery.AddressTypeIPv4,
  1209  			Endpoints: []discovery.Endpoint{{
  1210  				Addresses: []string{"1.1.1.1"},
  1211  			}},
  1212  			Ports: []discovery.EndpointPort{{
  1213  				Name:     ptr.To("p11"),
  1214  				Port:     ptr.To[int32](11),
  1215  				Protocol: ptr.To(v1.ProtocolTCP),
  1216  			}},
  1217  		}
  1218  	}
  1219  
  1220  	createName := func(namespace, name string) types.NamespacedName {
  1221  		return types.NamespacedName{Namespace: namespace, Name: name}
  1222  	}
  1223  
  1224  	modifyEndpoints := func(slice *discovery.EndpointSlice, triggerTime time.Time) *discovery.EndpointSlice {
  1225  		e := slice.DeepCopy()
  1226  		(*e.Ports[0].Port)++
  1227  		e.Annotations[v1.EndpointsLastChangeTriggerTime] = triggerTime.Format(time.RFC3339Nano)
  1228  		return e
  1229  	}
  1230  
  1231  	testCases := []struct {
  1232  		name     string
  1233  		scenario func(fp *FakeProxier)
  1234  		expected map[types.NamespacedName][]time.Time
  1235  	}{
  1236  		{
  1237  			name: "Single addEndpoints",
  1238  			scenario: func(fp *FakeProxier) {
  1239  				e := createEndpoints("ns", "ep1", t0)
  1240  				fp.addEndpointSlice(e)
  1241  			},
  1242  			expected: map[types.NamespacedName][]time.Time{createName("ns", "ep1"): {t0}},
  1243  		},
  1244  		{
  1245  			name: "addEndpoints then updatedEndpoints",
  1246  			scenario: func(fp *FakeProxier) {
  1247  				e := createEndpoints("ns", "ep1", t0)
  1248  				fp.addEndpointSlice(e)
  1249  
  1250  				e1 := modifyEndpoints(e, t1)
  1251  				fp.updateEndpointSlice(e, e1)
  1252  			},
  1253  			expected: map[types.NamespacedName][]time.Time{createName("ns", "ep1"): {t0, t1}},
  1254  		},
  1255  		{
  1256  			name: "Add two endpoints then modify one",
  1257  			scenario: func(fp *FakeProxier) {
  1258  				e1 := createEndpoints("ns", "ep1", t1)
  1259  				fp.addEndpointSlice(e1)
  1260  
  1261  				e2 := createEndpoints("ns", "ep2", t2)
  1262  				fp.addEndpointSlice(e2)
  1263  
  1264  				e11 := modifyEndpoints(e1, t3)
  1265  				fp.updateEndpointSlice(e1, e11)
  1266  			},
  1267  			expected: map[types.NamespacedName][]time.Time{createName("ns", "ep1"): {t1, t3}, createName("ns", "ep2"): {t2}},
  1268  		},
  1269  		{
  1270  			name: "Endpoints without annotation set",
  1271  			scenario: func(fp *FakeProxier) {
  1272  				e := createEndpoints("ns", "ep1", t1)
  1273  				delete(e.Annotations, v1.EndpointsLastChangeTriggerTime)
  1274  				fp.addEndpointSlice(e)
  1275  			},
  1276  			expected: map[types.NamespacedName][]time.Time{},
  1277  		},
  1278  		{
  1279  			name: "Endpoints create before tracker started",
  1280  			scenario: func(fp *FakeProxier) {
  1281  				e := createEndpoints("ns", "ep1", t_1)
  1282  				fp.addEndpointSlice(e)
  1283  			},
  1284  			expected: map[types.NamespacedName][]time.Time{},
  1285  		},
  1286  		{
  1287  			name: "addEndpoints then deleteEndpoints",
  1288  			scenario: func(fp *FakeProxier) {
  1289  				e := createEndpoints("ns", "ep1", t1)
  1290  				fp.addEndpointSlice(e)
  1291  				fp.deleteEndpointSlice(e)
  1292  			},
  1293  			expected: map[types.NamespacedName][]time.Time{},
  1294  		},
  1295  		{
  1296  			name: "add then delete then add again",
  1297  			scenario: func(fp *FakeProxier) {
  1298  				e := createEndpoints("ns", "ep1", t1)
  1299  				fp.addEndpointSlice(e)
  1300  				fp.deleteEndpointSlice(e)
  1301  				e = modifyEndpoints(e, t2)
  1302  				fp.addEndpointSlice(e)
  1303  			},
  1304  			expected: map[types.NamespacedName][]time.Time{createName("ns", "ep1"): {t2}},
  1305  		},
  1306  		{
  1307  			name: "delete",
  1308  			scenario: func(fp *FakeProxier) {
  1309  				e := createEndpoints("ns", "ep1", t1)
  1310  				fp.deleteEndpointSlice(e)
  1311  			},
  1312  			expected: map[types.NamespacedName][]time.Time{},
  1313  		},
  1314  	}
  1315  
  1316  	for _, tc := range testCases {
  1317  		fp := newFakeProxier(v1.IPv4Protocol, startTime)
  1318  
  1319  		tc.scenario(fp)
  1320  
  1321  		result := fp.endpointsMap.Update(fp.endpointsChanges)
  1322  		got := result.LastChangeTriggerTimes
  1323  
  1324  		if !reflect.DeepEqual(got, tc.expected) {
  1325  			t.Errorf("%s: Invalid LastChangeTriggerTimes, expected: %v, got: %v",
  1326  				tc.name, tc.expected, result.LastChangeTriggerTimes)
  1327  		}
  1328  	}
  1329  }
  1330  
  1331  func TestEndpointSliceUpdate(t *testing.T) {
  1332  	fqdnSlice := generateEndpointSlice("svc1", "ns1", 2, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)})
  1333  	fqdnSlice.AddressType = discovery.AddressTypeFQDN
  1334  
  1335  	testCases := map[string]struct {
  1336  		startingSlices         []*discovery.EndpointSlice
  1337  		endpointsChangeTracker *EndpointsChangeTracker
  1338  		namespacedName         types.NamespacedName
  1339  		paramEndpointSlice     *discovery.EndpointSlice
  1340  		paramRemoveSlice       bool
  1341  		expectedReturnVal      bool
  1342  		expectedCurrentChange  map[ServicePortName][]*BaseEndpointInfo
  1343  	}{
  1344  		// test starting from an empty state
  1345  		"add a simple slice that doesn't already exist": {
  1346  			startingSlices:         []*discovery.EndpointSlice{},
  1347  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1348  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1349  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1350  			paramRemoveSlice:       false,
  1351  			expectedReturnVal:      true,
  1352  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1353  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1354  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
  1355  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1356  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
  1357  				},
  1358  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1359  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: false, ready: true, serving: true, terminating: false},
  1360  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1361  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: false, ready: true, serving: true, terminating: false},
  1362  				},
  1363  			},
  1364  		},
  1365  		// test no modification to state - current change should be nil as nothing changes
  1366  		"add the same slice that already exists": {
  1367  			startingSlices: []*discovery.EndpointSlice{
  1368  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1369  			},
  1370  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1371  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1372  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1373  			paramRemoveSlice:       false,
  1374  			expectedReturnVal:      false,
  1375  			expectedCurrentChange:  nil,
  1376  		},
  1377  		// ensure that only valide address types are processed
  1378  		"add an FQDN slice (invalid address type)": {
  1379  			startingSlices: []*discovery.EndpointSlice{
  1380  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1381  			},
  1382  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1383  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1384  			paramEndpointSlice:     fqdnSlice,
  1385  			paramRemoveSlice:       false,
  1386  			expectedReturnVal:      false,
  1387  			expectedCurrentChange:  nil,
  1388  		},
  1389  		// test additions to existing state
  1390  		"add a slice that overlaps with existing state": {
  1391  			startingSlices: []*discovery.EndpointSlice{
  1392  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1393  				generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1394  			},
  1395  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1396  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1397  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1398  			paramRemoveSlice:       false,
  1399  			expectedReturnVal:      true,
  1400  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1401  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1402  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1403  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1404  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: true, serving: true, terminating: false},
  1405  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: true, ready: true, serving: true, terminating: false},
  1406  					&BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: true, ready: true, serving: true, terminating: false},
  1407  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false},
  1408  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1409  				},
  1410  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1411  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1412  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1413  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: true, ready: true, serving: true, terminating: false},
  1414  					&BaseEndpointInfo{ip: "10.0.1.4", port: 443, endpoint: "10.0.1.4:443", isLocal: true, ready: true, serving: true, terminating: false},
  1415  					&BaseEndpointInfo{ip: "10.0.1.5", port: 443, endpoint: "10.0.1.5:443", isLocal: true, ready: true, serving: true, terminating: false},
  1416  					&BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: false, ready: true, serving: true, terminating: false},
  1417  					&BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1418  				},
  1419  			},
  1420  		},
  1421  		// test additions to existing state with partially overlapping slices and ports
  1422  		"add a slice that overlaps with existing state and partial ports": {
  1423  			startingSlices: []*discovery.EndpointSlice{
  1424  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1425  				generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1426  			},
  1427  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1428  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1429  			paramEndpointSlice:     generateEndpointSliceWithOffset("svc1", "ns1", 3, 1, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80)}),
  1430  			paramRemoveSlice:       false,
  1431  			expectedReturnVal:      true,
  1432  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1433  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1434  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1435  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1436  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: true, serving: true, terminating: false},
  1437  					&BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: true, ready: true, serving: true, terminating: false},
  1438  					&BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: true, ready: true, serving: true, terminating: false},
  1439  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false},
  1440  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1441  				},
  1442  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1443  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: false, ready: true, serving: true, terminating: false},
  1444  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1445  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: false, ready: true, serving: true, terminating: false},
  1446  					&BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: false, ready: true, serving: true, terminating: false},
  1447  					&BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1448  				},
  1449  			},
  1450  		},
  1451  		// test deletions from existing state with partially overlapping slices and ports
  1452  		"remove a slice that overlaps with existing state": {
  1453  			startingSlices: []*discovery.EndpointSlice{
  1454  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1455  				generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1456  			},
  1457  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1458  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1459  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1460  			paramRemoveSlice:       true,
  1461  			expectedReturnVal:      true,
  1462  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1463  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1464  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false},
  1465  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1466  				},
  1467  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1468  					&BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: false, ready: true, serving: true, terminating: false},
  1469  					&BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1470  				},
  1471  			},
  1472  		},
  1473  		// ensure a removal that has no effect turns into a no-op
  1474  		"remove a slice that doesn't even exist in current state": {
  1475  			startingSlices: []*discovery.EndpointSlice{
  1476  				generateEndpointSlice("svc1", "ns1", 1, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1477  				generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1478  			},
  1479  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1480  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1481  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 3, 5, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1482  			paramRemoveSlice:       true,
  1483  			expectedReturnVal:      false,
  1484  			expectedCurrentChange:  nil,
  1485  		},
  1486  		// start with all endpoints ready, transition to no endpoints ready
  1487  		"transition all endpoints to unready state": {
  1488  			startingSlices: []*discovery.EndpointSlice{
  1489  				generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1490  			},
  1491  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1492  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1493  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 3, 1, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1494  			paramRemoveSlice:       false,
  1495  			expectedReturnVal:      true,
  1496  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1497  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1498  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: false, serving: false, terminating: false},
  1499  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: false, serving: false, terminating: false},
  1500  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: false, serving: false, terminating: false},
  1501  				},
  1502  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1503  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: false, serving: false, terminating: false},
  1504  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: false, serving: false, terminating: false},
  1505  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: true, ready: false, serving: false, terminating: false},
  1506  				},
  1507  			},
  1508  		},
  1509  		// start with no endpoints ready, transition to all endpoints ready
  1510  		"transition all endpoints to ready state": {
  1511  			startingSlices: []*discovery.EndpointSlice{
  1512  				generateEndpointSlice("svc1", "ns1", 1, 2, 1, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1513  			},
  1514  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1515  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1516  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 2, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1517  			paramRemoveSlice:       false,
  1518  			expectedReturnVal:      true,
  1519  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1520  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1521  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1522  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1523  				},
  1524  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1525  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1526  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1527  				},
  1528  			},
  1529  		},
  1530  		// start with some endpoints ready, transition to more endpoints ready
  1531  		"transition some endpoints to ready state": {
  1532  			startingSlices: []*discovery.EndpointSlice{
  1533  				generateEndpointSlice("svc1", "ns1", 1, 3, 2, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1534  				generateEndpointSlice("svc1", "ns1", 2, 2, 2, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1535  			},
  1536  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1537  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1538  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 3, 3, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1539  			paramRemoveSlice:       false,
  1540  			expectedReturnVal:      true,
  1541  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1542  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1543  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1544  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
  1545  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: false, serving: false, terminating: false},
  1546  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1547  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: false, serving: false, terminating: false},
  1548  				},
  1549  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1550  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1551  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
  1552  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: true, ready: false, serving: false, terminating: false},
  1553  					&BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1554  					&BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: false, serving: false, terminating: false},
  1555  				},
  1556  			},
  1557  		},
  1558  		// start with some endpoints ready, transition to some terminating
  1559  		"transition some endpoints to terminating state": {
  1560  			startingSlices: []*discovery.EndpointSlice{
  1561  				generateEndpointSlice("svc1", "ns1", 1, 3, 2, 2, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1562  				generateEndpointSlice("svc1", "ns1", 2, 2, 2, 2, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1563  			},
  1564  			endpointsChangeTracker: NewEndpointsChangeTracker("host1", nil, v1.IPv4Protocol, nil, nil),
  1565  			namespacedName:         types.NamespacedName{Name: "svc1", Namespace: "ns1"},
  1566  			paramEndpointSlice:     generateEndpointSlice("svc1", "ns1", 1, 3, 3, 2, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1567  			paramRemoveSlice:       false,
  1568  			expectedReturnVal:      true,
  1569  			expectedCurrentChange: map[ServicePortName][]*BaseEndpointInfo{
  1570  				makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
  1571  					&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1572  					&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: false, serving: true, terminating: true},
  1573  					&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: false, serving: false, terminating: false},
  1574  					&BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: true, ready: true, serving: true, terminating: false},
  1575  					&BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: false, serving: false, terminating: true},
  1576  				},
  1577  				makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
  1578  					&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1579  					&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: false, serving: true, terminating: true},
  1580  					&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: true, ready: false, serving: false, terminating: false},
  1581  					&BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: true, ready: true, serving: true, terminating: false},
  1582  					&BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: false, serving: false, terminating: true},
  1583  				},
  1584  			},
  1585  		},
  1586  	}
  1587  
  1588  	for name, tc := range testCases {
  1589  		t.Run(name, func(t *testing.T) {
  1590  			initializeCache(tc.endpointsChangeTracker.endpointSliceCache, tc.startingSlices)
  1591  
  1592  			got := tc.endpointsChangeTracker.EndpointSliceUpdate(tc.paramEndpointSlice, tc.paramRemoveSlice)
  1593  			if !reflect.DeepEqual(got, tc.expectedReturnVal) {
  1594  				t.Errorf("EndpointSliceUpdate return value got: %v, want %v", got, tc.expectedReturnVal)
  1595  			}
  1596  
  1597  			changes := tc.endpointsChangeTracker.checkoutChanges()
  1598  			if tc.expectedCurrentChange == nil {
  1599  				if len(changes) != 0 {
  1600  					t.Errorf("Expected %s to have no changes", tc.namespacedName)
  1601  				}
  1602  			} else {
  1603  				if _, exists := changes[tc.namespacedName]; !exists {
  1604  					t.Fatalf("Expected %s to have changes", tc.namespacedName)
  1605  				}
  1606  				compareEndpointsMapsStr(t, changes[tc.namespacedName].current, tc.expectedCurrentChange)
  1607  			}
  1608  		})
  1609  	}
  1610  }
  1611  
  1612  func TestCheckoutChanges(t *testing.T) {
  1613  	svcPortName0 := ServicePortName{types.NamespacedName{Namespace: "ns1", Name: "svc1"}, "port-0", v1.ProtocolTCP}
  1614  	svcPortName1 := ServicePortName{types.NamespacedName{Namespace: "ns1", Name: "svc1"}, "port-1", v1.ProtocolTCP}
  1615  
  1616  	testCases := map[string]struct {
  1617  		endpointsChangeTracker *EndpointsChangeTracker
  1618  		expectedChanges        []*endpointsChange
  1619  		items                  map[types.NamespacedName]*endpointsChange
  1620  		appliedSlices          []*discovery.EndpointSlice
  1621  		pendingSlices          []*discovery.EndpointSlice
  1622  	}{
  1623  		"empty slices": {
  1624  			endpointsChangeTracker: NewEndpointsChangeTracker("", nil, v1.IPv4Protocol, nil, nil),
  1625  			expectedChanges:        []*endpointsChange{},
  1626  			appliedSlices:          []*discovery.EndpointSlice{},
  1627  			pendingSlices:          []*discovery.EndpointSlice{},
  1628  		},
  1629  		"adding initial slice": {
  1630  			endpointsChangeTracker: NewEndpointsChangeTracker("", nil, v1.IPv4Protocol, nil, nil),
  1631  			expectedChanges: []*endpointsChange{{
  1632  				previous: EndpointsMap{},
  1633  				current: EndpointsMap{
  1634  					svcPortName0: []Endpoint{
  1635  						&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", ready: true, serving: true, terminating: false},
  1636  						&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", ready: false, serving: true, terminating: true},
  1637  						&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", ready: false, serving: false, terminating: false},
  1638  					},
  1639  				},
  1640  			}},
  1641  			appliedSlices: []*discovery.EndpointSlice{},
  1642  			pendingSlices: []*discovery.EndpointSlice{
  1643  				generateEndpointSlice("svc1", "ns1", 1, 3, 3, 2, []string{"host1"}, []*int32{ptr.To[int32](80)}),
  1644  			},
  1645  		},
  1646  		"removing port in update": {
  1647  			endpointsChangeTracker: NewEndpointsChangeTracker("", nil, v1.IPv4Protocol, nil, nil),
  1648  			expectedChanges: []*endpointsChange{{
  1649  				previous: EndpointsMap{
  1650  					svcPortName0: []Endpoint{
  1651  						&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", ready: true, serving: true, terminating: false},
  1652  						&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", ready: true, serving: true, terminating: false},
  1653  						&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", ready: false, serving: false, terminating: false},
  1654  					},
  1655  					svcPortName1: []Endpoint{
  1656  						&BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", ready: true, serving: true, terminating: false},
  1657  						&BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", ready: true, serving: true, terminating: false},
  1658  						&BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", ready: false, serving: false, terminating: false},
  1659  					},
  1660  				},
  1661  				current: EndpointsMap{
  1662  					svcPortName0: []Endpoint{
  1663  						&BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", ready: true, serving: true, terminating: false},
  1664  						&BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", ready: true, serving: true, terminating: false},
  1665  						&BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", ready: false, serving: false, terminating: false},
  1666  					},
  1667  				},
  1668  			}},
  1669  			appliedSlices: []*discovery.EndpointSlice{
  1670  				generateEndpointSlice("svc1", "ns1", 1, 3, 3, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
  1671  			},
  1672  			pendingSlices: []*discovery.EndpointSlice{
  1673  				generateEndpointSlice("svc1", "ns1", 1, 3, 3, 999, []string{"host1"}, []*int32{ptr.To[int32](80)}),
  1674  			},
  1675  		},
  1676  	}
  1677  
  1678  	for name, tc := range testCases {
  1679  		t.Run(name, func(t *testing.T) {
  1680  			for _, slice := range tc.appliedSlices {
  1681  				tc.endpointsChangeTracker.EndpointSliceUpdate(slice, false)
  1682  			}
  1683  			tc.endpointsChangeTracker.checkoutChanges()
  1684  			for _, slice := range tc.pendingSlices {
  1685  				tc.endpointsChangeTracker.EndpointSliceUpdate(slice, false)
  1686  			}
  1687  			changes := tc.endpointsChangeTracker.checkoutChanges()
  1688  
  1689  			if len(tc.expectedChanges) != len(changes) {
  1690  				t.Fatalf("Expected %d changes, got %d", len(tc.expectedChanges), len(changes))
  1691  			}
  1692  
  1693  			for _, change := range changes {
  1694  				// All of the test cases have 0 or 1 changes, so if we're
  1695  				// here, then expectedChanges[0] is what we expect.
  1696  				expectedChange := tc.expectedChanges[0]
  1697  
  1698  				if !reflect.DeepEqual(change.previous, expectedChange.previous) {
  1699  					t.Errorf("Expected change.previous: %+v, got: %+v", expectedChange.previous, change.previous)
  1700  				}
  1701  
  1702  				if !reflect.DeepEqual(change.current, expectedChange.current) {
  1703  					t.Errorf("Expected change.current: %+v, got: %+v", expectedChange.current, change.current)
  1704  				}
  1705  			}
  1706  		})
  1707  	}
  1708  }
  1709  
  1710  // Test helpers
  1711  
  1712  func compareEndpointsMapsStr(t *testing.T, newMap EndpointsMap, expected map[ServicePortName][]*BaseEndpointInfo) {
  1713  	t.Helper()
  1714  	if len(newMap) != len(expected) {
  1715  		t.Fatalf("expected %d results, got %d: %v", len(expected), len(newMap), newMap)
  1716  	}
  1717  	endpointEqual := func(a, b *BaseEndpointInfo) bool {
  1718  		return a.endpoint == b.endpoint && a.isLocal == b.isLocal && a.ready == b.ready && a.serving == b.serving && a.terminating == b.terminating
  1719  	}
  1720  	for x := range expected {
  1721  		if len(newMap[x]) != len(expected[x]) {
  1722  			t.Logf("Endpoints %+v", newMap[x])
  1723  			t.Fatalf("expected %d endpoints for %v, got %d", len(expected[x]), x, len(newMap[x]))
  1724  		} else {
  1725  			for i := range expected[x] {
  1726  				newEp, ok := newMap[x][i].(*BaseEndpointInfo)
  1727  				if !ok {
  1728  					t.Fatalf("Failed to cast endpointInfo")
  1729  				}
  1730  				if !endpointEqual(newEp, expected[x][i]) {
  1731  					t.Fatalf("expected new[%v][%d] to be %v, got %v"+
  1732  						"(IsLocal expected %v, got %v) (Ready expected %v, got %v) (Serving expected %v, got %v) (Terminating expected %v got %v)",
  1733  						x, i, expected[x][i], newEp, expected[x][i].isLocal, newEp.isLocal, expected[x][i].ready, newEp.ready,
  1734  						expected[x][i].serving, newEp.serving, expected[x][i].terminating, newEp.terminating)
  1735  				}
  1736  			}
  1737  		}
  1738  	}
  1739  }
  1740  
  1741  func initializeCache(endpointSliceCache *EndpointSliceCache, endpointSlices []*discovery.EndpointSlice) {
  1742  	for _, endpointSlice := range endpointSlices {
  1743  		endpointSliceCache.updatePending(endpointSlice, false)
  1744  	}
  1745  
  1746  	for _, tracker := range endpointSliceCache.trackerByServiceMap {
  1747  		tracker.applied = tracker.pending
  1748  		tracker.pending = endpointSliceDataByName{}
  1749  	}
  1750  }
  1751  

View as plain text