Add trafficdist package for handling reconciliation of new field
This commit is contained in:
		
							
								
								
									
										143
									
								
								staging/src/k8s.io/endpointslice/trafficdist/trafficdist.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								staging/src/k8s.io/endpointslice/trafficdist/trafficdist.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// trafficdist handles reconciliation of hints for trafficDistribution field.
 | 
				
			||||||
 | 
					package trafficdist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						discoveryv1 "k8s.io/api/discovery/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReconcileHints will reconcile hints for the given EndpointSlices.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// EndpointSlice resources within slicesUnchanged will not be modified.
 | 
				
			||||||
 | 
					func ReconcileHints(trafficDistribution *string, slicesToCreate, slicesToUpdate, slicesUnchanged []*discoveryv1.EndpointSlice) ([]*discoveryv1.EndpointSlice, []*discoveryv1.EndpointSlice, []*discoveryv1.EndpointSlice) {
 | 
				
			||||||
 | 
						var h heuristic = &defaultHeuristic{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if trafficDistribution != nil && *trafficDistribution == corev1.ServiceTrafficDistributionPreferClose {
 | 
				
			||||||
 | 
							h = &preferCloseHeuristic{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Identify the Unchanged slices that need an update because of missing or
 | 
				
			||||||
 | 
						// incorrect zone hint.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Uses filtering in place to remove any endpoints that are no longer
 | 
				
			||||||
 | 
						// unchanged and need to be moved to slicesToUpdate
 | 
				
			||||||
 | 
						// (https://github.com/golang/go/wiki/SliceTricks#filter-in-place)
 | 
				
			||||||
 | 
						j := 0
 | 
				
			||||||
 | 
						for _, slice := range slicesUnchanged {
 | 
				
			||||||
 | 
							if h.needsUpdate(slice) {
 | 
				
			||||||
 | 
								// Unchanged slices are direct copies from informer cache. We need to deep
 | 
				
			||||||
 | 
								// copy an unchanged slice before making any modifications to it so that we do
 | 
				
			||||||
 | 
								// not modify the slice within the informer cache.
 | 
				
			||||||
 | 
								slicesToUpdate = append(slicesToUpdate, slice.DeepCopy())
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								slicesUnchanged[j] = slice
 | 
				
			||||||
 | 
								j++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Truncate slicesUnchanged so it only includes slices that are still
 | 
				
			||||||
 | 
						// unchanged.
 | 
				
			||||||
 | 
						slicesUnchanged = slicesUnchanged[:j]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add zone hints to all slices that need to be created or updated.
 | 
				
			||||||
 | 
						for _, slice := range slicesToCreate {
 | 
				
			||||||
 | 
							h.update(slice)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, slice := range slicesToUpdate {
 | 
				
			||||||
 | 
							h.update(slice)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return slicesToCreate, slicesToUpdate, slicesUnchanged
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type heuristic interface {
 | 
				
			||||||
 | 
						needsUpdate(*discoveryv1.EndpointSlice) bool
 | 
				
			||||||
 | 
						update(*discoveryv1.EndpointSlice)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// endpointReady returns true if an Endpoint has the Ready condition set to
 | 
				
			||||||
 | 
					// true.
 | 
				
			||||||
 | 
					func endpointReady(endpoint discoveryv1.Endpoint) bool {
 | 
				
			||||||
 | 
						return endpoint.Conditions.Ready != nil && *endpoint.Conditions.Ready
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// defaultHeuristic means cluster wide routing, hence it will remove any hints
 | 
				
			||||||
 | 
					// present in the EndpointSlice.
 | 
				
			||||||
 | 
					type defaultHeuristic struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// needsUpdate returns true if any endpoint in the slice has a zone hint.
 | 
				
			||||||
 | 
					func (defaultHeuristic) needsUpdate(slice *discoveryv1.EndpointSlice) bool {
 | 
				
			||||||
 | 
						if slice == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, endpoint := range slice.Endpoints {
 | 
				
			||||||
 | 
							if endpoint.Hints != nil {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// update removes zone hints from all endpoints.
 | 
				
			||||||
 | 
					func (defaultHeuristic) update(slice *discoveryv1.EndpointSlice) {
 | 
				
			||||||
 | 
						for i := range slice.Endpoints {
 | 
				
			||||||
 | 
							slice.Endpoints[i].Hints = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// preferCloseHeuristic adds
 | 
				
			||||||
 | 
					type preferCloseHeuristic struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// needsUpdate returns true if any ready endpoint in the slice has a
 | 
				
			||||||
 | 
					// missing or incorrect hint.
 | 
				
			||||||
 | 
					func (preferCloseHeuristic) needsUpdate(slice *discoveryv1.EndpointSlice) bool {
 | 
				
			||||||
 | 
						if slice == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, endpoint := range slice.Endpoints {
 | 
				
			||||||
 | 
							if !endpointReady(endpoint) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var zone string
 | 
				
			||||||
 | 
							if endpoint.Zone != nil {
 | 
				
			||||||
 | 
								zone = *endpoint.Zone
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if endpoint.Hints == nil || len(endpoint.Hints.ForZones) != 1 || endpoint.Hints.ForZones[0].Name != zone {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// update adds a same zone topology hint for all ready endpoints
 | 
				
			||||||
 | 
					func (preferCloseHeuristic) update(slice *discoveryv1.EndpointSlice) {
 | 
				
			||||||
 | 
						for i, endpoint := range slice.Endpoints {
 | 
				
			||||||
 | 
							if !endpointReady(endpoint) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var zone string
 | 
				
			||||||
 | 
							if endpoint.Zone != nil {
 | 
				
			||||||
 | 
								zone = *endpoint.Zone
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							slice.Endpoints[i].Hints = &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: zone}}}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										470
									
								
								staging/src/k8s.io/endpointslice/trafficdist/trafficdist_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										470
									
								
								staging/src/k8s.io/endpointslice/trafficdist/trafficdist_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,470 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package trafficdist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
 | 
						"github.com/google/go-cmp/cmp/cmpopts"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						discoveryv1 "k8s.io/api/discovery/v1"
 | 
				
			||||||
 | 
						"k8s.io/utils/ptr"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReconcileHints_trafficDistribution_is_PreferClose(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							trafficDistribution *string
 | 
				
			||||||
 | 
							slicesToCreate      []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							slicesToUpdate      []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							slicesUnchanged     []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wantSlicesToCreate  []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							wantSlicesToUpdate  []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							wantSlicesUnchanged []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                "should set same zone hints",
 | 
				
			||||||
 | 
								trafficDistribution: ptrTo(corev1.ServiceTrafficDistributionPreferClose),
 | 
				
			||||||
 | 
								slicesToCreate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.4"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								slicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.6"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.7"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.8"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-c"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToCreate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.4"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.6"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.7"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.8"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-c"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-c"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                "incorrect hints should be corrected",
 | 
				
			||||||
 | 
								trafficDistribution: ptrTo(corev1.ServiceTrafficDistributionPreferClose),
 | 
				
			||||||
 | 
								slicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}}, // incorrect hint as per new heuristic
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								slicesUnchanged: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-c"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-c"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-c"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-c"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                "unready endpoints should not trigger updates",
 | 
				
			||||||
 | 
								trafficDistribution: ptrTo(corev1.ServiceTrafficDistributionPreferClose),
 | 
				
			||||||
 | 
								slicesUnchanged: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(false)}, // endpoint is not ready
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesUnchanged: []*discoveryv1.EndpointSlice{ // ... so there should be no updates
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(false)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								gotSlicesToCreate, gotSlicesToUpdate, gotSlicesUnchanged := ReconcileHints(tc.trafficDistribution, tc.slicesToCreate, tc.slicesToUpdate, tc.slicesUnchanged)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesToCreate, gotSlicesToCreate, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesToCreate': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesToUpdate, gotSlicesToUpdate, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesToUpdate': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesUnchanged, gotSlicesUnchanged, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesUnchanged': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReconcileHints_trafficDistribution_is_nil_or_empty(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							trafficDistribution *string
 | 
				
			||||||
 | 
							slicesToCreate      []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							slicesToUpdate      []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							slicesUnchanged     []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wantSlicesToCreate  []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							wantSlicesToUpdate  []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
							wantSlicesUnchanged []*discoveryv1.EndpointSlice
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                "trafficDistribution='' should remove zone hints",
 | 
				
			||||||
 | 
								trafficDistribution: ptrTo(""),
 | 
				
			||||||
 | 
								slicesToCreate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								slicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToCreate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.2"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.3"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                "trafficDistribution=nil should remove zone hints",
 | 
				
			||||||
 | 
								trafficDistribution: nil,
 | 
				
			||||||
 | 
								slicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-a"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								slicesUnchanged: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.6"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
												Hints:      &discoveryv1.EndpointHints{ForZones: []discoveryv1.ForZone{{Name: "zone-b"}}},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantSlicesToUpdate: []*discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.5"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Addresses:  []string{"10.0.0.6"},
 | 
				
			||||||
 | 
												Zone:       ptr.To("zone-b"),
 | 
				
			||||||
 | 
												Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								gotSlicesToCreate, gotSlicesToUpdate, gotSlicesUnchanged := ReconcileHints(tc.trafficDistribution, tc.slicesToCreate, tc.slicesToUpdate, tc.slicesUnchanged)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesToCreate, gotSlicesToCreate, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesToCreate': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesToUpdate, gotSlicesToUpdate, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesToUpdate': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tc.wantSlicesUnchanged, gotSlicesUnchanged, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("ReconcileHints(...) returned unexpected diff in 'slicesUnchanged': (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Ensure that the EndpointSlice objects within `slicesUnchanged` don't get modified.
 | 
				
			||||||
 | 
					func TestReconcileHints_doesNotMutateUnchangedSlices(t *testing.T) {
 | 
				
			||||||
 | 
						originalEps := &discoveryv1.EndpointSlice{
 | 
				
			||||||
 | 
							Endpoints: []discoveryv1.Endpoint{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Addresses:  []string{"10.0.0.1"},
 | 
				
			||||||
 | 
									Zone:       ptr.To("zone-a"),
 | 
				
			||||||
 | 
									Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						clonedEps := originalEps.DeepCopy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// originalEps should not get modified.
 | 
				
			||||||
 | 
						ReconcileHints(ptrTo(corev1.ServiceTrafficDistributionPreferClose), nil, nil, []*discoveryv1.EndpointSlice{originalEps})
 | 
				
			||||||
 | 
						if diff := cmp.Diff(clonedEps, originalEps); diff != "" {
 | 
				
			||||||
 | 
							t.Errorf("ReconcileHints(...) modified objects within slicesUnchanged, want objects within slicesUnchanged to remain unmodified: (-want, +got)\n%v", diff)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ptrTo[T any](obj T) *T {
 | 
				
			||||||
 | 
						return &obj
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user