Merge pull request #99522 from robscott/topology-hints

Adding support for Topology Aware Hints
This commit is contained in:
Kubernetes Prow Robot
2021-03-09 09:19:12 -08:00
committed by GitHub
59 changed files with 4418 additions and 297 deletions

View File

@@ -116,4 +116,9 @@ const (
//
// This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled.
PodDeletionCost = "controller.kubernetes.io/pod-deletion-cost"
// AnnotationTopologyAwareHints can be used to enable or disable Topology
// Aware Hints for a Service. This may be set to "auto" or "disabled". Any
// other value is treated as "disabled".
AnnotationTopologyAwareHints = "service.kubernetes.io/topology-aware-hints"
)

View File

@@ -100,6 +100,11 @@ type Endpoint struct {
// zone is the name of the Zone this endpoint exists in.
// +optional
Zone *string
// hints contains information associated with how an endpoint should be
// consumed.
// +featureGate=TopologyAwareHints
// +optional
Hints *EndpointHints
}
// EndpointConditions represents the current condition of an endpoint.
@@ -127,6 +132,19 @@ type EndpointConditions struct {
Terminating *bool
}
// EndpointHints provides hints describing how an endpoint should be consumed.
type EndpointHints struct {
// forZones indicates the zone(s) this endpoint should be consumed by to
// enable topology aware routing. May contain a maximum of 8 entries.
ForZones []ForZone
}
// ForZone provides information about which zones should consume this endpoint.
type ForZone struct {
// name represents the name of the zone.
Name string
}
// EndpointPort represents a Port used by an EndpointSlice.
type EndpointPort struct {
// The name of this port. All ports in an EndpointSlice must have a unique

View File

@@ -58,6 +58,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.EndpointHints)(nil), (*discovery.EndpointHints)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_EndpointHints_To_discovery_EndpointHints(a.(*v1.EndpointHints), b.(*discovery.EndpointHints), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*discovery.EndpointHints)(nil), (*v1.EndpointHints)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_discovery_EndpointHints_To_v1_EndpointHints(a.(*discovery.EndpointHints), b.(*v1.EndpointHints), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.EndpointPort)(nil), (*discovery.EndpointPort)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_EndpointPort_To_discovery_EndpointPort(a.(*v1.EndpointPort), b.(*discovery.EndpointPort), scope)
}); err != nil {
@@ -88,6 +98,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.ForZone)(nil), (*discovery.ForZone)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_ForZone_To_discovery_ForZone(a.(*v1.ForZone), b.(*discovery.ForZone), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*discovery.ForZone)(nil), (*v1.ForZone)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_discovery_ForZone_To_v1_ForZone(a.(*discovery.ForZone), b.(*v1.ForZone), scope)
}); err != nil {
return err
}
return nil
}
@@ -101,6 +121,7 @@ func autoConvert_v1_Endpoint_To_discovery_Endpoint(in *v1.Endpoint, out *discove
out.DeprecatedTopology = *(*map[string]string)(unsafe.Pointer(&in.DeprecatedTopology))
out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Zone = (*string)(unsafe.Pointer(in.Zone))
out.Hints = (*discovery.EndpointHints)(unsafe.Pointer(in.Hints))
return nil
}
@@ -119,6 +140,7 @@ func autoConvert_discovery_Endpoint_To_v1_Endpoint(in *discovery.Endpoint, out *
out.DeprecatedTopology = *(*map[string]string)(unsafe.Pointer(&in.DeprecatedTopology))
out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Zone = (*string)(unsafe.Pointer(in.Zone))
out.Hints = (*v1.EndpointHints)(unsafe.Pointer(in.Hints))
return nil
}
@@ -151,6 +173,26 @@ func Convert_discovery_EndpointConditions_To_v1_EndpointConditions(in *discovery
return autoConvert_discovery_EndpointConditions_To_v1_EndpointConditions(in, out, s)
}
func autoConvert_v1_EndpointHints_To_discovery_EndpointHints(in *v1.EndpointHints, out *discovery.EndpointHints, s conversion.Scope) error {
out.ForZones = *(*[]discovery.ForZone)(unsafe.Pointer(&in.ForZones))
return nil
}
// Convert_v1_EndpointHints_To_discovery_EndpointHints is an autogenerated conversion function.
func Convert_v1_EndpointHints_To_discovery_EndpointHints(in *v1.EndpointHints, out *discovery.EndpointHints, s conversion.Scope) error {
return autoConvert_v1_EndpointHints_To_discovery_EndpointHints(in, out, s)
}
func autoConvert_discovery_EndpointHints_To_v1_EndpointHints(in *discovery.EndpointHints, out *v1.EndpointHints, s conversion.Scope) error {
out.ForZones = *(*[]v1.ForZone)(unsafe.Pointer(&in.ForZones))
return nil
}
// Convert_discovery_EndpointHints_To_v1_EndpointHints is an autogenerated conversion function.
func Convert_discovery_EndpointHints_To_v1_EndpointHints(in *discovery.EndpointHints, out *v1.EndpointHints, s conversion.Scope) error {
return autoConvert_discovery_EndpointHints_To_v1_EndpointHints(in, out, s)
}
func autoConvert_v1_EndpointPort_To_discovery_EndpointPort(in *v1.EndpointPort, out *discovery.EndpointPort, s conversion.Scope) error {
out.Name = (*string)(unsafe.Pointer(in.Name))
out.Protocol = (*core.Protocol)(unsafe.Pointer(in.Protocol))
@@ -224,3 +266,23 @@ func autoConvert_discovery_EndpointSliceList_To_v1_EndpointSliceList(in *discove
func Convert_discovery_EndpointSliceList_To_v1_EndpointSliceList(in *discovery.EndpointSliceList, out *v1.EndpointSliceList, s conversion.Scope) error {
return autoConvert_discovery_EndpointSliceList_To_v1_EndpointSliceList(in, out, s)
}
func autoConvert_v1_ForZone_To_discovery_ForZone(in *v1.ForZone, out *discovery.ForZone, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_v1_ForZone_To_discovery_ForZone is an autogenerated conversion function.
func Convert_v1_ForZone_To_discovery_ForZone(in *v1.ForZone, out *discovery.ForZone, s conversion.Scope) error {
return autoConvert_v1_ForZone_To_discovery_ForZone(in, out, s)
}
func autoConvert_discovery_ForZone_To_v1_ForZone(in *discovery.ForZone, out *v1.ForZone, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_discovery_ForZone_To_v1_ForZone is an autogenerated conversion function.
func Convert_discovery_ForZone_To_v1_ForZone(in *discovery.ForZone, out *v1.ForZone, s conversion.Scope) error {
return autoConvert_discovery_ForZone_To_v1_ForZone(in, out, s)
}

View File

@@ -48,6 +48,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1beta1.EndpointHints)(nil), (*discovery.EndpointHints)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_EndpointHints_To_discovery_EndpointHints(a.(*v1beta1.EndpointHints), b.(*discovery.EndpointHints), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*discovery.EndpointHints)(nil), (*v1beta1.EndpointHints)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_discovery_EndpointHints_To_v1beta1_EndpointHints(a.(*discovery.EndpointHints), b.(*v1beta1.EndpointHints), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1beta1.EndpointPort)(nil), (*discovery.EndpointPort)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_EndpointPort_To_discovery_EndpointPort(a.(*v1beta1.EndpointPort), b.(*discovery.EndpointPort), scope)
}); err != nil {
@@ -78,6 +88,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1beta1.ForZone)(nil), (*discovery.ForZone)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ForZone_To_discovery_ForZone(a.(*v1beta1.ForZone), b.(*discovery.ForZone), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*discovery.ForZone)(nil), (*v1beta1.ForZone)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_discovery_ForZone_To_v1beta1_ForZone(a.(*discovery.ForZone), b.(*v1beta1.ForZone), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*discovery.Endpoint)(nil), (*v1beta1.Endpoint)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_discovery_Endpoint_To_v1beta1_Endpoint(a.(*discovery.Endpoint), b.(*v1beta1.Endpoint), scope)
}); err != nil {
@@ -100,6 +120,7 @@ func autoConvert_v1beta1_Endpoint_To_discovery_Endpoint(in *v1beta1.Endpoint, ou
out.TargetRef = (*core.ObjectReference)(unsafe.Pointer(in.TargetRef))
// WARNING: in.Topology requires manual conversion: does not exist in peer-type
out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Hints = (*discovery.EndpointHints)(unsafe.Pointer(in.Hints))
return nil
}
@@ -113,6 +134,7 @@ func autoConvert_discovery_Endpoint_To_v1beta1_Endpoint(in *discovery.Endpoint,
// WARNING: in.DeprecatedTopology requires manual conversion: does not exist in peer-type
out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
// WARNING: in.Zone requires manual conversion: does not exist in peer-type
out.Hints = (*v1beta1.EndpointHints)(unsafe.Pointer(in.Hints))
return nil
}
@@ -140,6 +162,26 @@ func Convert_discovery_EndpointConditions_To_v1beta1_EndpointConditions(in *disc
return autoConvert_discovery_EndpointConditions_To_v1beta1_EndpointConditions(in, out, s)
}
func autoConvert_v1beta1_EndpointHints_To_discovery_EndpointHints(in *v1beta1.EndpointHints, out *discovery.EndpointHints, s conversion.Scope) error {
out.ForZones = *(*[]discovery.ForZone)(unsafe.Pointer(&in.ForZones))
return nil
}
// Convert_v1beta1_EndpointHints_To_discovery_EndpointHints is an autogenerated conversion function.
func Convert_v1beta1_EndpointHints_To_discovery_EndpointHints(in *v1beta1.EndpointHints, out *discovery.EndpointHints, s conversion.Scope) error {
return autoConvert_v1beta1_EndpointHints_To_discovery_EndpointHints(in, out, s)
}
func autoConvert_discovery_EndpointHints_To_v1beta1_EndpointHints(in *discovery.EndpointHints, out *v1beta1.EndpointHints, s conversion.Scope) error {
out.ForZones = *(*[]v1beta1.ForZone)(unsafe.Pointer(&in.ForZones))
return nil
}
// Convert_discovery_EndpointHints_To_v1beta1_EndpointHints is an autogenerated conversion function.
func Convert_discovery_EndpointHints_To_v1beta1_EndpointHints(in *discovery.EndpointHints, out *v1beta1.EndpointHints, s conversion.Scope) error {
return autoConvert_discovery_EndpointHints_To_v1beta1_EndpointHints(in, out, s)
}
func autoConvert_v1beta1_EndpointPort_To_discovery_EndpointPort(in *v1beta1.EndpointPort, out *discovery.EndpointPort, s conversion.Scope) error {
out.Name = (*string)(unsafe.Pointer(in.Name))
out.Protocol = (*core.Protocol)(unsafe.Pointer(in.Protocol))
@@ -253,3 +295,23 @@ func autoConvert_discovery_EndpointSliceList_To_v1beta1_EndpointSliceList(in *di
func Convert_discovery_EndpointSliceList_To_v1beta1_EndpointSliceList(in *discovery.EndpointSliceList, out *v1beta1.EndpointSliceList, s conversion.Scope) error {
return autoConvert_discovery_EndpointSliceList_To_v1beta1_EndpointSliceList(in, out, s)
}
func autoConvert_v1beta1_ForZone_To_discovery_ForZone(in *v1beta1.ForZone, out *discovery.ForZone, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_v1beta1_ForZone_To_discovery_ForZone is an autogenerated conversion function.
func Convert_v1beta1_ForZone_To_discovery_ForZone(in *v1beta1.ForZone, out *discovery.ForZone, s conversion.Scope) error {
return autoConvert_v1beta1_ForZone_To_discovery_ForZone(in, out, s)
}
func autoConvert_discovery_ForZone_To_v1beta1_ForZone(in *discovery.ForZone, out *v1beta1.ForZone, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_discovery_ForZone_To_v1beta1_ForZone is an autogenerated conversion function.
func Convert_discovery_ForZone_To_v1beta1_ForZone(in *discovery.ForZone, out *v1beta1.ForZone, s conversion.Scope) error {
return autoConvert_discovery_ForZone_To_v1beta1_ForZone(in, out, s)
}

View File

@@ -45,6 +45,7 @@ var (
maxAddresses = 100
maxPorts = 20000
maxEndpoints = 1000
maxZoneHints = 8
)
// ValidateEndpointSliceName can be used to check whether the given endpoint
@@ -125,6 +126,10 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres
if endpoint.Hostname != nil {
allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpoint.Hostname, idxPath.Child("hostname"))...)
}
if endpoint.Hints != nil {
allErrs = append(allErrs, validateHints(endpoint.Hints, idxPath.Child("hints"))...)
}
}
return allErrs
@@ -179,3 +184,29 @@ func validateAddressType(addressType discovery.AddressType) field.ErrorList {
return allErrs
}
func validateHints(endpointHints *discovery.EndpointHints, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
fzPath := fldPath.Child("forZones")
if len(endpointHints.ForZones) > maxZoneHints {
allErrs = append(allErrs, field.TooMany(fzPath, len(endpointHints.ForZones), maxZoneHints))
return allErrs
}
zoneNames := sets.String{}
for i, forZone := range endpointHints.ForZones {
zonePath := fzPath.Index(i).Child("name")
if zoneNames.Has(forZone.Name) {
allErrs = append(allErrs, field.Duplicate(zonePath, forZone.Name))
} else {
zoneNames.Insert(forZone.Name)
}
for _, msg := range validation.IsValidLabelValue(forZone.Name) {
allErrs = append(allErrs, field.Invalid(zonePath, forZone.Name, msg))
}
}
return allErrs
}

View File

@@ -203,6 +203,23 @@ func TestValidateEndpointSlice(t *testing.T) {
}},
},
},
"valid-hints": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: discovery.AddressTypeIPv4,
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
}},
},
},
// expected failures
"duplicate-port-name": {
@@ -451,6 +468,71 @@ func TestValidateEndpointSlice(t *testing.T) {
}},
},
},
"invalid-hints": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: discovery.AddressTypeIPv4,
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "inv@lid"}},
},
}},
},
},
"overlapping-hints": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: discovery.AddressTypeIPv4,
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{
{Name: "zone-a"},
{Name: "zone-b"},
{Name: "zone-a"},
},
},
}},
},
},
"too-many-hints": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: discovery.AddressTypeIPv4,
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{
{Name: "zone-a"},
{Name: "zone-b"},
{Name: "zone-c"},
{Name: "zone-d"},
{Name: "zone-e"},
{Name: "zone-f"},
{Name: "zone-g"},
{Name: "zone-h"},
{Name: "zone-i"},
},
},
}},
},
},
"empty-everything": {
expectedErrors: 3,
endpointSlice: &discovery.EndpointSlice{},

View File

@@ -61,6 +61,11 @@ func (in *Endpoint) DeepCopyInto(out *Endpoint) {
*out = new(string)
**out = **in
}
if in.Hints != nil {
in, out := &in.Hints, &out.Hints
*out = new(EndpointHints)
(*in).DeepCopyInto(*out)
}
return
}
@@ -105,6 +110,27 @@ func (in *EndpointConditions) DeepCopy() *EndpointConditions {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointHints) DeepCopyInto(out *EndpointHints) {
*out = *in
if in.ForZones != nil {
in, out := &in.ForZones, &out.ForZones
*out = make([]ForZone, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointHints.
func (in *EndpointHints) DeepCopy() *EndpointHints {
if in == nil {
return nil
}
out := new(EndpointHints)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
*out = *in
@@ -213,3 +239,19 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object {
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForZone) DeepCopyInto(out *ForZone) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForZone.
func (in *ForZone) DeepCopy() *ForZone {
if in == nil {
return nil
}
out := new(ForZone)
in.DeepCopyInto(out)
return out
}