Updating TopologyCache to disregard unready endpoints in calculations

This commit is contained in:
Rob Scott 2021-11-17 15:39:09 -08:00
parent 1367cca8fd
commit 9813ec7e8a
No known key found for this signature in database
GPG Key ID: D91A796D0CFF0C5D
8 changed files with 333 additions and 119 deletions

View File

@ -516,7 +516,7 @@ func TestReconcile1EndpointSlicePublishNotReadyAddresses(t *testing.T) {
endpointSlices := fetchEndpointSlices(t, client, namespace) endpointSlices := fetchEndpointSlices(t, client, namespace)
for _, endpointSlice := range endpointSlices { for _, endpointSlice := range endpointSlices {
for i, endpoint := range endpointSlice.Endpoints { for i, endpoint := range endpointSlice.Endpoints {
if !*endpoint.Conditions.Ready { if !endpointsliceutil.EndpointReady(endpoint) {
t.Errorf("Expected endpoints[%d] to be ready", i) t.Errorf("Expected endpoints[%d] to be ready", i)
} }
} }

View File

@ -30,16 +30,16 @@ type SliceInfo struct {
Unchanged []*discovery.EndpointSlice Unchanged []*discovery.EndpointSlice
} }
func (si *SliceInfo) getTotalEndpoints() int { func (si *SliceInfo) getTotalReadyEndpoints() int {
totalEndpoints := 0 totalEndpoints := 0
for _, slice := range si.ToCreate { for _, slice := range si.ToCreate {
totalEndpoints += len(slice.Endpoints) totalEndpoints += numReadyEndpoints(slice.Endpoints)
} }
for _, slice := range si.ToUpdate { for _, slice := range si.ToUpdate {
totalEndpoints += len(slice.Endpoints) totalEndpoints += numReadyEndpoints(slice.Endpoints)
} }
for _, slice := range si.Unchanged { for _, slice := range si.Unchanged {
totalEndpoints += len(slice.Endpoints) totalEndpoints += numReadyEndpoints(slice.Endpoints)
} }
return totalEndpoints return totalEndpoints
} }

View File

@ -21,13 +21,15 @@ import (
"testing" "testing"
discovery "k8s.io/api/discovery/v1" discovery "k8s.io/api/discovery/v1"
utilpointer "k8s.io/utils/pointer"
) )
func TestGetTotalEndpoints(t *testing.T) { func Test_getTotalReadyEndpoints(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
si *SliceInfo si *SliceInfo
expectedTotal int expectedTotalReady int
expectedTotal int // this is really just testing the test helper
}{{ }{{
name: "empty", name: "empty",
si: &SliceInfo{}, si: &SliceInfo{},
@ -35,30 +37,49 @@ func TestGetTotalEndpoints(t *testing.T) {
}, { }, {
name: "empty slice", name: "empty slice",
si: &SliceInfo{ si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(0)}, ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(0, 0)},
}, },
expectedTotalReady: 0,
expectedTotal: 0, expectedTotal: 0,
}, { }, {
name: "multiple slices", name: "multiple slices",
si: &SliceInfo{ si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15), sliceWithNEndpoints(8)}, ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15, 0), sliceWithNEndpoints(8, 0)},
}, },
expectedTotalReady: 23,
expectedTotal: 23, expectedTotal: 23,
}, { }, {
name: "slices for all", name: "slices for all",
si: &SliceInfo{ si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15), sliceWithNEndpoints(8)}, ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15, 0), sliceWithNEndpoints(8, 0)},
ToUpdate: []*discovery.EndpointSlice{sliceWithNEndpoints(2)}, ToUpdate: []*discovery.EndpointSlice{sliceWithNEndpoints(2, 0)},
Unchanged: []*discovery.EndpointSlice{sliceWithNEndpoints(100), sliceWithNEndpoints(90)}, Unchanged: []*discovery.EndpointSlice{sliceWithNEndpoints(100, 0), sliceWithNEndpoints(90, 0)},
}, },
expectedTotalReady: 215,
expectedTotal: 215, expectedTotal: 215,
}, {
name: "slices for all with some unready",
si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15, 3), sliceWithNEndpoints(5, 4)},
ToUpdate: []*discovery.EndpointSlice{sliceWithNEndpoints(3, 8)},
Unchanged: []*discovery.EndpointSlice{sliceWithNEndpoints(98, 2), sliceWithNEndpoints(90, 6)},
},
expectedTotalReady: 211,
expectedTotal: 234,
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
actualTotal := tc.si.getTotalEndpoints() actualTotal := countEndpoints(tc.si.ToCreate) + countEndpoints(tc.si.ToUpdate) + countEndpoints(tc.si.Unchanged)
if actualTotal != tc.expectedTotal { if actualTotal != tc.expectedTotal {
t.Errorf("Expected %d, got %d", tc.expectedTotal, actualTotal) // This likely means that sliceWithNEndpoints or countEndpoints are not working as expected
t.Errorf("Problem with test or test helper. Expected %d total endpoints, got %d", tc.expectedTotal, actualTotal)
}
actualTotalReady := tc.si.getTotalReadyEndpoints()
if actualTotalReady != tc.expectedTotalReady {
t.Errorf("Expected %d, got %d", tc.expectedTotalReady, actualTotalReady)
} }
}) })
} }
@ -66,14 +87,32 @@ func TestGetTotalEndpoints(t *testing.T) {
// helpers // helpers
func sliceWithNEndpoints(n int) *discovery.EndpointSlice { func sliceWithNEndpoints(ready, unready int) *discovery.EndpointSlice {
endpoints := []discovery.Endpoint{} endpoints := make([]discovery.Endpoint, ready+unready)
for i := 0; i < n; i++ { for i := 0; i < ready; i++ {
endpoints = append(endpoints, discovery.Endpoint{Addresses: []string{fmt.Sprintf("10.1.2.%d", i)}}) endpoints[i] = discovery.Endpoint{
Addresses: []string{fmt.Sprintf("10.1.2.%d", i)},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}
}
for i := 0; i < unready; i++ {
endpoints[ready+i] = discovery.Endpoint{
Addresses: []string{fmt.Sprintf("10.1.2.%d", ready+i)},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
}
} }
return &discovery.EndpointSlice{ return &discovery.EndpointSlice{
Endpoints: endpoints, Endpoints: endpoints,
} }
} }
func countEndpoints(slices []*discovery.EndpointSlice) int {
total := 0
for _, slice := range slices {
total += len(slice.Endpoints)
}
return total
}

View File

@ -24,6 +24,7 @@ import (
discovery "k8s.io/api/discovery/v1" discovery "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/klog/v2" "k8s.io/klog/v2"
endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice"
) )
const ( const (
@ -84,7 +85,7 @@ func (t *TopologyCache) GetOverloadedServices() []string {
// AddHints adds or updates topology hints on EndpointSlices and returns updated // AddHints adds or updates topology hints on EndpointSlices and returns updated
// lists of EndpointSlices to create and update. // lists of EndpointSlices to create and update.
func (t *TopologyCache) AddHints(si *SliceInfo) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice) { func (t *TopologyCache) AddHints(si *SliceInfo) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice) {
totalEndpoints := si.getTotalEndpoints() totalEndpoints := si.getTotalReadyEndpoints()
allocations := t.getAllocations(totalEndpoints) allocations := t.getAllocations(totalEndpoints)
if allocations == nil { if allocations == nil {
@ -103,6 +104,10 @@ func (t *TopologyCache) AddHints(si *SliceInfo) ([]*discovery.EndpointSlice, []*
// step 1: assign same-zone hints for all endpoints as a starting point. // step 1: assign same-zone hints for all endpoints as a starting point.
for _, slice := range allocatableSlices { for _, slice := range allocatableSlices {
for i, endpoint := range slice.Endpoints { for i, endpoint := range slice.Endpoints {
if !endpointsliceutil.EndpointReady(endpoint) {
endpoint.Hints = nil
continue
}
if endpoint.Zone == nil || *endpoint.Zone == "" { if endpoint.Zone == nil || *endpoint.Zone == "" {
klog.InfoS("Endpoint found without zone specified, removing hints from service", "serviceKey", si.ServiceKey) klog.InfoS("Endpoint found without zone specified, removing hints from service", "serviceKey", si.ServiceKey)
t.RemoveHints(si.ServiceKey, si.AddressType) t.RemoveHints(si.ServiceKey, si.AddressType)

View File

@ -55,6 +55,7 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
}, },
@ -63,6 +64,7 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
expectedSlicesToUpdate: []*discovery.EndpointSlice{}, expectedSlicesToUpdate: []*discovery.EndpointSlice{},
@ -80,9 +82,11 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
}, },
@ -91,9 +95,11 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
expectedSlicesToUpdate: []*discovery.EndpointSlice{}, expectedSlicesToUpdate: []*discovery.EndpointSlice{},
@ -110,9 +116,59 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}},
}},
},
expectedEndpointsByAddrType: map[discovery.AddressType]EndpointZoneInfo{
discovery.AddressTypeIPv4: {
"zone-a": 1,
"zone-b": 1,
},
},
expectedSlicesToCreate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}},
}},
expectedSlicesToUpdate: []*discovery.EndpointSlice{},
}, {
name: "slice to create with 2 ready, 1 unready, 1 unknown endpoints, zone ratios only require 2",
cpuRatiosByZone: map[string]float64{
"zone-a": 0.45,
"zone-b": 0.55,
},
sliceInfo: &SliceInfo{
ServiceKey: "ns/svc",
AddressType: discovery.AddressTypeIPv4,
ToCreate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, {
Addresses: []string{"10.1.2.5"},
Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
}, {
Addresses: []string{"10.1.2.6"},
Zone: utilpointer.StringPtr("zone-b"),
}}, }},
}}, }},
}, },
@ -127,10 +183,21 @@ func TestAddHints(t *testing.T) {
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, {
Addresses: []string{"10.1.2.5"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: nil,
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
}, {
Addresses: []string{"10.1.2.6"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: nil,
}}, }},
}}, }},
expectedSlicesToUpdate: []*discovery.EndpointSlice{}, expectedSlicesToUpdate: []*discovery.EndpointSlice{},
@ -148,40 +215,50 @@ func TestAddHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}, { }, {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.3.3"}, Addresses: []string{"10.1.3.3"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.3.4"}, Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.3.4"}, Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
ToUpdate: []*discovery.EndpointSlice{{ ToUpdate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.2.3"}, Addresses: []string{"10.2.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.2.4"}, Addresses: []string{"10.2.2.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}, { }, {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.3.3"}, Addresses: []string{"10.2.3.3"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.3.4"}, Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.3.4"}, Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
}, },
@ -197,24 +274,29 @@ func TestAddHints(t *testing.T) {
Addresses: []string{"10.1.2.3"}, Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.2.4"}, Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}, { }, {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.3.3"}, Addresses: []string{"10.1.3.3"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.3.4"}, Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.1.3.4"}, Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
expectedSlicesToUpdate: []*discovery.EndpointSlice{{ expectedSlicesToUpdate: []*discovery.EndpointSlice{{
@ -222,24 +304,29 @@ func TestAddHints(t *testing.T) {
Addresses: []string{"10.2.2.3"}, Addresses: []string{"10.2.2.3"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.2.4"}, Addresses: []string{"10.2.2.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}, { }, {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.3.3"}, Addresses: []string{"10.2.3.3"},
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.3.4"}, Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-c"), Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Addresses: []string{"10.2.3.4"}, Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
}} }}

View File

@ -19,9 +19,10 @@ package topologycache
import ( import (
"math" "math"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1" discovery "k8s.io/api/discovery/v1"
"k8s.io/klog/v2" "k8s.io/klog/v2"
endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice"
) )
// RemoveHintsFromSlices removes topology hints on EndpointSlices and returns // RemoveHintsFromSlices removes topology hints on EndpointSlices and returns
@ -75,6 +76,10 @@ func redistributeHints(slices []*discovery.EndpointSlice, givingZones, receiving
for _, slice := range slices { for _, slice := range slices {
for i, endpoint := range slice.Endpoints { for i, endpoint := range slice.Endpoints {
if !endpointsliceutil.EndpointReady(endpoint) {
endpoint.Hints = nil
continue
}
if len(givingZones) == 0 || len(receivingZones) == 0 { if len(givingZones) == 0 || len(receivingZones) == 0 {
return redistributions return redistributions
} }
@ -185,6 +190,9 @@ func getMost(zones map[string]float64) (string, float64) {
func getHintsByZone(slice *discovery.EndpointSlice, allocatedHintsByZone EndpointZoneInfo, allocations map[string]Allocation) map[string]int { func getHintsByZone(slice *discovery.EndpointSlice, allocatedHintsByZone EndpointZoneInfo, allocations map[string]Allocation) map[string]int {
hintsByZone := map[string]int{} hintsByZone := map[string]int{}
for _, endpoint := range slice.Endpoints { for _, endpoint := range slice.Endpoints {
if !endpointsliceutil.EndpointReady(endpoint) {
continue
}
if endpoint.Hints == nil || len(endpoint.Hints.ForZones) == 0 { if endpoint.Hints == nil || len(endpoint.Hints.ForZones) == 0 {
return nil return nil
} }
@ -248,3 +256,15 @@ func NodeReady(nodeStatus v1.NodeStatus) bool {
} }
return false return false
} }
// numReadyEndpoints returns the number of Endpoints that are ready from the
// specified list.
func numReadyEndpoints(endpoints []discovery.Endpoint) int {
var total int
for _, ep := range endpoints {
if ep.Conditions.Ready != nil && *ep.Conditions.Ready {
total++
}
}
return total
}

View File

@ -43,6 +43,7 @@ func Test_redistributeHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
givingZones: map[string]int{"zone-a": 1}, givingZones: map[string]int{"zone-a": 1},
@ -54,12 +55,15 @@ func Test_redistributeHints(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, { }, {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}}, }},
}}, }},
givingZones: map[string]int{"zone-a": 2}, givingZones: map[string]int{"zone-a": 2},
@ -213,6 +217,30 @@ func Test_getHintsByZone(t *testing.T) {
Endpoints: []discovery.Endpoint{{ Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}},
},
allocations: map[string]Allocation{
"zone-a": {Maximum: 3},
},
allocatedHintsByZone: EndpointZoneInfo{"zone-a": 1},
expectedHintsByZone: map[string]int{
"zone-a": 1,
},
}, {
name: "single zone hint with 1 unready endpoint and 1 unknown endpoint",
slice: discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, {
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
}, {
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}}, }},
}, },
allocations: map[string]Allocation{ allocations: map[string]Allocation{
@ -229,14 +257,17 @@ func Test_getHintsByZone(t *testing.T) {
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
{ {
Zone: utilpointer.StringPtr("zone-b"), Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
}, },
}, },
@ -257,6 +288,7 @@ func Test_getHintsByZone(t *testing.T) {
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-non-existent"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-non-existent"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
}, },
}, },
@ -272,6 +304,7 @@ func Test_getHintsByZone(t *testing.T) {
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: nil, Hints: nil,
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
}, },
}, },
@ -287,6 +320,7 @@ func Test_getHintsByZone(t *testing.T) {
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
}, },
}, },
@ -302,10 +336,12 @@ func Test_getHintsByZone(t *testing.T) {
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
{ {
Zone: utilpointer.StringPtr("zone-a"), Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}}, Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
}, },
}, },
}, },

View File

@ -0,0 +1,27 @@
/*
Copyright 2021 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 endpointslice
import (
discovery "k8s.io/api/discovery/v1"
)
// EndpointReady returns true if an Endpoint has the Ready condition set to
// true.
func EndpointReady(endpoint discovery.Endpoint) bool {
return endpoint.Conditions.Ready != nil && *endpoint.Conditions.Ready
}