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
commit 207c75c6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 4418 additions and 297 deletions

View File

@ -11018,6 +11018,10 @@
"description": "deprecatedTopology contains topology information part of the v1beta1 API. This field is deprecated, and will be removed when the v1beta1 API is removed (no sooner than kubernetes v1.24). While this field can hold values, it is not writable through the v1 API, and any attempts to write to it will be silently ignored. Topology information can be found in the zone and nodeName fields instead.", "description": "deprecatedTopology contains topology information part of the v1beta1 API. This field is deprecated, and will be removed when the v1beta1 API is removed (no sooner than kubernetes v1.24). While this field can hold values, it is not writable through the v1 API, and any attempts to write to it will be silently ignored. Topology information can be found in the zone and nodeName fields instead.",
"type": "object" "type": "object"
}, },
"hints": {
"$ref": "#/definitions/io.k8s.api.discovery.v1.EndpointHints",
"description": "hints contains information associated with how an endpoint should be consumed."
},
"hostname": { "hostname": {
"description": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must be lowercase and pass DNS Label (RFC 1123) validation.", "description": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must be lowercase and pass DNS Label (RFC 1123) validation.",
"type": "string" "type": "string"
@ -11058,6 +11062,20 @@
}, },
"type": "object" "type": "object"
}, },
"io.k8s.api.discovery.v1.EndpointHints": {
"description": "EndpointHints provides hints describing how an endpoint should be consumed.",
"properties": {
"forZones": {
"description": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing.",
"items": {
"$ref": "#/definitions/io.k8s.api.discovery.v1.ForZone"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
}
},
"type": "object"
},
"io.k8s.api.discovery.v1.EndpointPort": { "io.k8s.api.discovery.v1.EndpointPort": {
"description": "EndpointPort represents a Port used by an EndpointSlice", "description": "EndpointPort represents a Port used by an EndpointSlice",
"properties": { "properties": {
@ -11165,6 +11183,19 @@
} }
] ]
}, },
"io.k8s.api.discovery.v1.ForZone": {
"description": "ForZone provides information about which zones should consume this endpoint.",
"properties": {
"name": {
"description": "name represents the name of the zone.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.discovery.v1beta1.Endpoint": { "io.k8s.api.discovery.v1beta1.Endpoint": {
"description": "Endpoint represents a single logical \"backend\" implementing a service.", "description": "Endpoint represents a single logical \"backend\" implementing a service.",
"properties": { "properties": {
@ -11180,6 +11211,10 @@
"$ref": "#/definitions/io.k8s.api.discovery.v1beta1.EndpointConditions", "$ref": "#/definitions/io.k8s.api.discovery.v1beta1.EndpointConditions",
"description": "conditions contains information about the current status of the endpoint." "description": "conditions contains information about the current status of the endpoint."
}, },
"hints": {
"$ref": "#/definitions/io.k8s.api.discovery.v1beta1.EndpointHints",
"description": "hints contains information associated with how an endpoint should be consumed."
},
"hostname": { "hostname": {
"description": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must be lowercase and pass DNS Label (RFC 1123) validation.", "description": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must be lowercase and pass DNS Label (RFC 1123) validation.",
"type": "string" "type": "string"
@ -11223,6 +11258,20 @@
}, },
"type": "object" "type": "object"
}, },
"io.k8s.api.discovery.v1beta1.EndpointHints": {
"description": "EndpointHints provides hints describing how an endpoint should be consumed.",
"properties": {
"forZones": {
"description": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing. May contain a maximum of 8 entries.",
"items": {
"$ref": "#/definitions/io.k8s.api.discovery.v1beta1.ForZone"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
}
},
"type": "object"
},
"io.k8s.api.discovery.v1beta1.EndpointPort": { "io.k8s.api.discovery.v1beta1.EndpointPort": {
"description": "EndpointPort represents a Port used by an EndpointSlice", "description": "EndpointPort represents a Port used by an EndpointSlice",
"properties": { "properties": {
@ -11330,6 +11379,19 @@
} }
] ]
}, },
"io.k8s.api.discovery.v1beta1.ForZone": {
"description": "ForZone provides information about which zones should consume this endpoint.",
"properties": {
"name": {
"description": "name represents the name of the zone.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.events.v1.Event": { "io.k8s.api.events.v1.Event": {
"description": "Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system. Events have a limited retention time and triggers and messages may evolve with time. Event consumers should not rely on the timing of an event with a given Reason reflecting a consistent underlying trigger, or the continued existence of events with that Reason. Events should be treated as informative, best-effort, supplemental data.", "description": "Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system. Events have a limited retention time and triggers and messages may evolve with time. Event consumers should not rely on the timing of an event with a given Reason reflecting a consistent underlying trigger, or the continued existence of events with that Reason. Events should be treated as informative, best-effort, supplemental data.",
"properties": { "properties": {

View File

@ -750,7 +750,7 @@ func (s *ProxyServer) Run() error {
// functions must configure their shared informer event handlers first. // functions must configure their shared informer event handlers first.
informerFactory.Start(wait.NeverStop) informerFactory.Start(wait.NeverStop)
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) { if utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) || utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) {
// Make an informer that selects for our nodename. // Make an informer that selects for our nodename.
currentNodeInformerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.ConfigSyncPeriod, currentNodeInformerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.ConfigSyncPeriod,
informers.WithTweakListOptions(func(options *metav1.ListOptions) { informers.WithTweakListOptions(func(options *metav1.ListOptions) {

View File

@ -116,4 +116,9 @@ const (
// //
// This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled. // This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled.
PodDeletionCost = "controller.kubernetes.io/pod-deletion-cost" 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. // zone is the name of the Zone this endpoint exists in.
// +optional // +optional
Zone *string 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. // EndpointConditions represents the current condition of an endpoint.
@ -127,6 +132,19 @@ type EndpointConditions struct {
Terminating *bool 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. // EndpointPort represents a Port used by an EndpointSlice.
type EndpointPort struct { type EndpointPort struct {
// The name of this port. All ports in an EndpointSlice must have a unique // 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 { }); err != nil {
return err 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 { 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) return Convert_v1_EndpointPort_To_discovery_EndpointPort(a.(*v1.EndpointPort), b.(*discovery.EndpointPort), scope)
}); err != nil { }); err != nil {
@ -88,6 +98,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err 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 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.DeprecatedTopology = *(*map[string]string)(unsafe.Pointer(&in.DeprecatedTopology))
out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Zone = (*string)(unsafe.Pointer(in.Zone)) out.Zone = (*string)(unsafe.Pointer(in.Zone))
out.Hints = (*discovery.EndpointHints)(unsafe.Pointer(in.Hints))
return nil 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.DeprecatedTopology = *(*map[string]string)(unsafe.Pointer(&in.DeprecatedTopology))
out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Zone = (*string)(unsafe.Pointer(in.Zone)) out.Zone = (*string)(unsafe.Pointer(in.Zone))
out.Hints = (*v1.EndpointHints)(unsafe.Pointer(in.Hints))
return nil 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) 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 { 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.Name = (*string)(unsafe.Pointer(in.Name))
out.Protocol = (*core.Protocol)(unsafe.Pointer(in.Protocol)) 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 { 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) 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 { }); err != nil {
return err 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 { 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) return Convert_v1beta1_EndpointPort_To_discovery_EndpointPort(a.(*v1beta1.EndpointPort), b.(*discovery.EndpointPort), scope)
}); err != nil { }); err != nil {
@ -78,6 +88,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err 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 { 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) return Convert_discovery_Endpoint_To_v1beta1_Endpoint(a.(*discovery.Endpoint), b.(*v1beta1.Endpoint), scope)
}); err != nil { }); 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)) out.TargetRef = (*core.ObjectReference)(unsafe.Pointer(in.TargetRef))
// WARNING: in.Topology requires manual conversion: does not exist in peer-type // WARNING: in.Topology requires manual conversion: does not exist in peer-type
out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
out.Hints = (*discovery.EndpointHints)(unsafe.Pointer(in.Hints))
return nil 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 // WARNING: in.DeprecatedTopology requires manual conversion: does not exist in peer-type
out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) out.NodeName = (*string)(unsafe.Pointer(in.NodeName))
// WARNING: in.Zone requires manual conversion: does not exist in peer-type // WARNING: in.Zone requires manual conversion: does not exist in peer-type
out.Hints = (*v1beta1.EndpointHints)(unsafe.Pointer(in.Hints))
return nil 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) 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 { 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.Name = (*string)(unsafe.Pointer(in.Name))
out.Protocol = (*core.Protocol)(unsafe.Pointer(in.Protocol)) 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 { 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) 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 maxAddresses = 100
maxPorts = 20000 maxPorts = 20000
maxEndpoints = 1000 maxEndpoints = 1000
maxZoneHints = 8
) )
// ValidateEndpointSliceName can be used to check whether the given endpoint // 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 { if endpoint.Hostname != nil {
allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpoint.Hostname, idxPath.Child("hostname"))...) 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 return allErrs
@ -179,3 +184,29 @@ func validateAddressType(addressType discovery.AddressType) field.ErrorList {
return allErrs 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 // expected failures
"duplicate-port-name": { "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": { "empty-everything": {
expectedErrors: 3, expectedErrors: 3,
endpointSlice: &discovery.EndpointSlice{}, endpointSlice: &discovery.EndpointSlice{},

View File

@ -61,6 +61,11 @@ func (in *Endpoint) DeepCopyInto(out *Endpoint) {
*out = new(string) *out = new(string)
**out = **in **out = **in
} }
if in.Hints != nil {
in, out := &in.Hints, &out.Hints
*out = new(EndpointHints)
(*in).DeepCopyInto(*out)
}
return return
} }
@ -105,6 +110,27 @@ func (in *EndpointConditions) DeepCopy() *EndpointConditions {
return out 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) { func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
*out = *in *out = *in
@ -213,3 +239,19 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object {
} }
return nil 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
}

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
coreinformers "k8s.io/client-go/informers/core/v1" coreinformers "k8s.io/client-go/informers/core/v1"
discoveryinformers "k8s.io/client-go/informers/discovery/v1" discoveryinformers "k8s.io/client-go/informers/discovery/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
@ -42,7 +43,9 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
endpointslicemetrics "k8s.io/kubernetes/pkg/controller/endpointslice/metrics" endpointslicemetrics "k8s.io/kubernetes/pkg/controller/endpointslice/metrics"
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features"
) )
const ( const (
@ -142,13 +145,6 @@ func NewController(podInformer coreinformers.PodInformer,
c.maxEndpointsPerSlice = maxEndpointsPerSlice c.maxEndpointsPerSlice = maxEndpointsPerSlice
c.reconciler = &reconciler{
client: c.client,
nodeLister: c.nodeLister,
maxEndpointsPerSlice: c.maxEndpointsPerSlice,
endpointSliceTracker: c.endpointSliceTracker,
metricsCache: endpointslicemetrics.NewCache(maxEndpointsPerSlice),
}
c.triggerTimeTracker = endpointutil.NewTriggerTimeTracker() c.triggerTimeTracker = endpointutil.NewTriggerTimeTracker()
c.eventBroadcaster = broadcaster c.eventBroadcaster = broadcaster
@ -157,6 +153,25 @@ func NewController(podInformer coreinformers.PodInformer,
c.endpointUpdatesBatchPeriod = endpointUpdatesBatchPeriod c.endpointUpdatesBatchPeriod = endpointUpdatesBatchPeriod
c.serviceSelectorCache = endpointutil.NewServiceSelectorCache() c.serviceSelectorCache = endpointutil.NewServiceSelectorCache()
if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) {
nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.addNode,
UpdateFunc: c.updateNode,
DeleteFunc: c.deleteNode,
})
c.topologyCache = topologycache.NewTopologyCache()
}
c.reconciler = &reconciler{
client: c.client,
nodeLister: c.nodeLister,
maxEndpointsPerSlice: c.maxEndpointsPerSlice,
endpointSliceTracker: c.endpointSliceTracker,
metricsCache: endpointslicemetrics.NewCache(maxEndpointsPerSlice),
topologyCache: c.topologyCache,
}
return c return c
} }
@ -227,6 +242,10 @@ type Controller struct {
// serviceSelectorCache is a cache of service selectors to avoid high CPU consumption caused by frequent calls // serviceSelectorCache is a cache of service selectors to avoid high CPU consumption caused by frequent calls
// to AsSelectorPreValidated (see #73527) // to AsSelectorPreValidated (see #73527)
serviceSelectorCache *endpointutil.ServiceSelectorCache serviceSelectorCache *endpointutil.ServiceSelectorCache
// topologyCache tracks the distribution of Nodes and endpoints across zones
// to enable TopologyAwareHints.
topologyCache *topologycache.TopologyCache
} }
// Run will not return until stopCh is closed. // Run will not return until stopCh is closed.
@ -275,6 +294,8 @@ func (c *Controller) processNextWorkItem() bool {
} }
func (c *Controller) handleErr(err error, key interface{}) { func (c *Controller) handleErr(err error, key interface{}) {
trackSync(err)
if err == nil { if err == nil {
c.queue.Forget(key) c.queue.Forget(key)
return return
@ -490,3 +511,50 @@ func (c *Controller) deletePod(obj interface{}) {
c.addPod(pod) c.addPod(pod)
} }
} }
func (c *Controller) addNode(obj interface{}) {
c.checkNodeTopologyDistribution()
}
func (c *Controller) updateNode(old, cur interface{}) {
oldNode := old.(*v1.Node)
curNode := cur.(*v1.Node)
if topologycache.NodeReady(oldNode.Status) != topologycache.NodeReady(curNode.Status) {
c.checkNodeTopologyDistribution()
}
}
func (c *Controller) deleteNode(obj interface{}) {
c.checkNodeTopologyDistribution()
}
// checkNodeTopologyDistribution updates Nodes in the topology cache and then
// queues any Services that are past the threshold.
func (c *Controller) checkNodeTopologyDistribution() {
if c.topologyCache == nil {
return
}
nodes, err := c.nodeLister.List(labels.Everything())
if err != nil {
klog.Errorf("Error listing Nodes: %v", err)
}
c.topologyCache.SetNodes(nodes)
serviceKeys := c.topologyCache.GetOverloadedServices()
for _, serviceKey := range serviceKeys {
c.queue.Add(serviceKey)
}
}
// trackSync increments the EndpointSliceSyncs metric with the result of a sync.
func trackSync(err error) {
metricLabel := "success"
if err != nil {
if isStaleInformerCacheErr(err) {
metricLabel = "stale"
} else {
metricLabel = "error"
}
}
endpointslicemetrics.EndpointSliceSyncs.WithLabelValues(metricLabel).Inc()
}

View File

@ -27,6 +27,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "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/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -40,6 +41,7 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
featuregatetesting "k8s.io/component-base/featuregate/testing" featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
@ -1501,6 +1503,183 @@ func TestSyncServiceStaleInformer(t *testing.T) {
} }
} }
func Test_checkNodeTopologyDistribution(t *testing.T) {
zoneA := "zone-a"
zoneB := "zone-b"
zoneC := "zone-c"
readyTrue := true
readyFalse := false
cpu100 := resource.MustParse("100m")
cpu1000 := resource.MustParse("1000m")
cpu2000 := resource.MustParse("2000m")
type nodeInfo struct {
zoneLabel *string
ready *bool
cpu *resource.Quantity
}
testCases := []struct {
name string
nodes []nodeInfo
topologyCacheEnabled bool
endpointZoneInfo map[string]topologycache.EndpointZoneInfo
expectedQueueLen int
}{{
name: "empty",
nodes: []nodeInfo{},
topologyCacheEnabled: false,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{},
expectedQueueLen: 0,
}, {
name: "lopsided, queue required",
nodes: []nodeInfo{
{zoneLabel: &zoneA, ready: &readyTrue, cpu: &cpu100},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu1000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneA: 1, zoneB: 2, zoneC: 3},
},
expectedQueueLen: 1,
}, {
name: "lopsided but 1 unready, queue required because unready node means 0 CPU in one zone",
nodes: []nodeInfo{
{zoneLabel: &zoneA, ready: &readyFalse, cpu: &cpu100},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu1000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneA: 1, zoneB: 2, zoneC: 3},
},
expectedQueueLen: 1,
}, {
name: "even zones, uneven endpoint distribution but within threshold, no sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneB: 5, zoneC: 4},
},
expectedQueueLen: 0,
}, {
name: "even zones but node missing zone, sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneB: 5, zoneC: 4},
},
expectedQueueLen: 1,
}, {
name: "even zones but node missing cpu, sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneB, ready: &readyTrue},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneB: 5, zoneC: 4},
},
expectedQueueLen: 1,
}, {
name: "even zones, uneven endpoint distribution beyond threshold, no sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu2000},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneB: 6, zoneC: 4},
},
expectedQueueLen: 1,
}, {
name: "3 uneven zones, matching endpoint distribution, no sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneA, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu1000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu100},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneA: 20, zoneB: 10, zoneC: 1},
},
expectedQueueLen: 0,
}, {
name: "3 uneven zones, endpoint distribution within threshold but below 1, sync required",
nodes: []nodeInfo{
{zoneLabel: &zoneA, ready: &readyTrue, cpu: &cpu2000},
{zoneLabel: &zoneB, ready: &readyTrue, cpu: &cpu1000},
{zoneLabel: &zoneC, ready: &readyTrue, cpu: &cpu100},
},
topologyCacheEnabled: true,
endpointZoneInfo: map[string]topologycache.EndpointZoneInfo{
"ns/svc1": {zoneA: 20, zoneB: 10, zoneC: 0},
},
expectedQueueLen: 1,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, esController := newController([]string{}, time.Duration(0))
for i, nodeInfo := range tc.nodes {
node := &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("node-%d", i)},
Status: v1.NodeStatus{},
}
if nodeInfo.zoneLabel != nil {
node.Labels = map[string]string{v1.LabelTopologyZone: *nodeInfo.zoneLabel}
}
if nodeInfo.ready != nil {
status := v1.ConditionFalse
if *nodeInfo.ready {
status = v1.ConditionTrue
}
node.Status.Conditions = []v1.NodeCondition{{
Type: v1.NodeReady,
Status: status,
}}
}
if nodeInfo.cpu != nil {
node.Status.Allocatable = v1.ResourceList{
v1.ResourceCPU: *nodeInfo.cpu,
}
}
esController.nodeStore.Add(node)
if tc.topologyCacheEnabled {
esController.topologyCache = topologycache.NewTopologyCache()
for serviceKey, endpointZoneInfo := range tc.endpointZoneInfo {
esController.topologyCache.SetHints(serviceKey, discovery.AddressTypeIPv4, endpointZoneInfo)
}
}
}
esController.checkNodeTopologyDistribution()
if esController.queue.Len() != tc.expectedQueueLen {
t.Errorf("Expected %d services to be queued, got %d", tc.expectedQueueLen, esController.queue.Len())
}
})
}
}
// Test helpers // Test helpers
func addPods(t *testing.T, esController *endpointSliceController, namespace string, podsCount int) { func addPods(t *testing.T, esController *endpointSliceController, namespace string, podsCount int) {
t.Helper() t.Helper()

View File

@ -93,6 +93,29 @@ var (
}, },
[]string{"operation"}, []string{"operation"},
) )
// EndpointSlicesChangedPerSync observes the number of EndpointSlices
// changed per sync.
EndpointSlicesChangedPerSync = metrics.NewHistogramVec(
&metrics.HistogramOpts{
Subsystem: EndpointSliceSubsystem,
Name: "endpointslices_changed_per_sync",
Help: "Number of EndpointSlices changed on each Service sync",
},
[]string{"topology"}, // either "auto" or "disabled"
)
// EndpointSliceSyncs tracks the number of sync operations the controller
// runs along with their result.
EndpointSliceSyncs = metrics.NewCounterVec(
&metrics.CounterOpts{
Subsystem: EndpointSliceSubsystem,
Name: "syncs",
Help: "Number of EndpointSlice syncs",
StabilityLevel: metrics.ALPHA,
},
[]string{"result"}, // either "success", "stale", or "error"
)
) )
var registerMetrics sync.Once var registerMetrics sync.Once
@ -106,5 +129,7 @@ func RegisterMetrics() {
legacyregistry.MustRegister(NumEndpointSlices) legacyregistry.MustRegister(NumEndpointSlices)
legacyregistry.MustRegister(DesiredEndpointSlices) legacyregistry.MustRegister(DesiredEndpointSlices)
legacyregistry.MustRegister(EndpointSliceChanges) legacyregistry.MustRegister(EndpointSliceChanges)
legacyregistry.MustRegister(EndpointSlicesChangedPerSync)
legacyregistry.MustRegister(EndpointSliceSyncs)
}) })
} }

View File

@ -32,7 +32,9 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/controller/endpointslice/metrics" "k8s.io/kubernetes/pkg/controller/endpointslice/metrics"
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
) )
@ -45,6 +47,9 @@ type reconciler struct {
maxEndpointsPerSlice int32 maxEndpointsPerSlice int32
endpointSliceTracker *endpointSliceTracker endpointSliceTracker *endpointSliceTracker
metricsCache *metrics.Cache metricsCache *metrics.Cache
// topologyCache tracks the distribution of Nodes and endpoints across zones
// to enable TopologyAwareHints.
topologyCache *topologycache.TopologyCache
} }
// endpointMeta includes the attributes we group slices on, this type helps with // endpointMeta includes the attributes we group slices on, this type helps with
@ -73,6 +78,15 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis
for _, existingSlice := range existingSlices { for _, existingSlice := range existingSlices {
// service no longer supports that address type, add it to deleted slices // service no longer supports that address type, add it to deleted slices
if _, ok := serviceSupportedAddressesTypes[existingSlice.AddressType]; !ok { if _, ok := serviceSupportedAddressesTypes[existingSlice.AddressType]; !ok {
if r.topologyCache != nil {
svcKey, err := serviceControllerKey(existingSlice)
if err != nil {
klog.Warningf("Couldn't get key to remove EndpointSlice from topology cache %+v: %v", existingSlice, err)
} else {
r.topologyCache.RemoveHints(svcKey, existingSlice.AddressType)
}
}
slicesToDelete = append(slicesToDelete, existingSlice) slicesToDelete = append(slicesToDelete, existingSlice)
continue continue
} }
@ -222,6 +236,25 @@ func (r *reconciler) reconcileByAddressType(service *corev1.Service, pods []*cor
serviceNN := types.NamespacedName{Name: service.Name, Namespace: service.Namespace} serviceNN := types.NamespacedName{Name: service.Name, Namespace: service.Namespace}
r.metricsCache.UpdateServicePortCache(serviceNN, spMetrics) r.metricsCache.UpdateServicePortCache(serviceNN, spMetrics)
// Topology hints are assigned per address type. This means it is
// theoretically possible for endpoints of one address type to be assigned
// hints while another endpoints of another address type are not.
si := &topologycache.SliceInfo{
ServiceKey: fmt.Sprintf("%s/%s", service.Namespace, service.Name),
ToCreate: slicesToCreate,
ToUpdate: slicesToUpdate,
Unchanged: unchangedSlices(existingSlices, slicesToUpdate, slicesToDelete),
}
if r.topologyCache != nil && hintsEnabled(service.Annotations) {
slicesToCreate, slicesToUpdate = r.topologyCache.AddHints(si)
} else {
if r.topologyCache != nil {
r.topologyCache.RemoveHints(si.ServiceKey, addressType)
}
slicesToCreate, slicesToUpdate = topologycache.RemoveHintsFromSlices(si)
}
return r.finalize(service, slicesToCreate, slicesToUpdate, slicesToDelete, triggerTime) return r.finalize(service, slicesToCreate, slicesToUpdate, slicesToDelete, triggerTime)
} }
@ -297,6 +330,14 @@ func (r *reconciler) finalize(
metrics.EndpointSliceChanges.WithLabelValues("delete").Inc() metrics.EndpointSliceChanges.WithLabelValues("delete").Inc()
} }
topologyLabel := "disabled"
if r.topologyCache != nil && hintsEnabled(service.Annotations) {
topologyLabel = "auto"
}
numSlicesChanged := len(slicesToCreate) + len(slicesToUpdate) + len(slicesToDelete)
metrics.EndpointSlicesChangedPerSync.WithLabelValues(topologyLabel).Observe(float64(numSlicesChanged))
return nil return nil
} }

View File

@ -29,6 +29,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1" discovery "k8s.io/api/discovery/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
@ -41,6 +42,7 @@ import (
"k8s.io/component-base/metrics/testutil" "k8s.io/component-base/metrics/testutil"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/endpointslice/metrics" "k8s.io/kubernetes/pkg/controller/endpointslice/metrics"
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -66,7 +68,7 @@ func TestReconcileEmpty(t *testing.T) {
assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports) assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports)
assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints) assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints)
expectTrackedGeneration(t, r.endpointSliceTracker, &slices[0], 1) expectTrackedGeneration(t, r.endpointSliceTracker, &slices[0], 1)
expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 0, addedPerSync: 0, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 0, addedPerSync: 0, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0, slicesChangedPerSync: 1})
} }
// Given a single pod matching a service selector and no existing endpoint slices, // Given a single pod matching a service selector and no existing endpoint slices,
@ -436,6 +438,10 @@ func TestReconcile1Pod(t *testing.T) {
expectTrackedGeneration(t, r.endpointSliceTracker, &slice, 1) expectTrackedGeneration(t, r.endpointSliceTracker, &slice, 1)
expectSlicesChangedPerSync := 1
if testCase.service.Spec.IPFamilies != nil && len(testCase.service.Spec.IPFamilies) > 0 {
expectSlicesChangedPerSync = len(testCase.service.Spec.IPFamilies)
}
expectMetrics(t, expectMetrics(t,
expectedMetrics{ expectedMetrics{
desiredSlices: 1, desiredSlices: 1,
@ -445,7 +451,9 @@ func TestReconcile1Pod(t *testing.T) {
removedPerSync: 0, removedPerSync: 0,
numCreated: len(testCase.expectedEndpointPerSlice), numCreated: len(testCase.expectedEndpointPerSlice),
numUpdated: 0, numUpdated: 0,
numDeleted: 0}) numDeleted: 0,
slicesChangedPerSync: expectSlicesChangedPerSync,
})
} }
}) })
} }
@ -478,7 +486,7 @@ func TestReconcile1EndpointSlice(t *testing.T) {
assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports) assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports)
assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints) assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints)
expectTrackedGeneration(t, r.endpointSliceTracker, &slices[0], 1) expectTrackedGeneration(t, r.endpointSliceTracker, &slices[0], 1)
expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 0, addedPerSync: 0, removedPerSync: 0, numCreated: 0, numUpdated: 1, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 0, addedPerSync: 0, removedPerSync: 0, numCreated: 0, numUpdated: 1, numDeleted: 0, slicesChangedPerSync: 1})
} }
// when a Service has PublishNotReadyAddresses set to true, corresponding // when a Service has PublishNotReadyAddresses set to true, corresponding
@ -539,7 +547,7 @@ func TestReconcileManyPods(t *testing.T) {
// Two endpoint slices should be completely full, the remainder should be in another one // Two endpoint slices should be completely full, the remainder should be in another one
expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{100, 100, 50}) expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{100, 100, 50})
expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 250, addedPerSync: 250, removedPerSync: 0, numCreated: 3, numUpdated: 0, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 250, addedPerSync: 250, removedPerSync: 0, numCreated: 3, numUpdated: 0, numDeleted: 0, slicesChangedPerSync: 3})
} }
// now with preexisting slices, we have 250 pods matching a service // now with preexisting slices, we have 250 pods matching a service
@ -590,7 +598,7 @@ func TestReconcileEndpointSlicesSomePreexisting(t *testing.T) {
// 1 new slice (0->100) + 1 updated slice (62->89) // 1 new slice (0->100) + 1 updated slice (62->89)
expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{89, 61, 100}) expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{89, 61, 100})
expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 250, addedPerSync: 127, removedPerSync: 0, numCreated: 1, numUpdated: 1, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 250, addedPerSync: 127, removedPerSync: 0, numCreated: 1, numUpdated: 1, numDeleted: 0, slicesChangedPerSync: 2})
// ensure cache mutation has not occurred // ensure cache mutation has not occurred
cmc.Check(t) cmc.Check(t)
@ -645,7 +653,7 @@ func TestReconcileEndpointSlicesSomePreexistingWorseAllocation(t *testing.T) {
// 2 new slices (100, 52) in addition to existing slices (74, 74) // 2 new slices (100, 52) in addition to existing slices (74, 74)
expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{74, 74, 100, 52}) expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{74, 74, 100, 52})
expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 4, desiredEndpoints: 300, addedPerSync: 152, removedPerSync: 0, numCreated: 2, numUpdated: 0, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 4, desiredEndpoints: 300, addedPerSync: 152, removedPerSync: 0, numCreated: 2, numUpdated: 0, numDeleted: 0, slicesChangedPerSync: 2})
// ensure cache mutation has not occurred // ensure cache mutation has not occurred
cmc.Check(t) cmc.Check(t)
@ -804,7 +812,7 @@ func TestReconcileEndpointSlicesRecycling(t *testing.T) {
// thanks to recycling, we get a free repack of endpoints, resulting in 3 full slices instead of 10 mostly empty slices // thanks to recycling, we get a free repack of endpoints, resulting in 3 full slices instead of 10 mostly empty slices
expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{100, 100, 100}) expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{100, 100, 100})
expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 300, addedPerSync: 300, removedPerSync: 0, numCreated: 0, numUpdated: 3, numDeleted: 7}) expectMetrics(t, expectedMetrics{desiredSlices: 3, actualSlices: 3, desiredEndpoints: 300, addedPerSync: 300, removedPerSync: 0, numCreated: 0, numUpdated: 3, numDeleted: 7, slicesChangedPerSync: 10})
// ensure cache mutation has not occurred // ensure cache mutation has not occurred
cmc.Check(t) cmc.Check(t)
@ -861,7 +869,7 @@ func TestReconcileEndpointSlicesUpdatePacking(t *testing.T) {
// ensure that both endpoint slices have been updated // ensure that both endpoint slices have been updated
expectActions(t, client.Actions(), 2, "update", "endpointslices") expectActions(t, client.Actions(), 2, "update", "endpointslices")
expectMetrics(t, expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 115, addedPerSync: 15, removedPerSync: 0, numCreated: 0, numUpdated: 2, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 115, addedPerSync: 15, removedPerSync: 0, numCreated: 0, numUpdated: 2, numDeleted: 0, slicesChangedPerSync: 2})
// additional pods should get added to fuller slice // additional pods should get added to fuller slice
expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{95, 20}) expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{95, 20})
@ -1036,7 +1044,7 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
// reconcile should create 5 endpoint slices // reconcile should create 5 endpoint slices
assert.Equal(t, 5, len(client.Actions()), "Expected 5 client actions as part of reconcile") assert.Equal(t, 5, len(client.Actions()), "Expected 5 client actions as part of reconcile")
expectActions(t, client.Actions(), 5, "create", "endpointslices") expectActions(t, client.Actions(), 5, "create", "endpointslices")
expectMetrics(t, expectedMetrics{desiredSlices: 5, actualSlices: 5, desiredEndpoints: 300, addedPerSync: 300, removedPerSync: 0, numCreated: 5, numUpdated: 0, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 5, actualSlices: 5, desiredEndpoints: 300, addedPerSync: 300, removedPerSync: 0, numCreated: 5, numUpdated: 0, numDeleted: 0, slicesChangedPerSync: 5})
fetchedSlices := fetchEndpointSlices(t, client, namespace) fetchedSlices := fetchEndpointSlices(t, client, namespace)
@ -1082,23 +1090,23 @@ func TestReconcileMaxEndpointsPerSlice(t *testing.T) {
{ {
maxEndpointsPerSlice: int32(50), maxEndpointsPerSlice: int32(50),
expectedSliceLengths: []int{50, 50, 50, 50, 50}, expectedSliceLengths: []int{50, 50, 50, 50, 50},
expectedMetricValues: expectedMetrics{desiredSlices: 5, actualSlices: 5, desiredEndpoints: 250, addedPerSync: 250, numCreated: 5}, expectedMetricValues: expectedMetrics{desiredSlices: 5, actualSlices: 5, desiredEndpoints: 250, addedPerSync: 250, numCreated: 5, slicesChangedPerSync: 5},
}, { }, {
maxEndpointsPerSlice: int32(80), maxEndpointsPerSlice: int32(80),
expectedSliceLengths: []int{80, 80, 80, 10}, expectedSliceLengths: []int{80, 80, 80, 10},
expectedMetricValues: expectedMetrics{desiredSlices: 4, actualSlices: 4, desiredEndpoints: 250, addedPerSync: 250, numCreated: 4}, expectedMetricValues: expectedMetrics{desiredSlices: 4, actualSlices: 4, desiredEndpoints: 250, addedPerSync: 250, numCreated: 4, slicesChangedPerSync: 4},
}, { }, {
maxEndpointsPerSlice: int32(150), maxEndpointsPerSlice: int32(150),
expectedSliceLengths: []int{150, 100}, expectedSliceLengths: []int{150, 100},
expectedMetricValues: expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 250, addedPerSync: 250, numCreated: 2}, expectedMetricValues: expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 250, addedPerSync: 250, numCreated: 2, slicesChangedPerSync: 2},
}, { }, {
maxEndpointsPerSlice: int32(250), maxEndpointsPerSlice: int32(250),
expectedSliceLengths: []int{250}, expectedSliceLengths: []int{250},
expectedMetricValues: expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 250, addedPerSync: 250, numCreated: 1}, expectedMetricValues: expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 250, addedPerSync: 250, numCreated: 1, slicesChangedPerSync: 1},
}, { }, {
maxEndpointsPerSlice: int32(500), maxEndpointsPerSlice: int32(500),
expectedSliceLengths: []int{250}, expectedSliceLengths: []int{250},
expectedMetricValues: expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 250, addedPerSync: 250, numCreated: 1}, expectedMetricValues: expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 250, addedPerSync: 250, numCreated: 1, slicesChangedPerSync: 1},
}, },
} }
@ -1133,11 +1141,11 @@ func TestReconcileEndpointSlicesMetrics(t *testing.T) {
assert.Equal(t, 1, len(actions), "Expected 1 additional client actions as part of reconcile") assert.Equal(t, 1, len(actions), "Expected 1 additional client actions as part of reconcile")
assert.True(t, actions[0].Matches("create", "endpointslices"), "First action should be create endpoint slice") assert.True(t, actions[0].Matches("create", "endpointslices"), "First action should be create endpoint slice")
expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 20, addedPerSync: 20, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 20, addedPerSync: 20, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0, slicesChangedPerSync: 1})
fetchedSlices := fetchEndpointSlices(t, client, namespace) fetchedSlices := fetchEndpointSlices(t, client, namespace)
reconcileHelper(t, r, &svc, pods[0:10], []*discovery.EndpointSlice{&fetchedSlices[0]}, time.Now()) reconcileHelper(t, r, &svc, pods[0:10], []*discovery.EndpointSlice{&fetchedSlices[0]}, time.Now())
expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 10, addedPerSync: 20, removedPerSync: 10, numCreated: 1, numUpdated: 1, numDeleted: 0}) expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 10, addedPerSync: 20, removedPerSync: 10, numCreated: 1, numUpdated: 1, numDeleted: 0, slicesChangedPerSync: 2})
} }
// When a Service has a non-nil deletionTimestamp we want to avoid creating any // When a Service has a non-nil deletionTimestamp we want to avoid creating any
@ -1310,6 +1318,271 @@ func TestReconcilerFinalizeSvcDeletionTimestamp(t *testing.T) {
} }
} }
func TestReconcileTopology(t *testing.T) {
ns := "testing"
svc, endpointMeta := newServiceAndEndpointMeta("foo", ns)
// 3 zones, 10 nodes and pods per zone
zones := []string{"zone-a", "zone-b", "zone-c"}
pods := []*corev1.Pod{}
nodes := []*corev1.Node{}
nodesByName := map[string]*corev1.Node{}
for i, zone := range zones {
for j := 0; j < 10; j++ {
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("node-%s-%d", zone, j),
Labels: map[string]string{
corev1.LabelTopologyZone: zone,
},
},
Status: corev1.NodeStatus{
Conditions: []corev1.NodeCondition{{
Type: corev1.NodeReady,
Status: corev1.ConditionTrue,
}},
Allocatable: corev1.ResourceList{"cpu": resource.MustParse("100m")},
},
}
nodesByName[node.Name] = node
nodes = append(nodes, node)
pod := newPod(i*100+j, ns, true, 1, false)
pod.Spec.NodeName = node.Name
pods = append(pods, pod)
}
}
slicesByName := map[string]*discovery.EndpointSlice{}
slicePods := map[string][]*corev1.Pod{
"zone-a-b": {pods[7], pods[8], pods[16], pods[17], pods[18]},
"zone-a-c": {pods[5], pods[6], pods[25], pods[26]},
"zone-c": {pods[27], pods[28], pods[29]},
}
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Service"}
ownerRef := metav1.NewControllerRef(&svc, gvk)
for name, pods := range slicePods {
endpoints := []discovery.Endpoint{}
for _, pod := range pods {
endpoints = append(endpoints, podToEndpoint(pod, nodesByName[pod.Spec.NodeName], &svc, endpointMeta.AddressType))
}
slicesByName[name] = &discovery.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Name: name,
OwnerReferences: []metav1.OwnerReference{*ownerRef},
Labels: map[string]string{
discovery.LabelManagedBy: controllerName,
discovery.LabelServiceName: svc.Name,
},
},
AddressType: endpointMeta.AddressType,
Ports: endpointMeta.Ports,
Endpoints: endpoints,
}
}
testCases := []struct {
name string
topologyCacheEnabled bool
hintsAnnotation string
existingSlices []*discovery.EndpointSlice
pods []*corev1.Pod
nodes []*corev1.Node
expectedHints map[string]int
expectedCrossZoneHints int
expectedMetrics expectedMetrics
}{{
name: "no change, topologyCache disabled, annotation == auto",
topologyCacheEnabled: false,
hintsAnnotation: "auto",
existingSlices: []*discovery.EndpointSlice{slicesByName["zone-c"]},
pods: slicePods["zone-c"],
nodes: nodes,
expectedHints: nil,
expectedCrossZoneHints: 0,
expectedMetrics: expectedMetrics{
desiredSlices: 1,
actualSlices: 1,
desiredEndpoints: 3,
addedPerSync: 0,
removedPerSync: 0,
numCreated: 0,
numUpdated: 0,
numDeleted: 0,
slicesChangedPerSync: 0,
},
}, {
name: "enabling topologyCache, hintsAnnotation == auto",
topologyCacheEnabled: true,
hintsAnnotation: "auto",
existingSlices: []*discovery.EndpointSlice{slicesByName["zone-c"]},
pods: slicePods["zone-c"],
nodes: nodes,
expectedHints: map[string]int{
"zone-a": 1,
"zone-b": 1,
"zone-c": 1,
},
expectedCrossZoneHints: 2,
expectedMetrics: expectedMetrics{
desiredSlices: 1,
actualSlices: 1,
desiredEndpoints: 3,
addedPerSync: 0,
removedPerSync: 0,
numCreated: 0,
numUpdated: 1,
numDeleted: 0,
slicesChangedPerSyncTopology: 1,
},
}, {
name: "topology enabled, hintsAnnotation==auto, ratio beyond threshold",
topologyCacheEnabled: true,
hintsAnnotation: "auto",
existingSlices: []*discovery.EndpointSlice{slicesByName["zone-a-c"]},
pods: slicePods["zone-a-c"],
nodes: nodes,
expectedHints: nil,
expectedCrossZoneHints: 0,
expectedMetrics: expectedMetrics{
desiredSlices: 1,
actualSlices: 1,
desiredEndpoints: 4,
addedPerSync: 0,
removedPerSync: 0,
numCreated: 0,
numUpdated: 0,
numDeleted: 0,
slicesChangedPerSyncTopology: 0,
},
}, {
name: "topology enabled, hintsAnnotation==auto, more slices and endpoints",
topologyCacheEnabled: true,
hintsAnnotation: "auto",
existingSlices: []*discovery.EndpointSlice{slicesByName["zone-a-c"], slicesByName["zone-a-b"]},
pods: append(slicePods["zone-a-c"], slicePods["zone-a-b"]...),
nodes: nodes,
expectedHints: map[string]int{
"zone-a": 3,
"zone-b": 3,
"zone-c": 3,
},
expectedCrossZoneHints: 1,
expectedMetrics: expectedMetrics{
desiredSlices: 1,
actualSlices: 1,
desiredEndpoints: 9,
addedPerSync: 0,
removedPerSync: 0,
numCreated: 0,
// TODO(robscott): Since we're potentially changing more slices when
// adding topology hints we could use it as a free repacking
// opportunity. That would make this value 1.
numUpdated: 2,
numDeleted: 0,
slicesChangedPerSyncTopology: 2,
},
}, {
name: "topology enabled, hintsAnnotation==disabled, more slices and endpoints",
topologyCacheEnabled: true,
hintsAnnotation: "disabled",
existingSlices: []*discovery.EndpointSlice{slicesByName["zone-a-c"], slicesByName["zone-a-b"]},
pods: append(slicePods["zone-a-c"], slicePods["zone-a-b"]...),
nodes: nodes,
expectedHints: nil,
expectedCrossZoneHints: 0,
expectedMetrics: expectedMetrics{
desiredSlices: 1,
actualSlices: 1,
desiredEndpoints: 9,
addedPerSync: 0,
removedPerSync: 0,
numCreated: 0,
numUpdated: 0,
numDeleted: 0,
slicesChangedPerSync: 0,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := newClientset()
cmc := newCacheMutationCheck(tc.existingSlices)
createEndpointSlices(t, client, ns, tc.existingSlices)
setupMetrics()
r := newReconciler(client, tc.nodes, defaultMaxEndpointsPerSlice)
if tc.topologyCacheEnabled {
r.topologyCache = topologycache.NewTopologyCache()
r.topologyCache.SetNodes(tc.nodes)
}
service := svc.DeepCopy()
service.Annotations = map[string]string{
corev1.AnnotationTopologyAwareHints: tc.hintsAnnotation,
}
r.reconcile(service, tc.pods, tc.existingSlices, time.Now())
cmc.Check(t)
expectMetrics(t, tc.expectedMetrics)
fetchedSlices := fetchEndpointSlices(t, client, ns)
if tc.expectedHints == nil {
for _, slice := range fetchedSlices {
for _, endpoint := range slice.Endpoints {
if endpoint.Hints != nil && len(endpoint.Hints.ForZones) > 0 {
t.Fatalf("Expected endpoint not to have zone hints: %+v", endpoint)
}
}
}
return
}
actualCrossZoneHints := 0
actualHints := map[string]int{}
for _, slice := range fetchedSlices {
for _, endpoint := range slice.Endpoints {
if endpoint.Hints == nil || len(endpoint.Hints.ForZones) == 0 {
t.Fatalf("Expected endpoint to have zone hints: %+v", endpoint)
}
if len(endpoint.Hints.ForZones) > 1 {
t.Fatalf("Expected endpoint to only have 1 zone hint, got %d", len(endpoint.Hints.ForZones))
}
if endpoint.Zone == nil || *endpoint.Zone == "" {
t.Fatalf("Expected endpoint to have zone: %+v", endpoint)
}
zoneHint := endpoint.Hints.ForZones[0].Name
if *endpoint.Zone != zoneHint {
actualCrossZoneHints++
}
actualHints[zoneHint]++
}
}
if len(actualHints) != len(tc.expectedHints) {
t.Errorf("Expected hints for %d zones, got %d", len(tc.expectedHints), len(actualHints))
}
for zone, expectedNum := range tc.expectedHints {
actualNum, _ := actualHints[zone]
if actualNum != expectedNum {
t.Errorf("Expected %d hints for %s zone, got %d", expectedNum, zone, actualNum)
}
}
if actualCrossZoneHints != tc.expectedCrossZoneHints {
t.Errorf("Expected %d cross zone hints, got %d", tc.expectedCrossZoneHints, actualCrossZoneHints)
}
})
}
}
// Test Helpers // Test Helpers
func newReconciler(client *fake.Clientset, nodes []*corev1.Node, maxEndpointsPerSlice int32) *reconciler { func newReconciler(client *fake.Clientset, nodes []*corev1.Node, maxEndpointsPerSlice int32) *reconciler {
@ -1454,6 +1727,10 @@ type expectedMetrics struct {
numCreated int numCreated int
numUpdated int numUpdated int
numDeleted int numDeleted int
slicesChangedPerSync int
slicesChangedPerSyncTopology int
syncSuccesses int
syncErrors int
} }
func expectMetrics(t *testing.T, em expectedMetrics) { func expectMetrics(t *testing.T, em expectedMetrics) {
@ -1506,6 +1783,30 @@ func expectMetrics(t *testing.T, em expectedMetrics) {
if actualDeleted != float64(em.numDeleted) { if actualDeleted != float64(em.numDeleted) {
t.Errorf("Expected endpointSliceChangesDeleted to be %d, got %v", em.numDeleted, actualDeleted) t.Errorf("Expected endpointSliceChangesDeleted to be %d, got %v", em.numDeleted, actualDeleted)
} }
actualSlicesChangedPerSync, err := testutil.GetHistogramMetricValue(metrics.EndpointSlicesChangedPerSync.WithLabelValues("disabled"))
handleErr(t, err, "slicesChangedPerSync")
if actualSlicesChangedPerSync != float64(em.slicesChangedPerSync) {
t.Errorf("Expected slicesChangedPerSync to be %d, got %v", em.slicesChangedPerSync, actualSlicesChangedPerSync)
}
actualSlicesChangedPerSyncTopology, err := testutil.GetHistogramMetricValue(metrics.EndpointSlicesChangedPerSync.WithLabelValues("auto"))
handleErr(t, err, "slicesChangedPerSyncTopology")
if actualSlicesChangedPerSyncTopology != float64(em.slicesChangedPerSyncTopology) {
t.Errorf("Expected slicesChangedPerSyncTopology to be %d, got %v", em.slicesChangedPerSyncTopology, actualSlicesChangedPerSyncTopology)
}
actualSyncSuccesses, err := testutil.GetCounterMetricValue(metrics.EndpointSliceSyncs.WithLabelValues("success"))
handleErr(t, err, "syncSuccesses")
if actualSyncSuccesses != float64(em.syncSuccesses) {
t.Errorf("Expected endpointSliceSyncSuccesses to be %d, got %v", em.syncSuccesses, actualSyncSuccesses)
}
actualSyncErrors, err := testutil.GetCounterMetricValue(metrics.EndpointSliceSyncs.WithLabelValues("error"))
handleErr(t, err, "syncErrors")
if actualSyncErrors != float64(em.syncErrors) {
t.Errorf("Expected endpointSliceSyncErrors to be %d, got %v", em.syncErrors, actualSyncErrors)
}
} }
func handleErr(t *testing.T, err error, metricName string) { func handleErr(t *testing.T, err error, metricName string) {
@ -1524,4 +1825,8 @@ func setupMetrics() {
metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "create"}) metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "create"})
metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "update"}) metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "update"})
metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "delete"}) metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "delete"})
metrics.EndpointSlicesChangedPerSync.Delete(map[string]string{"topology": "disabled"})
metrics.EndpointSlicesChangedPerSync.Delete(map[string]string{"topology": "auto"})
metrics.EndpointSliceSyncs.Delete(map[string]string{"result": "success"})
metrics.EndpointSliceSyncs.Delete(map[string]string{"result": "error"})
} }

View File

@ -0,0 +1,76 @@
/*
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 topologycache
import (
discovery "k8s.io/api/discovery/v1"
)
// SliceInfo stores information about EndpointSlices for the reconciliation
// process.
type SliceInfo struct {
ServiceKey string
AddressType discovery.AddressType
ToCreate []*discovery.EndpointSlice
ToUpdate []*discovery.EndpointSlice
Unchanged []*discovery.EndpointSlice
}
func (si *SliceInfo) getTotalEndpoints() int {
totalEndpoints := 0
for _, slice := range si.ToCreate {
totalEndpoints += len(slice.Endpoints)
}
for _, slice := range si.ToUpdate {
totalEndpoints += len(slice.Endpoints)
}
for _, slice := range si.Unchanged {
totalEndpoints += len(slice.Endpoints)
}
return totalEndpoints
}
// getAllocatedHintsByZone sums up the allocated hints we currently have in
// unchanged slices and marks slices for update as necessary. A slice needs to
// be updated if any of the following are true:
// - It has an endpoint without zone hints
// - It has an endpoint hint for a zone that no longer needs any
// - It has endpoint hints that would make the minimum allocations necessary
// impossible with changes to slices that are already being updated or
// created.
func (si *SliceInfo) getAllocatedHintsByZone(allocations map[string]Allocation) EndpointZoneInfo {
allocatedHintsByZone := EndpointZoneInfo{}
// Using filtering in place to remove any endpoints that are no longer
// unchanged (https://github.com/golang/go/wiki/SliceTricks#filter-in-place)
j := 0
for _, slice := range si.Unchanged {
hintsByZone := getHintsByZone(slice, allocatedHintsByZone, allocations)
if hintsByZone == nil {
si.ToUpdate = append(si.ToUpdate, slice.DeepCopy())
} else {
si.Unchanged[j] = slice
j++
for zone, numHints := range hintsByZone {
allocatedHintsByZone[zone] += numHints
}
}
}
si.Unchanged = si.Unchanged[:j]
return allocatedHintsByZone
}

View File

@ -0,0 +1,79 @@
/*
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 topologycache
import (
"fmt"
"testing"
discovery "k8s.io/api/discovery/v1"
)
func TestGetTotalEndpoints(t *testing.T) {
testCases := []struct {
name string
si *SliceInfo
expectedTotal int
}{{
name: "empty",
si: &SliceInfo{},
expectedTotal: 0,
}, {
name: "empty slice",
si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(0)},
},
expectedTotal: 0,
}, {
name: "multiple slices",
si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15), sliceWithNEndpoints(8)},
},
expectedTotal: 23,
}, {
name: "slices for all",
si: &SliceInfo{
ToCreate: []*discovery.EndpointSlice{sliceWithNEndpoints(15), sliceWithNEndpoints(8)},
ToUpdate: []*discovery.EndpointSlice{sliceWithNEndpoints(2)},
Unchanged: []*discovery.EndpointSlice{sliceWithNEndpoints(100), sliceWithNEndpoints(90)},
},
expectedTotal: 215,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualTotal := tc.si.getTotalEndpoints()
if actualTotal != tc.expectedTotal {
t.Errorf("Expected %d, got %d", tc.expectedTotal, actualTotal)
}
})
}
}
// helpers
func sliceWithNEndpoints(n int) *discovery.EndpointSlice {
endpoints := []discovery.Endpoint{}
for i := 0; i < n; i++ {
endpoints = append(endpoints, discovery.Endpoint{Addresses: []string{fmt.Sprintf("10.1.2.%d", i)}})
}
return &discovery.EndpointSlice{
Endpoints: endpoints,
}
}

View File

@ -0,0 +1,252 @@
/*
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 topologycache
import (
"math"
"sync"
"k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/klog/v2"
)
const (
// OverloadThreshold represents the maximum overload any individual endpoint
// should be exposed to.
OverloadThreshold float64 = 0.2
)
// TopologyCache tracks the distribution of Nodes and endpoints across zones.
type TopologyCache struct {
lock sync.Mutex
sufficientNodeInfo bool
cpuByZone map[string]*resource.Quantity
cpuRatiosByZone map[string]float64
endpointsByService map[string]map[discovery.AddressType]EndpointZoneInfo
}
// EndpointZoneInfo tracks the distribution of endpoints across zones for a
// Service.
type EndpointZoneInfo map[string]int
// Allocation describes the number of endpoints that should be allocated for a
// zone.
type Allocation struct {
Minimum int
Maximum int
Desired float64
}
// NewTopologyCache initializes a new TopologyCache.
func NewTopologyCache() *TopologyCache {
return &TopologyCache{
cpuByZone: map[string]*resource.Quantity{},
cpuRatiosByZone: map[string]float64{},
endpointsByService: map[string]map[discovery.AddressType]EndpointZoneInfo{},
}
}
// GetOverloadedServices returns a list of Service keys that refer to Services
// that have crossed the overload threshold for any zone.
func (t *TopologyCache) GetOverloadedServices() []string {
t.lock.Lock()
defer t.lock.Unlock()
svcKeys := []string{}
for svcKey, eziByAddrType := range t.endpointsByService {
for _, ezi := range eziByAddrType {
if serviceOverloaded(ezi, t.cpuRatiosByZone) {
svcKeys = append(svcKeys, svcKey)
break
}
}
}
return svcKeys
}
// AddHints adds or updates topology hints on EndpointSlices and returns updated
// lists of EndpointSlices to create and update.
func (t *TopologyCache) AddHints(si *SliceInfo) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice) {
totalEndpoints := si.getTotalEndpoints()
allocations := t.getAllocations(totalEndpoints)
if allocations == nil {
klog.V(2).Infof("Insufficient endpoints, removing hints from %s Service", si.ServiceKey)
t.RemoveHints(si.ServiceKey, si.AddressType)
return RemoveHintsFromSlices(si)
}
allocatedHintsByZone := si.getAllocatedHintsByZone(allocations)
allocatableSlices := si.ToCreate
for _, slice := range si.ToUpdate {
allocatableSlices = append(allocatableSlices, slice)
}
// step 1: assign same-zone hints for all endpoints as a starting point.
for _, slice := range allocatableSlices {
for i, endpoint := range slice.Endpoints {
if endpoint.Zone == nil || *endpoint.Zone == "" {
klog.Warningf("Endpoint found without zone specified, removing hints from %s Service", si.ServiceKey)
t.RemoveHints(si.ServiceKey, si.AddressType)
return RemoveHintsFromSlices(si)
}
allocatedHintsByZone[*endpoint.Zone]++
slice.Endpoints[i].Hints = &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: *endpoint.Zone}}}
}
}
// step 2. Identify which zones need to donate slices and which need more.
givingZones, receivingZones := getGivingAndReceivingZones(allocations, allocatedHintsByZone)
// step 3. Redistribute endpoints based on data from step 2.
redistributions := redistributeHints(allocatableSlices, givingZones, receivingZones)
for zone, diff := range redistributions {
allocatedHintsByZone[zone] += diff
}
t.SetHints(si.ServiceKey, si.AddressType, allocatedHintsByZone)
return si.ToCreate, si.ToUpdate
}
// SetHints sets topology hints for the provided serviceKey and addrType in this
// cache.
func (t *TopologyCache) SetHints(serviceKey string, addrType discovery.AddressType, allocatedHintsByZone EndpointZoneInfo) {
if len(allocatedHintsByZone) == 0 {
t.RemoveHints(serviceKey, addrType)
return
}
t.lock.Lock()
defer t.lock.Unlock()
_, ok := t.endpointsByService[serviceKey]
if !ok {
t.endpointsByService[serviceKey] = map[discovery.AddressType]EndpointZoneInfo{}
}
t.endpointsByService[serviceKey][addrType] = allocatedHintsByZone
}
// RemoveHints removes topology hints for the provided serviceKey and addrType
// from this cache.
func (t *TopologyCache) RemoveHints(serviceKey string, addrType discovery.AddressType) {
t.lock.Lock()
defer t.lock.Unlock()
_, ok := t.endpointsByService[serviceKey]
if ok {
delete(t.endpointsByService[serviceKey], addrType)
}
if len(t.endpointsByService[serviceKey]) == 0 {
delete(t.endpointsByService, serviceKey)
}
}
// SetNodes updates the Node distribution for the TopologyCache.
func (t *TopologyCache) SetNodes(nodes []*v1.Node) {
cpuByZone := map[string]*resource.Quantity{}
sufficientNodeInfo := true
totalCPU := resource.Quantity{}
for _, node := range nodes {
if !NodeReady(node.Status) {
continue
}
nodeCPU := node.Status.Allocatable.Cpu()
zone, ok := node.Labels[v1.LabelTopologyZone]
// TODO(robscott): Figure out if there's an acceptable proportion of
// nodes with inadequate information. The current logic means that as
// soon as we find any node without a zone or allocatable CPU specified,
// we bail out entirely. Bailing out at this level will make our cluster
// wide ratios nil, which would result in slices for all Services having
// their hints removed.
if !ok || zone == "" || nodeCPU.IsZero() {
cpuByZone = map[string]*resource.Quantity{}
sufficientNodeInfo = false
break
}
totalCPU.Add(*nodeCPU)
if _, ok = cpuByZone[zone]; !ok {
cpuByZone[zone] = nodeCPU
} else {
cpuByZone[zone].Add(*nodeCPU)
}
}
t.lock.Lock()
defer t.lock.Unlock()
if totalCPU.IsZero() || !sufficientNodeInfo || len(cpuByZone) < 2 {
t.sufficientNodeInfo = false
t.cpuByZone = nil
t.cpuRatiosByZone = nil
} else {
t.sufficientNodeInfo = sufficientNodeInfo
t.cpuByZone = cpuByZone
t.cpuRatiosByZone = map[string]float64{}
for zone, cpu := range cpuByZone {
t.cpuRatiosByZone[zone] = float64(cpu.MilliValue()) / float64(totalCPU.MilliValue())
}
}
}
// getAllocations returns a set of minimum and maximum allocations per zone. If
// it is not possible to provide allocations that are below the overload
// threshold, a nil value will be returned.
func (t *TopologyCache) getAllocations(numEndpoints int) map[string]Allocation {
if t.cpuRatiosByZone == nil || len(t.cpuRatiosByZone) < 2 || len(t.cpuRatiosByZone) > numEndpoints {
return nil
}
t.lock.Lock()
defer t.lock.Unlock()
remainingMinEndpoints := numEndpoints
minTotal := 0
allocations := map[string]Allocation{}
for zone, ratio := range t.cpuRatiosByZone {
desired := ratio * float64(numEndpoints)
minimum := int(math.Ceil(desired * (1 / (1 + OverloadThreshold))))
allocations[zone] = Allocation{
Minimum: minimum,
Desired: math.Max(desired, float64(minimum)),
}
minTotal += minimum
remainingMinEndpoints -= minimum
if remainingMinEndpoints < 0 {
return nil
}
}
for zone, allocation := range allocations {
allocation.Maximum = allocation.Minimum + numEndpoints - minTotal
allocations[zone] = allocation
}
return allocations
}

View File

@ -0,0 +1,486 @@
/*
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 topologycache
import (
"reflect"
"testing"
"k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
)
func TestAddHints(t *testing.T) {
testCases := []struct {
name string
cpuRatiosByZone map[string]float64
sliceInfo *SliceInfo
expectedEndpointsByAddrType map[discovery.AddressType]EndpointZoneInfo
expectedSlicesToCreate []*discovery.EndpointSlice
expectedSlicesToUpdate []*discovery.EndpointSlice
}{{
name: "empty",
cpuRatiosByZone: nil,
sliceInfo: &SliceInfo{
ServiceKey: "ns/svc",
AddressType: discovery.AddressTypeIPv4,
},
expectedEndpointsByAddrType: nil,
expectedSlicesToCreate: []*discovery.EndpointSlice{},
expectedSlicesToUpdate: []*discovery.EndpointSlice{},
}, {
name: "slice to create, no zone ratios",
cpuRatiosByZone: nil,
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"),
}},
}},
},
expectedEndpointsByAddrType: nil,
expectedSlicesToCreate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
}},
}},
expectedSlicesToUpdate: []*discovery.EndpointSlice{},
}, {
name: "slice to create with 2 endpoints, zone ratios require 3",
cpuRatiosByZone: map[string]float64{
"zone-a": 0.3,
"zone-b": 0.4,
"zone-c": 0.3,
},
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"),
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
}},
}},
},
expectedEndpointsByAddrType: nil,
expectedSlicesToCreate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
}},
}},
expectedSlicesToUpdate: []*discovery.EndpointSlice{},
}, {
name: "slice to create with 2 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"),
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
}},
}},
},
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"}}},
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
}},
}},
expectedSlicesToUpdate: []*discovery.EndpointSlice{},
}, {
name: "slices to create and update within 3 zone threshold",
cpuRatiosByZone: map[string]float64{
"zone-a": 0.35,
"zone-b": 0.35,
"zone-c": 0.30,
},
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"),
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
}},
}, {
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.3.3"},
Zone: utilpointer.StringPtr("zone-c"),
}, {
Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-c"),
}, {
Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-a"),
}},
}},
ToUpdate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
}, {
Addresses: []string{"10.2.2.4"},
Zone: utilpointer.StringPtr("zone-a"),
}},
}, {
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.3.3"},
Zone: utilpointer.StringPtr("zone-b"),
}, {
Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-c"),
}, {
Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-a"),
}},
}},
},
expectedEndpointsByAddrType: map[discovery.AddressType]EndpointZoneInfo{
discovery.AddressTypeIPv4: {
"zone-a": 4,
"zone-b": 3,
"zone-c": 3,
},
},
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-b"}}},
}, {
Addresses: []string{"10.1.2.4"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
}},
}, {
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.1.3.3"},
Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
}, {
Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
}, {
Addresses: []string{"10.1.3.4"},
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}},
}},
expectedSlicesToUpdate: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.2.3"},
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}, {
Addresses: []string{"10.2.2.4"},
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}},
}, {
Endpoints: []discovery.Endpoint{{
Addresses: []string{"10.2.3.3"},
Zone: utilpointer.StringPtr("zone-b"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-b"}}},
}, {
Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-c"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-c"}}},
}, {
Addresses: []string{"10.2.3.4"},
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}},
}},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cache := NewTopologyCache()
cache.cpuRatiosByZone = tc.cpuRatiosByZone
slicesToCreate, slicesToUpdate := cache.AddHints(tc.sliceInfo)
expectEquivalentSlices(t, slicesToCreate, tc.expectedSlicesToCreate)
expectEquivalentSlices(t, slicesToUpdate, tc.expectedSlicesToUpdate)
endpointsByAddrType, ok := cache.endpointsByService[tc.sliceInfo.ServiceKey]
if tc.expectedEndpointsByAddrType == nil {
if ok {
t.Errorf("Expected no endpoints for Service %s, got %+v", tc.sliceInfo.ServiceKey, endpointsByAddrType)
}
} else {
if len(tc.expectedEndpointsByAddrType) != len(endpointsByAddrType) {
t.Fatalf("Expected endpoints for %d address types, got %d", len(tc.expectedEndpointsByAddrType), len(endpointsByAddrType))
}
for addrType, expectedEndpointZoneInfo := range tc.expectedEndpointsByAddrType {
endpointZoneInfo, ok := endpointsByAddrType[addrType]
if !ok {
t.Fatalf("Expected endpoints for %s address type, got none", addrType)
}
if len(expectedEndpointZoneInfo) != len(endpointZoneInfo) {
t.Fatalf("Expected endpoints for %d zones, got %d", len(expectedEndpointZoneInfo), len(endpointZoneInfo))
}
for zone, expectedNum := range expectedEndpointZoneInfo {
num, ok := endpointZoneInfo[zone]
if !ok {
t.Fatalf("Expected endpoints for %s zone, got none", zone)
}
if num != expectedNum {
t.Errorf("Expected %d endpoints for %s zone, got %d", expectedNum, zone, num)
}
}
}
}
})
}
}
func TestSetNodes(t *testing.T) {
type nodeInfo struct {
zone string
cpu resource.Quantity
ready v1.ConditionStatus
}
testCases := []struct {
name string
nodes []nodeInfo
expectSufficientNodeInfo bool
expectedCPUByZone map[string]*resource.Quantity
expectedRatios map[string]float64
}{{
name: "empty",
nodes: []nodeInfo{},
expectSufficientNodeInfo: false,
expectedCPUByZone: nil,
expectedRatios: nil,
}, {
name: "single node",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
},
expectSufficientNodeInfo: false,
expectedCPUByZone: nil,
expectedRatios: nil,
}, {
name: "single zone",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
},
expectSufficientNodeInfo: false,
expectedCPUByZone: nil,
expectedRatios: nil,
}, {
name: "2 zones",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
},
expectSufficientNodeInfo: true,
expectedCPUByZone: map[string]*resource.Quantity{
"zone-a": resource.NewQuantity(1, resource.BinarySI),
"zone-b": resource.NewQuantity(1, resource.BinarySI),
},
expectedRatios: map[string]float64{
"zone-a": 0.5,
"zone-b": 0.5,
},
}, {
name: "2 zones, unready node in 1, ready node in 1",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionFalse},
{zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
},
expectSufficientNodeInfo: false,
expectedCPUByZone: nil,
expectedRatios: nil,
}, {
name: "2 zones, unready node in 1, ready node in 2",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionFalse},
},
expectSufficientNodeInfo: true,
expectedCPUByZone: map[string]*resource.Quantity{
"zone-a": resource.NewQuantity(1, resource.BinarySI),
"zone-b": resource.NewQuantity(1, resource.BinarySI),
},
expectedRatios: map[string]float64{
"zone-a": 0.5,
"zone-b": 0.5,
},
}, {
name: "3 zones, 4 nodes in 1, 2 nodes in 1, 1 node in 1",
nodes: []nodeInfo{
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue},
{zone: "zone-a", cpu: resource.MustParse("2000m"), ready: v1.ConditionTrue},
{zone: "zone-b", cpu: resource.MustParse("3000m"), ready: v1.ConditionTrue},
{zone: "zone-b", cpu: resource.MustParse("1500m"), ready: v1.ConditionTrue},
{zone: "zone-c", cpu: resource.MustParse("500m"), ready: v1.ConditionTrue},
},
expectSufficientNodeInfo: true,
expectedCPUByZone: map[string]*resource.Quantity{
"zone-a": resource.NewMilliQuantity(5000, resource.BinarySI),
"zone-b": resource.NewMilliQuantity(4500, resource.BinarySI),
"zone-c": resource.NewMilliQuantity(500, resource.BinarySI),
},
expectedRatios: map[string]float64{
"zone-a": 0.5,
"zone-b": 0.45,
"zone-c": 0.05,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cache := NewTopologyCache()
nodes := make([]*v1.Node, 0, len(tc.nodes))
for _, node := range tc.nodes {
labels := map[string]string{}
if node.zone != "" {
labels[v1.LabelTopologyZone] = node.zone
}
conditions := []v1.NodeCondition{{
Type: v1.NodeReady,
Status: node.ready,
}}
allocatable := v1.ResourceList{
v1.ResourceCPU: node.cpu,
}
nodes = append(nodes, &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Status: v1.NodeStatus{
Allocatable: allocatable,
Conditions: conditions,
},
})
}
cache.SetNodes(nodes)
if cache.sufficientNodeInfo != tc.expectSufficientNodeInfo {
t.Errorf("Expected sufficientNodeInfo to be %t, got %t", tc.expectSufficientNodeInfo, cache.sufficientNodeInfo)
}
if cache.cpuRatiosByZone == nil || tc.expectedRatios == nil {
if (cache.cpuRatiosByZone == nil) != (tc.expectedRatios == nil) {
t.Errorf("Expected %+v, got %+v", tc.expectedRatios, cache.cpuRatiosByZone)
}
} else {
if len(cache.cpuRatiosByZone) != len(tc.expectedRatios) {
t.Errorf("Expected ratios with %d zones, got %d", len(tc.expectedRatios), len(cache.cpuRatiosByZone))
}
for zone, expectedRatio := range tc.expectedRatios {
actualRatio, ok := cache.cpuRatiosByZone[zone]
if !ok {
t.Errorf("Expected ratio for %s zone, got none", zone)
} else if actualRatio != expectedRatio {
t.Errorf("Expected ratio to be %f, got %f", expectedRatio, actualRatio)
}
}
}
if cache.cpuByZone == nil || tc.expectedCPUByZone == nil {
if (cache.cpuByZone == nil) != (tc.expectedCPUByZone == nil) {
t.Errorf("Expected %+v, got %+v", tc.expectedCPUByZone, cache.cpuByZone)
}
} else {
if len(cache.cpuByZone) != len(tc.expectedCPUByZone) {
t.Errorf("Expected CPU with %d zones, got %d", len(tc.expectedCPUByZone), len(cache.cpuByZone))
}
for zone, expectedCPU := range tc.expectedCPUByZone {
actualCPU, ok := cache.cpuByZone[zone]
if !ok {
t.Errorf("Expected CPU for %s zone, got none", zone)
} else if !actualCPU.Equal(*expectedCPU) {
t.Errorf("Expected CPU to be %d, got %d", expectedCPU.MilliValue(), actualCPU.MilliValue())
}
}
}
})
}
}
// Test Helpers
func expectEquivalentSlices(t *testing.T, actualSlices, expectedSlices []*discovery.EndpointSlice) {
t.Helper()
if len(actualSlices) != len(expectedSlices) {
t.Fatalf("Expected %d slices, got %d", len(expectedSlices), len(actualSlices))
}
for i, expectedSlice := range expectedSlices {
actualSlice := actualSlices[i]
if len(expectedSlice.Endpoints) != len(actualSlice.Endpoints) {
t.Errorf("Expected %d endpoints, got %d", len(expectedSlice.Endpoints), len(actualSlice.Endpoints))
continue
}
for j, expectedEndpoint := range expectedSlice.Endpoints {
actualEndpoint := actualSlice.Endpoints[j]
if !reflect.DeepEqual(actualEndpoint, expectedEndpoint) {
t.Errorf("Endpoints didn't match\nExpected: %+v\nGot: %+v", expectedEndpoint, actualEndpoint)
}
}
}
}

View File

@ -0,0 +1,246 @@
/*
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 topologycache
import (
"math"
"k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
"k8s.io/klog/v2"
)
// RemoveHintsFromSlices removes topology hints on EndpointSlices and returns
// updated lists of EndpointSlices to create and update.
func RemoveHintsFromSlices(si *SliceInfo) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice) {
// Remove hints on all EndpointSlices we were already going to change.
slices := append(si.ToCreate, si.ToUpdate...)
for _, slice := range slices {
for i := range slice.Endpoints {
slice.Endpoints[i].Hints = nil
}
}
// Remove hints on all unchanged EndpointSlices and mark them for update
// if any already had hints. We use j to track the number/index of slices
// that are still unchanged.
j := 0
for _, slice := range si.Unchanged {
changed := false
for i, endpoint := range slice.Endpoints {
if endpoint.Hints != nil {
// Unchanged slices are still direct copies from informer cache.
// Need to deep copy before we make any modifications to avoid
// accidentally changing informer cache.
slice = slice.DeepCopy()
slice.Endpoints[i].Hints = nil
changed = true
}
}
if changed {
si.ToUpdate = append(si.ToUpdate, slice)
} else {
si.Unchanged[j] = slice
j++
}
}
// truncate si.Unchanged so it only includes slices that are still
// unchanged.
si.Unchanged = si.Unchanged[:j]
return si.ToCreate, si.ToUpdate
}
// redistributeHints redistributes hints based in the provided EndpointSlices.
// It allocates endpoints from the provided givingZones to the provided
// receivingZones. This returns a map that represents the changes in allocated
// endpoints by zone.
func redistributeHints(slices []*discovery.EndpointSlice, givingZones, receivingZones map[string]int) map[string]int {
redistributions := map[string]int{}
for _, slice := range slices {
for i, endpoint := range slice.Endpoints {
if len(givingZones) == 0 || len(receivingZones) == 0 {
return redistributions
}
if endpoint.Zone == nil || *endpoint.Zone == "" {
// This should always be caught earlier in AddHints()
klog.Warningf("Endpoint found without zone specified")
continue
}
givingZone := *endpoint.Zone
numToGive, ok := givingZones[givingZone]
if ok && numToGive > 0 {
for receivingZone, numToReceive := range receivingZones {
if numToReceive > 0 {
slice.Endpoints[i].Hints = &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: receivingZone}}}
if numToGive == 1 {
delete(givingZones, givingZone)
} else {
givingZones[givingZone]--
}
if numToReceive == 1 {
delete(receivingZones, receivingZone)
} else {
receivingZones[receivingZone]--
}
redistributions[receivingZone]++
redistributions[givingZone]--
break
}
}
}
}
}
return redistributions
}
// getGivingAndReceivingZones returns the number of endpoints each zone should
// give to other zones along with the number of endpoints each zone should
// receive from other zones. This is calculated with the provided allocations
// (desired state) and allocatedHintsByZone (current state).
func getGivingAndReceivingZones(allocations map[string]Allocation, allocatedHintsByZone map[string]int) (map[string]int, map[string]int) {
// 1. Determine the precise number of additional endpoints each zone has
// (giving) or needs (receiving).
givingZonesDesired := map[string]float64{}
receivingZonesDesired := map[string]float64{}
for zone, allocation := range allocations {
allocatedHints, _ := allocatedHintsByZone[zone]
target := allocation.Desired
if float64(allocatedHints) > target {
givingZonesDesired[zone] = float64(allocatedHints) - target
} else if float64(allocatedHints) < target {
receivingZonesDesired[zone] = target - float64(allocatedHints)
}
}
// 2. Convert the precise numbers needed into ints representing real
// endpoints given from one zone to another.
givingZones := map[string]int{}
receivingZones := map[string]int{}
for {
givingZone, numToGive := getMost(givingZonesDesired)
receivingZone, numToReceive := getMost(receivingZonesDesired)
// return early if any of the following are true:
// - giving OR receiving zone are unspecified
// - giving AND receiving zones have less than 1 endpoint left to give or receive
// - giving OR receiving zones have less than 0.5 endpoints left to give or receive
if givingZone == "" || receivingZone == "" || (numToGive < 1.0 && numToReceive < 1.0) || numToGive < 0.5 || numToReceive < 0.5 {
break
}
givingZones[givingZone]++
givingZonesDesired[givingZone]--
receivingZones[receivingZone]++
receivingZonesDesired[receivingZone]--
}
return givingZones, receivingZones
}
// getMost accepts a map[string]float64 and returns the string and float64 that
// represent the greatest value in this provided map. This function is not very
// efficient but it is expected that len() will rarely be greater than 2.
func getMost(zones map[string]float64) (string, float64) {
zone := ""
num := 0.0
for z, n := range zones {
if n > num {
zone = z
num = n
}
}
return zone, num
}
// getHintsByZone returns the number of hints allocated to each zone by the
// provided EndpointSlice. This function returns nil to indicate that the
// current allocations are invalid and that the EndpointSlice needs to be
// updated. This could be caused by:
// - A hint for a zone that no longer requires any allocations.
// - An endpoint with no hints.
// - Hints that would make minimum allocations impossible.
func getHintsByZone(slice *discovery.EndpointSlice, allocatedHintsByZone EndpointZoneInfo, allocations map[string]Allocation) map[string]int {
hintsByZone := map[string]int{}
for _, endpoint := range slice.Endpoints {
if endpoint.Hints == nil || len(endpoint.Hints.ForZones) == 0 {
return nil
}
zone := endpoint.Hints.ForZones[0].Name
if _, ok := allocations[zone]; ok {
return nil
}
}
for zone, numHints := range hintsByZone {
alreadyAllocated, _ := allocatedHintsByZone[zone]
allocation, ok := allocations[zone]
if !ok || (numHints+alreadyAllocated) > allocation.Maximum {
return nil
}
}
return hintsByZone
}
// serviceOverloaded returns true if the Service has an insufficient amount of
// endpoints for any zone.
func serviceOverloaded(ezi EndpointZoneInfo, zoneRatios map[string]float64) bool {
if len(ezi) == 0 {
return false
}
if len(zoneRatios) == 0 {
return true
}
totalEndpoints := 0.0
for _, numEndpoints := range ezi {
totalEndpoints += float64(numEndpoints)
}
for zone, ratio := range zoneRatios {
svcEndpoints, ok := ezi[zone]
if !ok {
return true
}
minEndpoints := math.Ceil(totalEndpoints * ratio * (1 / (1 + OverloadThreshold)))
if svcEndpoints < int(minEndpoints) {
return true
}
}
return false
}
// NodeReady returns true if the Node has a status condition of type "NodeReady"
// with a status of "True".
func NodeReady(nodeStatus v1.NodeStatus) bool {
for _, cond := range nodeStatus.Conditions {
if cond.Type == v1.NodeReady {
return cond.Status == v1.ConditionTrue
}
}
return false
}

View File

@ -0,0 +1,195 @@
/*
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 topologycache
import (
"reflect"
"testing"
discovery "k8s.io/api/discovery/v1"
utilpointer "k8s.io/utils/pointer"
)
func Test_redistributeHints(t *testing.T) {
testCases := []struct {
name string
slices []*discovery.EndpointSlice
givingZones map[string]int
receivingZones map[string]int
expectedRedistributions map[string]int
}{{
name: "empty",
slices: []*discovery.EndpointSlice{},
givingZones: map[string]int{},
receivingZones: map[string]int{},
expectedRedistributions: map[string]int{},
}, {
name: "single endpoint",
slices: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}},
}},
givingZones: map[string]int{"zone-a": 1},
receivingZones: map[string]int{"zone-b": 1},
expectedRedistributions: map[string]int{"zone-a": -1, "zone-b": 1},
}, {
name: "endpoints from 1 zone redistributed to 2 other zones",
slices: []*discovery.EndpointSlice{{
Endpoints: []discovery.Endpoint{{
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}, {
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}, {
Zone: utilpointer.StringPtr("zone-a"),
Hints: &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: "zone-a"}}},
}},
}},
givingZones: map[string]int{"zone-a": 2},
receivingZones: map[string]int{"zone-b": 1, "zone-c": 1},
expectedRedistributions: map[string]int{"zone-a": -2, "zone-b": 1, "zone-c": 1},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualRedistributions := redistributeHints(tc.slices, tc.givingZones, tc.receivingZones)
if len(actualRedistributions) != len(tc.expectedRedistributions) {
t.Fatalf("Expected redistributions for %d zones, got %d (%+v)", len(tc.expectedRedistributions), len(actualRedistributions), actualRedistributions)
}
for zone, expectedNum := range tc.expectedRedistributions {
actualNum, _ := actualRedistributions[zone]
if actualNum != expectedNum {
t.Errorf("Expected redistribution of %d for zone %s, got %d", expectedNum, zone, actualNum)
}
}
})
}
}
func Test_getGivingAndReceivingZones(t *testing.T) {
testCases := []struct {
name string
allocations map[string]Allocation
allocatedHintsByZone map[string]int
expectedGivingZones map[string]int
expectedReceivingZones map[string]int
}{{
name: "empty",
allocations: map[string]Allocation{},
allocatedHintsByZone: map[string]int{},
expectedGivingZones: map[string]int{},
expectedReceivingZones: map[string]int{},
}, {
name: "simple allocation with no need for rebalancing",
allocations: map[string]Allocation{
"zone-a": {Desired: 1.2},
"zone-b": {Desired: 1.1},
"zone-c": {Desired: 1.0},
},
allocatedHintsByZone: map[string]int{"zone-a": 1, "zone-b": 1, "zone-c": 1},
expectedGivingZones: map[string]int{},
expectedReceivingZones: map[string]int{},
}, {
name: "preference for same zone even when giving an extra endpoint would result in slightly better distribution",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.1},
"zone-b": {Desired: 5.1},
"zone-c": {Desired: 5.8},
},
allocatedHintsByZone: map[string]int{"zone-a": 16},
expectedGivingZones: map[string]int{"zone-a": 10},
expectedReceivingZones: map[string]int{"zone-b": 5, "zone-c": 5},
}, {
name: "when 2 zones need < 1 endpoint, give to zone that needs endpoint most",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.0},
"zone-b": {Desired: 5.6},
"zone-c": {Desired: 5.4},
},
allocatedHintsByZone: map[string]int{"zone-a": 16},
expectedGivingZones: map[string]int{"zone-a": 11},
expectedReceivingZones: map[string]int{"zone-b": 6, "zone-c": 5},
}, {
name: "when 2 zones have extra endpoints, give from zone with most extra",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.0},
"zone-b": {Desired: 5.6},
"zone-c": {Desired: 5.4},
},
allocatedHintsByZone: map[string]int{"zone-b": 8, "zone-c": 8},
expectedGivingZones: map[string]int{"zone-b": 2, "zone-c": 3},
expectedReceivingZones: map[string]int{"zone-a": 5},
}, {
name: "ensure function can handle unexpected data (more allocated than allocations)",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.0},
"zone-b": {Desired: 5.0},
"zone-c": {Desired: 5.0},
},
allocatedHintsByZone: map[string]int{"zone-a": 6, "zone-b": 6, "zone-c": 6},
expectedGivingZones: map[string]int{},
expectedReceivingZones: map[string]int{},
}, {
name: "ensure function can handle unexpected data (negative allocations)",
allocations: map[string]Allocation{
"zone-a": {Desired: -5.0},
"zone-b": {Desired: -5.0},
"zone-c": {Desired: -5.0},
},
allocatedHintsByZone: map[string]int{"zone-a": 6, "zone-b": 6, "zone-c": 6},
expectedGivingZones: map[string]int{},
expectedReceivingZones: map[string]int{},
}, {
name: "ensure function can handle unexpected data (negative allocated)",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.0},
"zone-b": {Desired: 5.0},
"zone-c": {Desired: 5.0},
},
allocatedHintsByZone: map[string]int{"zone-a": -4, "zone-b": -3, "zone-c": -2},
expectedGivingZones: map[string]int{},
expectedReceivingZones: map[string]int{},
}, {
name: "ensure function can handle unexpected data (negative for 1 zone)",
allocations: map[string]Allocation{
"zone-a": {Desired: 5.0},
"zone-b": {Desired: 5.0},
"zone-c": {Desired: 5.0},
},
allocatedHintsByZone: map[string]int{"zone-a": -40, "zone-b": 20, "zone-c": 20},
expectedGivingZones: map[string]int{"zone-b": 15, "zone-c": 15},
expectedReceivingZones: map[string]int{"zone-a": 30},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualGivingZones, actualReceivingZones := getGivingAndReceivingZones(tc.allocations, tc.allocatedHintsByZone)
if !reflect.DeepEqual(actualGivingZones, tc.expectedGivingZones) {
t.Errorf("Expected %+v giving zones, got %+v", tc.expectedGivingZones, actualGivingZones)
}
if !reflect.DeepEqual(actualReceivingZones, tc.expectedReceivingZones) {
t.Errorf("Expected %+v receiving zones, got %+v", tc.expectedReceivingZones, actualReceivingZones)
}
})
}
}

View File

@ -27,6 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/klog/v2" "k8s.io/klog/v2"
@ -365,3 +366,31 @@ func getAddressTypesForService(service *corev1.Service) map[discovery.AddressTyp
klog.V(2).Infof("couldn't find ipfamilies for headless service: %v/%v likely because controller manager is likely connected to an old apiserver that does not support ip families yet. The service endpoint slice will use dual stack families until api-server default it correctly", service.Namespace, service.Name) klog.V(2).Infof("couldn't find ipfamilies for headless service: %v/%v likely because controller manager is likely connected to an old apiserver that does not support ip families yet. The service endpoint slice will use dual stack families until api-server default it correctly", service.Namespace, service.Name)
return serviceSupportedAddresses return serviceSupportedAddresses
} }
func unchangedSlices(existingSlices, slicesToUpdate, slicesToDelete []*discovery.EndpointSlice) []*discovery.EndpointSlice {
changedSliceNames := sets.String{}
for _, slice := range slicesToUpdate {
changedSliceNames.Insert(slice.Name)
}
for _, slice := range slicesToDelete {
changedSliceNames.Insert(slice.Name)
}
unchangedSlices := []*discovery.EndpointSlice{}
for _, slice := range existingSlices {
if !changedSliceNames.Has(slice.Name) {
unchangedSlices = append(unchangedSlices, slice)
}
}
return unchangedSlices
}
// hintsEnabled returns true if the provided annotations include a
// corev1.AnnotationTopologyAwareHints key with a value set to "auto".
func hintsEnabled(annotations map[string]string) bool {
val, ok := annotations[corev1.AnnotationTopologyAwareHints]
if !ok {
return false
}
return val == "auto"
}

View File

@ -688,6 +688,12 @@ const (
// Enables controlling pod ranking on replicaset scale-down. // Enables controlling pod ranking on replicaset scale-down.
PodDeletionCost featuregate.Feature = "PodDeletionCost" PodDeletionCost featuregate.Feature = "PodDeletionCost"
// owner: @robscott
// alpha: v1.21
//
// Enables topology aware hints for EndpointSlices
TopologyAwareHints featuregate.Feature = "TopologyAwareHints"
// owner: @ahg-g // owner: @ahg-g
// alpha: v1.21 // alpha: v1.21
// //
@ -832,6 +838,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
PreferNominatedNode: {Default: false, PreRelease: featuregate.Alpha}, PreferNominatedNode: {Default: false, PreRelease: featuregate.Alpha},
RunAsGroup: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22 RunAsGroup: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
PodDeletionCost: {Default: false, PreRelease: featuregate.Alpha}, PodDeletionCost: {Default: false, PreRelease: featuregate.Alpha},
TopologyAwareHints: {Default: false, PreRelease: featuregate.Alpha},
PodAffinityNamespaceSelector: {Default: false, PreRelease: featuregate.Alpha}, PodAffinityNamespaceSelector: {Default: false, PreRelease: featuregate.Alpha},
ServiceLoadBalancerClass: {Default: false, PreRelease: featuregate.Alpha}, ServiceLoadBalancerClass: {Default: false, PreRelease: featuregate.Alpha},
LogarithmicScaleDown: {Default: false, PreRelease: featuregate.Alpha}, LogarithmicScaleDown: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -50,6 +50,9 @@ type BaseEndpointInfo struct {
IsLocal bool IsLocal bool
Topology map[string]string Topology map[string]string
// ZoneHints represent the zone hints for the endpoint. This is based on
// endpoint.hints.forZones[*].name in the EndpointSlice API.
ZoneHints sets.String
// Ready indicates whether this endpoint is ready and NOT terminating. // Ready indicates whether this endpoint is ready and NOT terminating.
// For pods, this is true if a pod has a ready status and a nil deletion timestamp. // For pods, this is true if a pod has a ready status and a nil deletion timestamp.
// This is only set when watching EndpointSlices. If using Endpoints, this is always // This is only set when watching EndpointSlices. If using Endpoints, this is always
@ -102,6 +105,11 @@ func (info *BaseEndpointInfo) GetTopology() map[string]string {
return info.Topology return info.Topology
} }
// GetZoneHints returns the zone hint for the endpoint.
func (info *BaseEndpointInfo) GetZoneHints() sets.String {
return info.ZoneHints
}
// IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface. // IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface.
func (info *BaseEndpointInfo) IP() string { func (info *BaseEndpointInfo) IP() string {
return utilproxy.IPPart(info.Endpoint) return utilproxy.IPPart(info.Endpoint)
@ -118,7 +126,7 @@ func (info *BaseEndpointInfo) Equal(other Endpoint) bool {
} }
func newBaseEndpointInfo(IP string, port int, isLocal bool, topology map[string]string, func newBaseEndpointInfo(IP string, port int, isLocal bool, topology map[string]string,
ready, serving, terminating bool) *BaseEndpointInfo { ready, serving, terminating bool, zoneHints sets.String) *BaseEndpointInfo {
return &BaseEndpointInfo{ return &BaseEndpointInfo{
Endpoint: net.JoinHostPort(IP, strconv.Itoa(port)), Endpoint: net.JoinHostPort(IP, strconv.Itoa(port)),
IsLocal: isLocal, IsLocal: isLocal,
@ -126,6 +134,7 @@ func newBaseEndpointInfo(IP string, port int, isLocal bool, topology map[string]
Ready: ready, Ready: ready,
Serving: serving, Serving: serving,
Terminating: terminating, Terminating: terminating,
ZoneHints: zoneHints,
} }
} }
@ -427,8 +436,10 @@ func (ect *EndpointChangeTracker) endpointsToEndpointsMap(endpoints *v1.Endpoint
isServing := true isServing := true
isTerminating := false isTerminating := false
isLocal := addr.NodeName != nil && *addr.NodeName == ect.hostname isLocal := addr.NodeName != nil && *addr.NodeName == ect.hostname
// Only supported with EndpointSlice API
zoneHints := sets.String{}
baseEndpointInfo := newBaseEndpointInfo(addr.IP, int(port.Port), isLocal, nil, isReady, isServing, isTerminating) baseEndpointInfo := newBaseEndpointInfo(addr.IP, int(port.Port), isLocal, nil, isReady, isServing, isTerminating, zoneHints)
if ect.makeEndpointInfo != nil { if ect.makeEndpointInfo != nil {
endpointsMap[svcPortName] = append(endpointsMap[svcPortName], ect.makeEndpointInfo(baseEndpointInfo)) endpointsMap[svcPortName] = append(endpointsMap[svcPortName], ect.makeEndpointInfo(baseEndpointInfo))
} else { } else {

View File

@ -194,7 +194,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -218,7 +218,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "port", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "port", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -241,7 +241,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -278,12 +278,12 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -307,7 +307,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -331,7 +331,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -355,7 +355,7 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -385,10 +385,10 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): {
{Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },
@ -418,10 +418,10 @@ func TestEndpointsToEndpointsMap(t *testing.T) {
}), }),
expected: map[ServicePortName][]*BaseEndpointInfo{ expected: map[ServicePortName][]*BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p1", v1.ProtocolTCP): {
{Endpoint: "[2001:db8:85a3:0:0:8a2e:370:7334]:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "[2001:db8:85a3:0:0:8a2e:370:7334]:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): { makeServicePortName("ns1", "ep1", "p2", v1.ProtocolTCP): {
{Endpoint: "[2001:db8:85a3:0:0:8a2e:370:7334]:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "[2001:db8:85a3:0:0:8a2e:370:7334]:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
}, },

View File

@ -26,8 +26,11 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1beta1" discovery "k8s.io/api/discovery/v1beta1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
utilproxy "k8s.io/kubernetes/pkg/proxy/util" utilproxy "k8s.io/kubernetes/pkg/proxy/util"
utilnet "k8s.io/utils/net" utilnet "k8s.io/utils/net"
) )
@ -78,6 +81,7 @@ type endpointInfo struct {
Addresses []string Addresses []string
NodeName *string NodeName *string
Topology map[string]string Topology map[string]string
ZoneHints sets.String
Ready bool Ready bool
Serving bool Serving bool
@ -136,6 +140,15 @@ func newEndpointSliceInfo(endpointSlice *discovery.EndpointSlice, remove bool) *
epInfo.NodeName = endpoint.NodeName epInfo.NodeName = endpoint.NodeName
if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) {
if endpoint.Hints != nil && len(endpoint.Hints.ForZones) > 0 {
epInfo.ZoneHints = sets.String{}
for _, zone := range endpoint.Hints.ForZones {
epInfo.ZoneHints.Insert(zone.Name)
}
}
}
esInfo.Endpoints = append(esInfo.Endpoints, epInfo) esInfo.Endpoints = append(esInfo.Endpoints, epInfo)
} }
@ -275,7 +288,7 @@ func (cache *EndpointSliceCache) addEndpointsByIP(serviceNN types.NamespacedName
} }
endpointInfo := newBaseEndpointInfo(endpoint.Addresses[0], portNum, isLocal, endpoint.Topology, endpointInfo := newBaseEndpointInfo(endpoint.Addresses[0], portNum, isLocal, endpoint.Topology,
endpoint.Ready, endpoint.Serving, endpoint.Terminating) endpoint.Ready, endpoint.Serving, endpoint.Terminating, endpoint.ZoneHints)
// This logic ensures we're deduping potential overlapping endpoints // This logic ensures we're deduping potential overlapping endpoints
// isLocal should not vary between matching IPs, but if it does, we // isLocal should not vary between matching IPs, but if it does, we

View File

@ -1020,22 +1020,12 @@ func (proxier *Proxier) syncProxyRules() {
allEndpoints := proxier.endpointsMap[svcName] allEndpoints := proxier.endpointsMap[svcName]
// Service Topology will not be enabled in the following cases: // Filtering for topology aware endpoints. This function will only
// 1. externalTrafficPolicy=Local (mutually exclusive with service topology). // filter endpoints if appropriate feature gates are enabled and the
// 2. ServiceTopology is not enabled. // Service does not have conflicting configuration such as
// 3. EndpointSlice is not enabled (service topology depends on endpoint slice // externalTrafficPolicy=Local.
// to get topology information). allEndpoints = proxy.FilterEndpoints(allEndpoints, svcInfo, proxier.nodeLabels)
if !svcInfo.NodeLocalExternal() && utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) && utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceProxying) {
allEndpoints = proxy.FilterTopologyEndpoint(proxier.nodeLabels, svcInfo.TopologyKeys(), allEndpoints)
}
// Service InternalTrafficPolicy is only enabled when all of the
// following are true:
// 1. InternalTrafficPolicy is Local
// 2. ServiceInternalTrafficPolicy feature gate is on
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceInternalTrafficPolicy) && svcInfo.NodeLocalInternal() {
allEndpoints = proxy.FilterLocalEndpoint(svcInfo.InternalTrafficPolicy(), allEndpoints)
}
readyEndpoints := make([]proxy.Endpoint, 0, len(allEndpoints)) readyEndpoints := make([]proxy.Endpoint, 0, len(allEndpoints))
for _, endpoint := range allEndpoints { for _, endpoint := range allEndpoints {
if !endpoint.IsReady() { if !endpoint.IsReady() {

View File

@ -2057,21 +2057,15 @@ func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNode
endpoints := proxier.endpointsMap[svcPortName] endpoints := proxier.endpointsMap[svcPortName]
// Service Topology will not be enabled in the following cases: // Filtering for topology aware endpoints. This function will only
// 1. externalTrafficPolicy=Local (mutually exclusive with service topology). // filter endpoints if appropriate feature gates are enabled and the
// 2. ServiceTopology is not enabled. // Service does not have conflicting configuration such as
// 3. EndpointSlice is not enabled (service topology depends on endpoint slice // externalTrafficPolicy=Local.
// to get topology information). svcInfo, ok := proxier.serviceMap[svcPortName]
if !onlyNodeLocalEndpoints && utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) && utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceProxying) { if !ok {
endpoints = proxy.FilterTopologyEndpoint(proxier.nodeLabels, proxier.serviceMap[svcPortName].TopologyKeys(), endpoints) klog.Warningf("Unable to filter endpoints due to missing Service info for %s", svcPortName)
} } else {
endpoints = proxy.FilterEndpoints(endpoints, svcInfo, proxier.nodeLabels)
// Service InternalTrafficPolicy is only enabled when all of the
// following are true:
// 1. InternalTrafficPolicy is PreferLocal or Local
// 2. ServiceInternalTrafficPolicy feature gate is on
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceInternalTrafficPolicy) && onlyNodeLocalEndpointsForInternal {
endpoints = proxy.FilterLocalEndpoint(proxier.serviceMap[svcPortName].InternalTrafficPolicy(), endpoints)
} }
for _, epInfo := range endpoints { for _, epInfo := range endpoints {

View File

@ -2952,12 +2952,12 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -2973,12 +2973,12 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -2996,18 +2996,18 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3023,24 +3023,24 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3060,54 +3060,54 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.4:13", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.4:13", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:14", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:14", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.4:14", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.4:14", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): {
{Endpoint: "2.2.2.1:21", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.1:21", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:21", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:21", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
{Endpoint: "2.2.2.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p13", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:13", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.4:13", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.4:13", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p14", v1.ProtocolUDP): {
{Endpoint: "1.1.1.3:14", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.3:14", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.4:14", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.4:14", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p21", v1.ProtocolUDP): {
{Endpoint: "2.2.2.1:21", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.1:21", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:21", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:21", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
{Endpoint: "2.2.2.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3127,7 +3127,7 @@ func Test_updateEndpointsMap(t *testing.T) {
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{}, oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{},
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3147,7 +3147,7 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{}, expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{},
@ -3167,17 +3167,17 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3197,17 +3197,17 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:11", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{{ expectedStaleEndpoints: []proxy.ServiceEndpoint{{
@ -3232,15 +3232,15 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},
@ -3260,15 +3260,15 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{{ expectedStaleEndpoints: []proxy.ServiceEndpoint{{
@ -3287,12 +3287,12 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11-2", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11-2", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{{ expectedStaleEndpoints: []proxy.ServiceEndpoint{{
@ -3313,12 +3313,12 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:22", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{{ expectedStaleEndpoints: []proxy.ServiceEndpoint{{
@ -3343,39 +3343,39 @@ func Test_updateEndpointsMap(t *testing.T) {
}, },
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p22", v1.ProtocolUDP): {
{Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.2:22", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "2.2.2.22:22", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.22:22", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns2", "ep2", "p23", v1.ProtocolUDP): { makeServicePortName("ns2", "ep2", "p23", v1.ProtocolUDP): {
{Endpoint: "2.2.2.3:23", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "2.2.2.3:23", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): { makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): {
{Endpoint: "4.4.4.4:44", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "4.4.4.4:44", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "4.4.4.5:44", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "4.4.4.5:44", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns4", "ep4", "p45", v1.ProtocolUDP): { makeServicePortName("ns4", "ep4", "p45", v1.ProtocolUDP): {
{Endpoint: "4.4.4.6:45", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "4.4.4.6:45", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p11", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
{Endpoint: "1.1.1.11:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.11:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p12", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:12", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns1", "ep1", "p122", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "p122", v1.ProtocolUDP): {
{Endpoint: "1.1.1.2:122", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.2:122", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns3", "ep3", "p33", v1.ProtocolUDP): { makeServicePortName("ns3", "ep3", "p33", v1.ProtocolUDP): {
{Endpoint: "3.3.3.3:33", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "3.3.3.3:33", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): { makeServicePortName("ns4", "ep4", "p44", v1.ProtocolUDP): {
{Endpoint: "4.4.4.4:44", IsLocal: true, Ready: true, Serving: true, Terminating: false}, {Endpoint: "4.4.4.4:44", IsLocal: true, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{{ expectedStaleEndpoints: []proxy.ServiceEndpoint{{
@ -3413,7 +3413,7 @@ func Test_updateEndpointsMap(t *testing.T) {
oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{}, oldEndpoints: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{},
expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{ expectedResult: map[proxy.ServicePortName][]*proxy.BaseEndpointInfo{
makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): { makeServicePortName("ns1", "ep1", "", v1.ProtocolUDP): {
{Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false}, {Endpoint: "1.1.1.1:11", IsLocal: false, Ready: true, Serving: true, Terminating: false, ZoneHints: sets.String{}},
}, },
}, },
expectedStaleEndpoints: []proxy.ServiceEndpoint{}, expectedStaleEndpoints: []proxy.ServiceEndpoint{},

View File

@ -55,6 +55,7 @@ type BaseServiceInfo struct {
nodeLocalInternal bool nodeLocalInternal bool
internalTrafficPolicy *v1.ServiceInternalTrafficPolicyType internalTrafficPolicy *v1.ServiceInternalTrafficPolicyType
topologyKeys []string topologyKeys []string
hintsAnnotation string
} }
var _ ServicePort = &BaseServiceInfo{} var _ ServicePort = &BaseServiceInfo{}
@ -138,6 +139,11 @@ func (info *BaseServiceInfo) TopologyKeys() []string {
return info.topologyKeys return info.topologyKeys
} }
// HintsAnnotation is part of ServicePort interface.
func (info *BaseServiceInfo) HintsAnnotation() string {
return info.hintsAnnotation
}
func (sct *ServiceChangeTracker) newBaseServiceInfo(port *v1.ServicePort, service *v1.Service) *BaseServiceInfo { func (sct *ServiceChangeTracker) newBaseServiceInfo(port *v1.ServicePort, service *v1.Service) *BaseServiceInfo {
nodeLocalExternal := false nodeLocalExternal := false
if apiservice.RequestsOnlyLocalTraffic(service) { if apiservice.RequestsOnlyLocalTraffic(service) {
@ -165,6 +171,7 @@ func (sct *ServiceChangeTracker) newBaseServiceInfo(port *v1.ServicePort, servic
nodeLocalInternal: nodeLocalInternal, nodeLocalInternal: nodeLocalInternal,
internalTrafficPolicy: service.Spec.InternalTrafficPolicy, internalTrafficPolicy: service.Spec.InternalTrafficPolicy,
topologyKeys: service.Spec.TopologyKeys, topologyKeys: service.Spec.TopologyKeys,
hintsAnnotation: service.Annotations[v1.AnnotationTopologyAwareHints],
} }
loadBalancerSourceRanges := make([]string, len(service.Spec.LoadBalancerSourceRanges)) loadBalancerSourceRanges := make([]string, len(service.Spec.LoadBalancerSourceRanges))

View File

@ -19,11 +19,80 @@ package proxy
import ( import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
) )
// FilterTopologyEndpoint returns the appropriate endpoints based on the cluster // FilterEndpoints filters endpoints based on Service configuration, node
// topology. // labels, and enabled feature gates. This is primarily used to enable topology
// aware routing.
func FilterEndpoints(endpoints []Endpoint, svcInfo ServicePort, nodeLabels map[string]string) []Endpoint {
if svcInfo.NodeLocalExternal() || !utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceProxying) {
return endpoints
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) {
return deprecatedTopologyFilter(nodeLabels, svcInfo.TopologyKeys(), endpoints)
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceInternalTrafficPolicy) && svcInfo.NodeLocalInternal() {
return filterEndpointsInternalTrafficPolicy(svcInfo.InternalTrafficPolicy(), endpoints)
}
if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) {
return filterEndpointsWithHints(endpoints, svcInfo.HintsAnnotation(), nodeLabels)
}
return endpoints
}
// filterEndpointsWithHints provides filtering based on the hints included in
// EndpointSlices. If any of the following are true, the full list of endpoints
// will be returned without any filtering:
// * The AnnotationTopologyAwareHints annotation is not set to "auto" for this
// Service.
// * No zone is specified in node labels.
// * No endpoints for this Service have a hint pointing to the zone this
// instance of kube-proxy is running in.
// * One or more endpoints for this Service do not have hints specified.
func filterEndpointsWithHints(endpoints []Endpoint, hintsAnnotation string, nodeLabels map[string]string) []Endpoint {
if hintsAnnotation != "auto" {
if hintsAnnotation != "" && hintsAnnotation != "disabled" {
klog.Warningf("Skipping topology aware endpoint filtering since Service has unexpected value for %s annotation: %s", v1.AnnotationTopologyAwareHints, hintsAnnotation)
}
return endpoints
}
zone, ok := nodeLabels[v1.LabelTopologyZone]
if !ok || zone == "" {
klog.Warningf("Skipping topology aware endpoint filtering since node is missing %s label", v1.LabelTopologyZone)
return endpoints
}
filteredEndpoints := []Endpoint{}
for _, endpoint := range endpoints {
if endpoint.GetZoneHints().Len() == 0 {
klog.Warningf("Skipping topology aware endpoint filtering since one or more endpoints is missing a zone hint")
return endpoints
}
if endpoint.GetZoneHints().Has(zone) {
filteredEndpoints = append(filteredEndpoints, endpoint)
}
}
if len(filteredEndpoints) > 0 {
klog.Warningf("Skipping topology aware endpoint filtering since no hints were provided for zone %s", zone)
return filteredEndpoints
}
return endpoints
}
// deprecatedTopologyFilter returns the appropriate endpoints based on the
// cluster topology. This will be removed in an upcoming release along with the
// ServiceTopology feature gate.
//
// This uses the current node's labels, which contain topology information, and // This uses the current node's labels, which contain topology information, and
// the required topologyKeys to find appropriate endpoints. If both the endpoint's // the required topologyKeys to find appropriate endpoints. If both the endpoint's
// topology and the current node have matching values for topologyKeys[0], the // topology and the current node have matching values for topologyKeys[0], the
@ -40,7 +109,7 @@ import (
// //
// If topologyKeys is not specified or empty, no topology constraints will be // If topologyKeys is not specified or empty, no topology constraints will be
// applied and this will return all endpoints. // applied and this will return all endpoints.
func FilterTopologyEndpoint(nodeLabels map[string]string, topologyKeys []string, endpoints []Endpoint) []Endpoint { func deprecatedTopologyFilter(nodeLabels map[string]string, topologyKeys []string, endpoints []Endpoint) []Endpoint {
// Do not filter endpoints if service has no topology keys. // Do not filter endpoints if service has no topology keys.
if len(topologyKeys) == 0 { if len(topologyKeys) == 0 {
return endpoints return endpoints
@ -81,13 +150,13 @@ func FilterTopologyEndpoint(nodeLabels map[string]string, topologyKeys []string,
return filteredEndpoints return filteredEndpoints
} }
// FilterLocalEndpoint returns the node local endpoints based on configured // filterEndpointsInternalTrafficPolicy returns the node local endpoints based
// InternalTrafficPolicy. // on configured InternalTrafficPolicy.
// //
// If ServiceInternalTrafficPolicy feature gate is off, returns the original // If ServiceInternalTrafficPolicy feature gate is off, returns the original
// endpoints slice. // EndpointSlice.
// Otherwise, if InternalTrafficPolicy is Local, only return the node local endpoints. // Otherwise, if InternalTrafficPolicy is Local, only return the node local endpoints.
func FilterLocalEndpoint(internalTrafficPolicy *v1.ServiceInternalTrafficPolicyType, endpoints []Endpoint) []Endpoint { func filterEndpointsInternalTrafficPolicy(internalTrafficPolicy *v1.ServiceInternalTrafficPolicyType, endpoints []Endpoint) []Endpoint {
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceInternalTrafficPolicy) { if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceInternalTrafficPolicy) {
return endpoints return endpoints
} }

View File

@ -22,12 +22,328 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing" featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
) )
func TestFilterTopologyEndpoint(t *testing.T) { func TestFilterEndpoints(t *testing.T) {
type endpoint struct {
ip string
zoneHints sets.String
}
testCases := []struct {
name string
epsProxyingEnabled bool
serviceTopologyEnabled bool
hintsEnabled bool
nodeLabels map[string]string
serviceInfo ServicePort
endpoints []endpoint
expectedEndpoints []endpoint
}{{
name: "hints + eps proxying enabled, hints annotation == auto",
hintsEnabled: true,
epsProxyingEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: false, hintsAnnotation: "auto"},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hints + eps proxying enabled, hints annotation == disabled, hints ignored",
hintsEnabled: true,
epsProxyingEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: false, hintsAnnotation: "disabled"},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hints + eps proxying enabled, hints annotation == Auto (wrong capitalization), hints ignored",
hintsEnabled: true,
epsProxyingEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: false, hintsAnnotation: "Auto"},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hints + eps proxying enabled, hints annotation empty, hints ignored",
hintsEnabled: true,
epsProxyingEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: false},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hints enabled, eps proxying not, hints are ignored",
hintsEnabled: true,
epsProxyingEnabled: false,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: false},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "node local endpoints, hints are ignored",
hintsEnabled: true,
epsProxyingEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: true},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "all gates enabled, serviceTopology gate takes precedence and hints are ignored",
hintsEnabled: true,
epsProxyingEnabled: true,
serviceTopologyEnabled: true,
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
serviceInfo: &BaseServiceInfo{nodeLocalExternal: true},
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}}
endpointsToStringArray := func(endpoints []Endpoint) []string {
result := make([]string, 0, len(endpoints))
for _, ep := range endpoints {
result = append(result, ep.String())
}
return result
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceProxying, tc.epsProxyingEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceTopology, tc.serviceTopologyEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, tc.hintsEnabled)()
endpoints := []Endpoint{}
for _, ep := range tc.endpoints {
endpoints = append(endpoints, &BaseEndpointInfo{Endpoint: ep.ip, ZoneHints: ep.zoneHints})
}
expectedEndpoints := []Endpoint{}
for _, ep := range tc.expectedEndpoints {
expectedEndpoints = append(expectedEndpoints, &BaseEndpointInfo{Endpoint: ep.ip, ZoneHints: ep.zoneHints})
}
filteredEndpoints := FilterEndpoints(endpoints, tc.serviceInfo, tc.nodeLabels)
if len(filteredEndpoints) != len(expectedEndpoints) {
t.Errorf("expected %d filtered endpoints, got %d", len(expectedEndpoints), len(filteredEndpoints))
}
if !reflect.DeepEqual(filteredEndpoints, expectedEndpoints) {
t.Errorf("expected %v, got %v", endpointsToStringArray(expectedEndpoints), endpointsToStringArray(filteredEndpoints))
}
})
}
}
func Test_filterEndpointsWithHints(t *testing.T) {
type endpoint struct {
ip string
zoneHints sets.String
}
testCases := []struct {
name string
nodeLabels map[string]string
hintsAnnotation string
endpoints []endpoint
expectedEndpoints []endpoint
}{{
name: "empty node labels",
nodeLabels: map[string]string{},
hintsAnnotation: "auto",
endpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
expectedEndpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
}, {
name: "empty zone label",
nodeLabels: map[string]string{v1.LabelTopologyZone: ""},
hintsAnnotation: "auto",
endpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
expectedEndpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
}, {
name: "node in different zone, no endpoint filtering",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-b"},
hintsAnnotation: "auto",
endpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
expectedEndpoints: []endpoint{{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")}},
}, {
name: "normal endpoint filtering",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
hintsAnnotation: "auto",
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hintsAnnotation empty, no filtering applied",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
hintsAnnotation: "",
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "hintsAnnotation disabled, no filtering applied",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
hintsAnnotation: "disabled",
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "missing hints, no filtering applied",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"},
hintsAnnotation: "auto",
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5"},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b")},
{ip: "10.1.2.5"},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-a")},
},
}, {
name: "multiple hints per endpoint, filtering includes any endpoint with zone included",
nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-c"},
hintsAnnotation: "auto",
endpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a", "zone-b", "zone-c")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b", "zone-c")},
{ip: "10.1.2.5", zoneHints: sets.NewString("zone-b", "zone-d")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-c")},
},
expectedEndpoints: []endpoint{
{ip: "10.1.2.3", zoneHints: sets.NewString("zone-a", "zone-b", "zone-c")},
{ip: "10.1.2.4", zoneHints: sets.NewString("zone-b", "zone-c")},
{ip: "10.1.2.6", zoneHints: sets.NewString("zone-c")},
},
}}
endpointsToStringArray := func(endpoints []Endpoint) []string {
result := make([]string, 0, len(endpoints))
for _, ep := range endpoints {
result = append(result, ep.String())
}
return result
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
endpoints := []Endpoint{}
for _, ep := range tc.endpoints {
endpoints = append(endpoints, &BaseEndpointInfo{Endpoint: ep.ip, ZoneHints: ep.zoneHints})
}
expectedEndpoints := []Endpoint{}
for _, ep := range tc.expectedEndpoints {
expectedEndpoints = append(expectedEndpoints, &BaseEndpointInfo{Endpoint: ep.ip, ZoneHints: ep.zoneHints})
}
filteredEndpoints := filterEndpointsWithHints(endpoints, tc.hintsAnnotation, tc.nodeLabels)
if len(filteredEndpoints) != len(expectedEndpoints) {
t.Errorf("expected %d filtered endpoints, got %d", len(expectedEndpoints), len(filteredEndpoints))
}
if !reflect.DeepEqual(filteredEndpoints, expectedEndpoints) {
t.Errorf("expected %v, got %v", endpointsToStringArray(expectedEndpoints), endpointsToStringArray(filteredEndpoints))
}
})
}
}
func Test_deprecatedTopologyFilter(t *testing.T) {
type endpoint struct { type endpoint struct {
Endpoint string Endpoint string
NodeName types.NodeName NodeName types.NodeName
@ -470,7 +786,7 @@ func TestFilterTopologyEndpoint(t *testing.T) {
} }
currentNodeLabels := tc.nodeLabels[tc.currentNodeName] currentNodeLabels := tc.nodeLabels[tc.currentNodeName]
filteredEndpoint := []endpoint{} filteredEndpoint := []endpoint{}
for _, ep := range FilterTopologyEndpoint(currentNodeLabels, tc.topologyKeys, endpoints) { for _, ep := range deprecatedTopologyFilter(currentNodeLabels, tc.topologyKeys, endpoints) {
filteredEndpoint = append(filteredEndpoint, m[ep]) filteredEndpoint = append(filteredEndpoint, m[ep])
} }
if !reflect.DeepEqual(filteredEndpoint, tc.expected) { if !reflect.DeepEqual(filteredEndpoint, tc.expected) {
@ -480,7 +796,7 @@ func TestFilterTopologyEndpoint(t *testing.T) {
} }
} }
func TestFilterLocalEndpoint(t *testing.T) { func Test_filterEndpointsInternalTrafficPolicy(t *testing.T) {
cluster := v1.ServiceInternalTrafficPolicyCluster cluster := v1.ServiceInternalTrafficPolicyCluster
local := v1.ServiceInternalTrafficPolicyLocal local := v1.ServiceInternalTrafficPolicyLocal
@ -566,7 +882,7 @@ func TestFilterLocalEndpoint(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceInternalTrafficPolicy, tc.featureGateOn)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceInternalTrafficPolicy, tc.featureGateOn)()
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
filteredEndpoint := FilterLocalEndpoint(tc.internalTrafficPolicy, tc.endpoints) filteredEndpoint := filterEndpointsInternalTrafficPolicy(tc.internalTrafficPolicy, tc.endpoints)
if !reflect.DeepEqual(filteredEndpoint, tc.expected) { if !reflect.DeepEqual(filteredEndpoint, tc.expected) {
t.Errorf("expected %v, got %v", tc.expected, filteredEndpoint) t.Errorf("expected %v, got %v", tc.expected, filteredEndpoint)
} }

View File

@ -22,6 +22,7 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/config"
) )
@ -91,6 +92,8 @@ type ServicePort interface {
InternalTrafficPolicy() *v1.ServiceInternalTrafficPolicyType InternalTrafficPolicy() *v1.ServiceInternalTrafficPolicyType
// TopologyKeys returns service TopologyKeys as a string array. // TopologyKeys returns service TopologyKeys as a string array.
TopologyKeys() []string TopologyKeys() []string
// HintsAnnotation returns the value of the v1.AnnotationTopologyAwareHints annotation.
HintsAnnotation() string
} }
// Endpoint in an interface which abstracts information about an endpoint. // Endpoint in an interface which abstracts information about an endpoint.
@ -117,6 +120,9 @@ type Endpoint interface {
IsTerminating() bool IsTerminating() bool
// GetTopology returns the topology information of the endpoint. // GetTopology returns the topology information of the endpoint.
GetTopology() map[string]string GetTopology() map[string]string
// GetZoneHint returns the zone hint for the endpoint. This is based on
// endpoint.hints.forZones[0].name in the EndpointSlice API.
GetZoneHints() sets.String
// IP returns IP part of the endpoint. // IP returns IP part of the endpoint.
IP() string IP() string
// Port returns the Port part of the endpoint. // Port returns the Port part of the endpoint.

View File

@ -37,6 +37,7 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
@ -201,6 +202,11 @@ func (info *endpointsInfo) GetTopology() map[string]string {
return nil return nil
} }
// GetZoneHint returns the zone hint for the endpoint.
func (info *endpointsInfo) GetZoneHints() sets.String {
return sets.String{}
}
// IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface. // IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface.
func (info *endpointsInfo) IP() string { func (info *endpointsInfo) IP() string {
return info.ip return info.ip

View File

@ -111,13 +111,17 @@ func (endpointSliceStrategy) AllowUnconditionalUpdate() bool {
// dropDisabledConditionsOnCreate will drop any fields that are disabled. // dropDisabledConditionsOnCreate will drop any fields that are disabled.
func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) { func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) {
dropTerminating := !utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceTerminatingCondition) dropTerminating := !utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceTerminatingCondition)
dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints)
if dropTerminating { if dropHints || dropTerminating {
for i := range endpointSlice.Endpoints { for i := range endpointSlice.Endpoints {
if dropTerminating { if dropTerminating {
endpointSlice.Endpoints[i].Conditions.Serving = nil endpointSlice.Endpoints[i].Conditions.Serving = nil
endpointSlice.Endpoints[i].Conditions.Terminating = nil endpointSlice.Endpoints[i].Conditions.Terminating = nil
} }
if dropHints {
endpointSlice.Endpoints[i].Hints = nil
}
} }
} }
} }
@ -135,12 +139,25 @@ func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) {
} }
} }
if dropTerminating { dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints)
if dropHints {
for _, ep := range oldEPS.Endpoints {
if ep.Hints != nil {
dropHints = false
break
}
}
}
if dropHints || dropTerminating {
for i := range newEPS.Endpoints { for i := range newEPS.Endpoints {
if dropTerminating { if dropTerminating {
newEPS.Endpoints[i].Conditions.Serving = nil newEPS.Endpoints[i].Conditions.Serving = nil
newEPS.Endpoints[i].Conditions.Terminating = nil newEPS.Endpoints[i].Conditions.Terminating = nil
} }
if dropHints {
newEPS.Endpoints[i].Hints = nil
}
} }
} }
} }

View File

@ -35,6 +35,7 @@ func Test_dropDisabledFieldsOnCreate(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
terminatingGateEnabled bool terminatingGateEnabled bool
hintsGateEnabled bool
eps *discovery.EndpointSlice eps *discovery.EndpointSlice
expectedEPS *discovery.EndpointSlice expectedEPS *discovery.EndpointSlice
}{ }{
@ -162,6 +163,7 @@ func Test_dropDisabledFieldsOnCreate(t *testing.T) {
for _, testcase := range testcases { for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) { t.Run(testcase.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGateEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGateEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, testcase.hintsGateEnabled)()
dropDisabledFieldsOnCreate(testcase.eps) dropDisabledFieldsOnCreate(testcase.eps)
if !apiequality.Semantic.DeepEqual(testcase.eps, testcase.expectedEPS) { if !apiequality.Semantic.DeepEqual(testcase.eps, testcase.expectedEPS) {
@ -177,6 +179,7 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
terminatingGateEnabled bool terminatingGateEnabled bool
hintsGateEnabled bool
oldEPS *discovery.EndpointSlice oldEPS *discovery.EndpointSlice
newEPS *discovery.EndpointSlice newEPS *discovery.EndpointSlice
expectedEPS *discovery.EndpointSlice expectedEPS *discovery.EndpointSlice
@ -524,11 +527,138 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) {
}, },
}, },
}, },
{
name: "hints gate enabled, set on new EPS",
hintsGateEnabled: true,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: nil,
},
{
Hints: nil,
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b"}},
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b"}},
},
},
},
},
},
{
name: "hints gate disabled, set on new EPS",
hintsGateEnabled: false,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: nil,
},
{
Hints: nil,
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b"}},
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: nil,
},
{
Hints: nil,
},
},
},
},
{
name: "hints gate disabled, set on new and old EPS",
hintsGateEnabled: false,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a-old"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b-old"}},
},
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b"}},
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-a"}},
},
},
{
Hints: &discovery.EndpointHints{
ForZones: []discovery.ForZone{{Name: "zone-b"}},
},
},
},
},
},
} }
for _, testcase := range testcases { for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) { t.Run(testcase.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGateEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGateEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, testcase.hintsGateEnabled)()
dropDisabledFieldsOnUpdate(testcase.oldEPS, testcase.newEPS) dropDisabledFieldsOnUpdate(testcase.oldEPS, testcase.newEPS)
if !apiequality.Semantic.DeepEqual(testcase.newEPS, testcase.expectedEPS) { if !apiequality.Semantic.DeepEqual(testcase.newEPS, testcase.expectedEPS) {

View File

@ -138,4 +138,9 @@ const (
// //
// This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled. // This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled.
PodDeletionCost = "controller.kubernetes.io/pod-deletion-cost" 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

@ -102,10 +102,38 @@ func (m *EndpointConditions) XXX_DiscardUnknown() {
var xxx_messageInfo_EndpointConditions proto.InternalMessageInfo var xxx_messageInfo_EndpointConditions proto.InternalMessageInfo
func (m *EndpointHints) Reset() { *m = EndpointHints{} }
func (*EndpointHints) ProtoMessage() {}
func (*EndpointHints) Descriptor() ([]byte, []int) {
return fileDescriptor_3a5d310fb1396ddf, []int{2}
}
func (m *EndpointHints) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *EndpointHints) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *EndpointHints) XXX_Merge(src proto.Message) {
xxx_messageInfo_EndpointHints.Merge(m, src)
}
func (m *EndpointHints) XXX_Size() int {
return m.Size()
}
func (m *EndpointHints) XXX_DiscardUnknown() {
xxx_messageInfo_EndpointHints.DiscardUnknown(m)
}
var xxx_messageInfo_EndpointHints proto.InternalMessageInfo
func (m *EndpointPort) Reset() { *m = EndpointPort{} } func (m *EndpointPort) Reset() { *m = EndpointPort{} }
func (*EndpointPort) ProtoMessage() {} func (*EndpointPort) ProtoMessage() {}
func (*EndpointPort) Descriptor() ([]byte, []int) { func (*EndpointPort) Descriptor() ([]byte, []int) {
return fileDescriptor_3a5d310fb1396ddf, []int{2} return fileDescriptor_3a5d310fb1396ddf, []int{3}
} }
func (m *EndpointPort) XXX_Unmarshal(b []byte) error { func (m *EndpointPort) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -133,7 +161,7 @@ var xxx_messageInfo_EndpointPort proto.InternalMessageInfo
func (m *EndpointSlice) Reset() { *m = EndpointSlice{} } func (m *EndpointSlice) Reset() { *m = EndpointSlice{} }
func (*EndpointSlice) ProtoMessage() {} func (*EndpointSlice) ProtoMessage() {}
func (*EndpointSlice) Descriptor() ([]byte, []int) { func (*EndpointSlice) Descriptor() ([]byte, []int) {
return fileDescriptor_3a5d310fb1396ddf, []int{3} return fileDescriptor_3a5d310fb1396ddf, []int{4}
} }
func (m *EndpointSlice) XXX_Unmarshal(b []byte) error { func (m *EndpointSlice) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -161,7 +189,7 @@ var xxx_messageInfo_EndpointSlice proto.InternalMessageInfo
func (m *EndpointSliceList) Reset() { *m = EndpointSliceList{} } func (m *EndpointSliceList) Reset() { *m = EndpointSliceList{} }
func (*EndpointSliceList) ProtoMessage() {} func (*EndpointSliceList) ProtoMessage() {}
func (*EndpointSliceList) Descriptor() ([]byte, []int) { func (*EndpointSliceList) Descriptor() ([]byte, []int) {
return fileDescriptor_3a5d310fb1396ddf, []int{4} return fileDescriptor_3a5d310fb1396ddf, []int{5}
} }
func (m *EndpointSliceList) XXX_Unmarshal(b []byte) error { func (m *EndpointSliceList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -186,13 +214,43 @@ func (m *EndpointSliceList) XXX_DiscardUnknown() {
var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo
func (m *ForZone) Reset() { *m = ForZone{} }
func (*ForZone) ProtoMessage() {}
func (*ForZone) Descriptor() ([]byte, []int) {
return fileDescriptor_3a5d310fb1396ddf, []int{6}
}
func (m *ForZone) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ForZone) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *ForZone) XXX_Merge(src proto.Message) {
xxx_messageInfo_ForZone.Merge(m, src)
}
func (m *ForZone) XXX_Size() int {
return m.Size()
}
func (m *ForZone) XXX_DiscardUnknown() {
xxx_messageInfo_ForZone.DiscardUnknown(m)
}
var xxx_messageInfo_ForZone proto.InternalMessageInfo
func init() { func init() {
proto.RegisterType((*Endpoint)(nil), "k8s.io.api.discovery.v1.Endpoint") proto.RegisterType((*Endpoint)(nil), "k8s.io.api.discovery.v1.Endpoint")
proto.RegisterMapType((map[string]string)(nil), "k8s.io.api.discovery.v1.Endpoint.DeprecatedTopologyEntry") proto.RegisterMapType((map[string]string)(nil), "k8s.io.api.discovery.v1.Endpoint.DeprecatedTopologyEntry")
proto.RegisterType((*EndpointConditions)(nil), "k8s.io.api.discovery.v1.EndpointConditions") proto.RegisterType((*EndpointConditions)(nil), "k8s.io.api.discovery.v1.EndpointConditions")
proto.RegisterType((*EndpointHints)(nil), "k8s.io.api.discovery.v1.EndpointHints")
proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1.EndpointPort") proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1.EndpointPort")
proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1.EndpointSlice") proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1.EndpointSlice")
proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1.EndpointSliceList") proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1.EndpointSliceList")
proto.RegisterType((*ForZone)(nil), "k8s.io.api.discovery.v1.ForZone")
} }
func init() { func init() {
@ -200,59 +258,63 @@ func init() {
} }
var fileDescriptor_3a5d310fb1396ddf = []byte{ var fileDescriptor_3a5d310fb1396ddf = []byte{
// 823 bytes of a gzipped FileDescriptorProto // 889 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xcf, 0x6f, 0xe3, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x4d, 0x6f, 0xe3, 0x44,
0x14, 0x8e, 0x9b, 0x86, 0xda, 0x93, 0xad, 0xd8, 0x1d, 0x21, 0x6d, 0x14, 0x90, 0x5d, 0x82, 0x16, 0x18, 0x8e, 0x9b, 0x86, 0xda, 0x93, 0x56, 0xec, 0x8e, 0x90, 0x36, 0x0a, 0x28, 0x0e, 0x46, 0x8b,
0x45, 0xaa, 0xb0, 0x69, 0x85, 0xd0, 0xc2, 0x89, 0x9a, 0xad, 0xf8, 0xbd, 0x54, 0xb3, 0x3d, 0xad, 0x22, 0x55, 0xd8, 0xb4, 0x42, 0x68, 0xe1, 0x44, 0xcd, 0x96, 0x5d, 0xbe, 0x4a, 0x35, 0xdb, 0xd3,
0x90, 0x60, 0x6a, 0xbf, 0x75, 0x4d, 0xe2, 0x19, 0x6b, 0x66, 0x12, 0x29, 0x9c, 0xb8, 0x70, 0x86, 0x0a, 0x69, 0x71, 0xed, 0xb7, 0x8e, 0x49, 0x33, 0x63, 0xcd, 0x4c, 0x22, 0x85, 0x13, 0x17, 0xce,
0xff, 0x83, 0xff, 0x81, 0x23, 0xea, 0x71, 0x6f, 0xec, 0xc9, 0xa2, 0xe6, 0xbf, 0xd8, 0x13, 0x9a, 0xf0, 0x8b, 0x38, 0xa2, 0x1e, 0xf7, 0xc6, 0x9e, 0x2c, 0x6a, 0xfe, 0x02, 0xa7, 0x3d, 0xa1, 0x19,
0xb1, 0x1d, 0x7b, 0x49, 0xab, 0x70, 0xf3, 0x7c, 0xef, 0x7d, 0xdf, 0x7b, 0xef, 0x9b, 0x79, 0x46, 0x7f, 0x96, 0xb4, 0x0a, 0xb7, 0x99, 0x67, 0x9e, 0xe7, 0xfd, 0x78, 0x66, 0xe6, 0x45, 0x9f, 0xcd,
0x9f, 0xcc, 0x1e, 0x4a, 0x3f, 0xe5, 0xc1, 0x6c, 0x71, 0x01, 0x82, 0x81, 0x02, 0x19, 0x2c, 0x81, 0x1e, 0x09, 0x37, 0x61, 0xde, 0x6c, 0x71, 0x0e, 0x9c, 0x82, 0x04, 0xe1, 0x2d, 0x81, 0x46, 0x8c,
0xc5, 0x5c, 0x04, 0x75, 0x80, 0xe6, 0x69, 0x10, 0xa7, 0x32, 0xe2, 0x4b, 0x10, 0xab, 0x60, 0x79, 0x7b, 0xe5, 0x41, 0x90, 0x26, 0x5e, 0x94, 0x88, 0x90, 0x2d, 0x81, 0xaf, 0xbc, 0xe5, 0x81, 0x17,
0x14, 0x24, 0xc0, 0x40, 0x50, 0x05, 0xb1, 0x9f, 0x0b, 0xae, 0x38, 0xbe, 0x5f, 0x25, 0xfa, 0x34, 0x03, 0x05, 0x1e, 0x48, 0x88, 0xdc, 0x94, 0x33, 0xc9, 0xf0, 0x83, 0x82, 0xe8, 0x06, 0x69, 0xe2,
0x4f, 0xfd, 0x75, 0xa2, 0xbf, 0x3c, 0x1a, 0xbf, 0x97, 0xa4, 0xea, 0x72, 0x71, 0xe1, 0x47, 0x3c, 0xd6, 0x44, 0x77, 0x79, 0x30, 0xfc, 0x20, 0x4e, 0xe4, 0x74, 0x71, 0xee, 0x86, 0x6c, 0xee, 0xc5,
0x0b, 0x12, 0x9e, 0xf0, 0xc0, 0xe4, 0x5f, 0x2c, 0x9e, 0x99, 0x93, 0x39, 0x98, 0xaf, 0x4a, 0x67, 0x2c, 0x66, 0x9e, 0xe6, 0x9f, 0x2f, 0x2e, 0xf4, 0x4e, 0x6f, 0xf4, 0xaa, 0x88, 0x33, 0x74, 0x5a,
0x3c, 0xe9, 0x14, 0x8c, 0xb8, 0x80, 0x1b, 0x6a, 0x8d, 0x3f, 0x68, 0x73, 0x32, 0x1a, 0x5d, 0xa6, 0x09, 0x43, 0xc6, 0xe1, 0x96, 0x5c, 0xc3, 0x8f, 0x1a, 0xce, 0x3c, 0x08, 0xa7, 0x09, 0x55, 0x35,
0x4c, 0xf7, 0x94, 0xcf, 0x12, 0x0d, 0xc8, 0x20, 0x03, 0x45, 0x6f, 0x62, 0x05, 0xb7, 0xb1, 0xc4, 0xa5, 0xb3, 0x58, 0x01, 0xc2, 0x9b, 0x83, 0x0c, 0x6e, 0x53, 0x79, 0x77, 0xa9, 0xf8, 0x82, 0xca,
0x82, 0xa9, 0x34, 0x83, 0x0d, 0xc2, 0x87, 0xdb, 0x08, 0x32, 0xba, 0x84, 0x8c, 0xfe, 0x97, 0x37, 0x64, 0x0e, 0x6b, 0x82, 0x8f, 0x37, 0x09, 0x44, 0x38, 0x85, 0x79, 0xf0, 0x5f, 0x9d, 0xf3, 0xcf,
0xf9, 0x7d, 0x17, 0xd9, 0xa7, 0x2c, 0xce, 0x79, 0xca, 0x14, 0x3e, 0x44, 0x0e, 0x8d, 0x63, 0x01, 0x36, 0x32, 0x8f, 0x69, 0x94, 0xb2, 0x84, 0x4a, 0xbc, 0x8f, 0xac, 0x20, 0x8a, 0x38, 0x08, 0x01,
0x52, 0x82, 0x1c, 0x59, 0x07, 0xfd, 0xa9, 0x13, 0xee, 0x97, 0x85, 0xe7, 0x9c, 0x34, 0x20, 0x69, 0x62, 0x60, 0x8c, 0xbb, 0x13, 0xcb, 0xdf, 0xcb, 0x33, 0xdb, 0x3a, 0xaa, 0x40, 0xd2, 0x9c, 0xe3,
0xe3, 0xf8, 0x7b, 0x84, 0x22, 0xce, 0xe2, 0x54, 0xa5, 0x9c, 0xc9, 0xd1, 0xce, 0x81, 0x35, 0x1d, 0x17, 0x08, 0x85, 0x8c, 0x46, 0x89, 0x4c, 0x18, 0x15, 0x83, 0xad, 0xb1, 0x31, 0xe9, 0x1f, 0xee,
0x1e, 0x1f, 0xfa, 0xb7, 0x38, 0xeb, 0x37, 0x35, 0x3e, 0x5d, 0x53, 0x42, 0x7c, 0x55, 0x78, 0xbd, 0xbb, 0x77, 0x38, 0xeb, 0x56, 0x39, 0x3e, 0xaf, 0x25, 0x3e, 0xbe, 0xca, 0xec, 0x4e, 0x9e, 0xd9,
0xb2, 0xf0, 0x50, 0x8b, 0x91, 0x8e, 0x24, 0x9e, 0x22, 0xfb, 0x92, 0x4b, 0xc5, 0x68, 0x06, 0xa3, 0xa8, 0xc1, 0x48, 0x2b, 0x24, 0x9e, 0x20, 0x73, 0xca, 0x84, 0xa4, 0xc1, 0x1c, 0x06, 0xdd, 0xb1,
0xfe, 0x81, 0x35, 0x75, 0xc2, 0x3b, 0x65, 0xe1, 0xd9, 0x9f, 0xd7, 0x18, 0x59, 0x47, 0xf1, 0x19, 0x31, 0xb1, 0xfc, 0xdd, 0x3c, 0xb3, 0xcd, 0xa7, 0x25, 0x46, 0xea, 0x53, 0x7c, 0x8a, 0x2c, 0x19,
0x72, 0x14, 0x15, 0x09, 0x28, 0x02, 0xcf, 0x46, 0xbb, 0xa6, 0x93, 0x77, 0xba, 0x9d, 0xe8, 0xbb, 0xf0, 0x18, 0x24, 0x81, 0x8b, 0xc1, 0xb6, 0xae, 0xe4, 0xbd, 0x76, 0x25, 0xea, 0x6e, 0x54, 0x11,
0xd1, 0x4d, 0x7c, 0x7b, 0xf1, 0x23, 0x44, 0x3a, 0x09, 0x04, 0xb0, 0x08, 0xaa, 0xe1, 0xce, 0x1b, 0xdf, 0x9d, 0xff, 0x08, 0xa1, 0x22, 0x01, 0x07, 0x1a, 0x42, 0xd1, 0xdc, 0x59, 0xa5, 0x24, 0x4d,
0x26, 0x69, 0x45, 0xf0, 0x2f, 0x16, 0xc2, 0x31, 0xe4, 0x02, 0x22, 0xed, 0xd5, 0x39, 0xcf, 0xf9, 0x10, 0xfc, 0x8b, 0x81, 0x70, 0x04, 0x29, 0x87, 0x50, 0x79, 0x75, 0xc6, 0x52, 0x76, 0xc9, 0xe2,
0x9c, 0x27, 0xab, 0xd1, 0xe0, 0xa0, 0x3f, 0x1d, 0x1e, 0x7f, 0xb4, 0x75, 0x4a, 0xff, 0xd1, 0x06, 0xd5, 0xa0, 0x37, 0xee, 0x4e, 0xfa, 0x87, 0x9f, 0x6c, 0xec, 0xd2, 0x7d, 0xbc, 0xa6, 0x3d, 0xa6,
0xf7, 0x94, 0x29, 0xb1, 0x0a, 0xc7, 0xf5, 0xcc, 0x78, 0x33, 0x81, 0xdc, 0x50, 0x50, 0x7b, 0xc0, 0x92, 0xaf, 0xfc, 0x61, 0xd9, 0x33, 0x5e, 0x27, 0x90, 0x5b, 0x12, 0x2a, 0x0f, 0x28, 0x8b, 0xe0,
0x78, 0x0c, 0x8f, 0xb5, 0x07, 0xaf, 0xb5, 0x1e, 0x3c, 0xae, 0x31, 0xb2, 0x8e, 0xe2, 0xb7, 0xd0, 0x44, 0x79, 0xf0, 0x46, 0xe3, 0xc1, 0x49, 0x89, 0x91, 0xfa, 0x14, 0xbf, 0x83, 0xb6, 0x7f, 0x62,
0xee, 0x4f, 0x9c, 0xc1, 0x68, 0xcf, 0x64, 0xd9, 0x65, 0xe1, 0xed, 0x3e, 0xe5, 0x0c, 0x88, 0x41, 0x14, 0x06, 0x3b, 0x9a, 0x65, 0xe6, 0x99, 0xbd, 0xfd, 0x9c, 0x51, 0x20, 0x1a, 0xc5, 0x4f, 0x50,
0xc7, 0xa7, 0xe8, 0xfe, 0x2d, 0x2d, 0xe1, 0xbb, 0xa8, 0x3f, 0x83, 0xd5, 0xc8, 0xd2, 0x3c, 0xa2, 0x6f, 0x9a, 0x50, 0x29, 0x06, 0xa6, 0x76, 0xe7, 0xfd, 0x8d, 0x1d, 0x3c, 0x55, 0x6c, 0xdf, 0xca,
0x3f, 0xf1, 0x1b, 0x68, 0xb0, 0xa4, 0xf3, 0x05, 0x98, 0x4b, 0x75, 0x48, 0x75, 0xf8, 0x78, 0xe7, 0x33, 0xbb, 0xa7, 0x97, 0xa4, 0xd0, 0x0f, 0x8f, 0xd1, 0x83, 0x3b, 0x7a, 0xc3, 0xf7, 0x50, 0x77,
0xa1, 0x35, 0xf9, 0xd5, 0x42, 0x78, 0xf3, 0x26, 0xb1, 0x87, 0x06, 0x02, 0x68, 0x5c, 0x89, 0xd8, 0x06, 0xab, 0x81, 0xa1, 0x0a, 0x20, 0x6a, 0x89, 0xdf, 0x42, 0xbd, 0x65, 0x70, 0xb9, 0x00, 0xfd,
0xa1, 0x53, 0x16, 0xde, 0x80, 0x68, 0x80, 0x54, 0x38, 0x7e, 0x80, 0xf6, 0x24, 0x88, 0x65, 0xca, 0x3a, 0x2c, 0x52, 0x6c, 0x3e, 0xdd, 0x7a, 0x64, 0x38, 0xbf, 0x1a, 0x08, 0xaf, 0x3f, 0x09, 0x6c,
0x12, 0xa3, 0x69, 0x87, 0xc3, 0xb2, 0xf0, 0xf6, 0x9e, 0x54, 0x10, 0x69, 0x62, 0xf8, 0x08, 0x0d, 0xa3, 0x1e, 0x87, 0x20, 0x2a, 0x82, 0x98, 0x45, 0x7a, 0xa2, 0x00, 0x52, 0xe0, 0xf8, 0x21, 0xda,
0x15, 0x88, 0x2c, 0x65, 0x54, 0xe9, 0xd4, 0xbe, 0x49, 0x7d, 0xbd, 0x2c, 0xbc, 0xe1, 0x79, 0x0b, 0x11, 0xc0, 0x97, 0x09, 0x8d, 0x75, 0x4c, 0xd3, 0xef, 0xe7, 0x99, 0xbd, 0xf3, 0xac, 0x80, 0x48,
0x93, 0x6e, 0xce, 0xe4, 0x4f, 0x0b, 0xdd, 0x69, 0x3a, 0x3a, 0xe3, 0x42, 0x69, 0x1f, 0xcc, 0x8b, 0x75, 0x86, 0x0f, 0x50, 0x5f, 0x02, 0x9f, 0x27, 0x34, 0x90, 0x8a, 0xda, 0xd5, 0xd4, 0x37, 0xf3,
0xb1, 0x5a, 0x1f, 0x8c, 0x53, 0x06, 0xc5, 0x9f, 0x21, 0xdb, 0xbc, 0xfb, 0x88, 0xcf, 0xab, 0xe9, 0xcc, 0xee, 0x9f, 0x35, 0x30, 0x69, 0x73, 0x9c, 0x17, 0x68, 0xef, 0x46, 0xef, 0xf8, 0x04, 0x99,
0xc2, 0x43, 0xed, 0xe7, 0x59, 0x8d, 0xbd, 0x2c, 0xbc, 0x37, 0x37, 0x77, 0xda, 0x6f, 0xc2, 0x64, 0x17, 0x8c, 0x2b, 0x0f, 0x8b, 0xbf, 0xd0, 0x3f, 0x1c, 0xdf, 0xe9, 0xda, 0x17, 0x05, 0xd1, 0xbf,
0x4d, 0xd6, 0x65, 0x72, 0x2e, 0x94, 0xe9, 0x71, 0x50, 0x95, 0xd1, 0xe5, 0x89, 0x41, 0xf5, 0x20, 0x57, 0x5e, 0xaf, 0x59, 0x02, 0x82, 0xd4, 0x31, 0x9c, 0x3f, 0x0c, 0xb4, 0x5b, 0x65, 0x38, 0x65,
0x34, 0xcf, 0x1b, 0x9a, 0x79, 0x92, 0x4e, 0x35, 0xc8, 0x49, 0x0b, 0x93, 0x6e, 0xce, 0xe4, 0xaf, 0x5c, 0xaa, 0x1b, 0xd3, 0x6f, 0xdb, 0x68, 0x6e, 0x4c, 0xdf, 0xa9, 0x46, 0xf1, 0x13, 0x64, 0xea,
0x1d, 0xb4, 0xdf, 0x0c, 0xf2, 0x64, 0x9e, 0x46, 0x80, 0x7f, 0x40, 0xb6, 0xfe, 0x3d, 0xc4, 0x54, 0x1f, 0x1a, 0xb2, 0xcb, 0xc2, 0x3e, 0x7f, 0x5f, 0x05, 0x3e, 0x2d, 0xb1, 0xd7, 0x99, 0xfd, 0xf6,
0x51, 0x33, 0xcd, 0xf0, 0xf8, 0xfd, 0xce, 0xc3, 0x5b, 0x6f, 0xb9, 0x9f, 0xcf, 0x12, 0x0d, 0x48, 0xfa, 0xf4, 0x71, 0xab, 0x63, 0x52, 0x8b, 0x55, 0x9a, 0x94, 0x71, 0xa9, 0x4d, 0xe8, 0x15, 0x69,
0x5f, 0x67, 0xb7, 0xcf, 0xfc, 0x1b, 0x50, 0xb4, 0xdd, 0xb1, 0x16, 0x23, 0x6b, 0x55, 0xfc, 0x08, 0x54, 0x7a, 0xa2, 0x51, 0xe5, 0x54, 0x90, 0xa6, 0x95, 0x4c, 0x7f, 0x1e, 0xab, 0x70, 0xea, 0xa8,
0x0d, 0xeb, 0x7d, 0x3e, 0x5f, 0xe5, 0x50, 0xb7, 0x39, 0xa9, 0x29, 0xc3, 0x93, 0x36, 0xf4, 0xf2, 0x81, 0x49, 0x9b, 0xe3, 0xfc, 0xb9, 0xd5, 0x58, 0xf5, 0xec, 0x32, 0x09, 0x01, 0xff, 0x80, 0x4c,
0xd5, 0x23, 0xe9, 0xd2, 0x30, 0x41, 0x0e, 0xd4, 0x8d, 0xeb, 0xff, 0x80, 0xde, 0x90, 0xb7, 0xb7, 0x35, 0xc8, 0xa2, 0x40, 0x06, 0xba, 0x9b, 0xfe, 0xe1, 0x87, 0x2d, 0xab, 0xea, 0x79, 0xe4, 0xa6,
0x6e, 0x48, 0x78, 0xaf, 0x2e, 0xe3, 0x34, 0x88, 0x24, 0xad, 0x0c, 0xfe, 0x12, 0x0d, 0xb4, 0x91, 0xb3, 0x58, 0x01, 0xc2, 0x55, 0xec, 0xe6, 0x43, 0x7e, 0x0b, 0x32, 0x68, 0xa6, 0x41, 0x83, 0x91,
0x72, 0xd4, 0x37, 0x7a, 0x0f, 0xb6, 0xea, 0x69, 0xf3, 0xc3, 0xfd, 0x5a, 0x73, 0xa0, 0x4f, 0x92, 0x3a, 0x2a, 0x7e, 0x8c, 0xfa, 0xe5, 0xe4, 0x39, 0x5b, 0xa5, 0x50, 0x96, 0xe9, 0x94, 0x92, 0xfe,
0x54, 0x12, 0x93, 0x3f, 0x2c, 0x74, 0xef, 0x15, 0x67, 0xbf, 0x4e, 0xa5, 0xc2, 0xdf, 0x6d, 0xb8, 0x51, 0x73, 0xf4, 0xfa, 0xe6, 0x96, 0xb4, 0x65, 0x98, 0x20, 0x0b, 0xca, 0xc2, 0xd5, 0xc4, 0x52,
0xeb, 0xff, 0x3f, 0x77, 0x35, 0xdb, 0x78, 0x7b, 0xb7, 0xae, 0x66, 0x37, 0x48, 0xc7, 0xd9, 0xaf, 0x77, 0xfa, 0xee, 0xc6, 0x9f, 0xe0, 0xdf, 0x2f, 0xd3, 0x58, 0x15, 0x22, 0x48, 0x13, 0x06, 0x7f,
0xd0, 0x20, 0x55, 0x90, 0x35, 0x7e, 0xbc, 0xbb, 0xb5, 0x7f, 0xd3, 0x58, 0x3b, 0xc0, 0x17, 0x9a, 0x85, 0x7a, 0xca, 0x48, 0x31, 0xe8, 0xea, 0x78, 0x0f, 0x37, 0xc6, 0x53, 0xe6, 0xfb, 0x7b, 0x65,
0x4c, 0x2a, 0x8d, 0x70, 0x7a, 0x75, 0xed, 0xf6, 0x9e, 0x5f, 0xbb, 0xbd, 0x17, 0xd7, 0x6e, 0xef, 0xcc, 0x9e, 0xda, 0x09, 0x52, 0x84, 0x70, 0x7e, 0x37, 0xd0, 0xfd, 0x1b, 0xce, 0x7e, 0x93, 0x08,
0xe7, 0xd2, 0xb5, 0xae, 0x4a, 0xd7, 0x7a, 0x5e, 0xba, 0xd6, 0x8b, 0xd2, 0xb5, 0xfe, 0x2e, 0x5d, 0x89, 0xbf, 0x5f, 0x73, 0xd7, 0xfd, 0x7f, 0xee, 0x2a, 0xb5, 0xf6, 0xb6, 0x7e, 0x96, 0x15, 0xd2,
0xeb, 0xb7, 0x7f, 0xdc, 0xde, 0xd3, 0x9d, 0xe5, 0xd1, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x34, 0x72, 0xf6, 0x6b, 0xd4, 0x4b, 0x24, 0xcc, 0x2b, 0x3f, 0x36, 0x4f, 0x06, 0x5d, 0x58, 0xd3, 0xc0,
0x9c, 0x0c, 0xa4, 0x1b, 0x07, 0x00, 0x00, 0x97, 0x4a, 0x4c, 0x8a, 0x18, 0xce, 0x3e, 0xda, 0x29, 0x5f, 0x3e, 0x1e, 0xdf, 0x78, 0xdd, 0xbb,
0x25, 0xbd, 0xf5, 0xc2, 0xfd, 0xc9, 0xd5, 0xf5, 0xa8, 0xf3, 0xf2, 0x7a, 0xd4, 0x79, 0x75, 0x3d,
0xea, 0xfc, 0x9c, 0x8f, 0x8c, 0xab, 0x7c, 0x64, 0xbc, 0xcc, 0x47, 0xc6, 0xab, 0x7c, 0x64, 0xfc,
0x95, 0x8f, 0x8c, 0xdf, 0xfe, 0x1e, 0x75, 0x9e, 0x6f, 0x2d, 0x0f, 0xfe, 0x0d, 0x00, 0x00, 0xff,
0xff, 0x66, 0x0f, 0x26, 0x7b, 0xf2, 0x07, 0x00, 0x00,
} }
func (m *Endpoint) Marshal() (dAtA []byte, err error) { func (m *Endpoint) Marshal() (dAtA []byte, err error) {
@ -275,6 +337,18 @@ func (m *Endpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
if m.Hints != nil {
{
size, err := m.Hints.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x42
}
if m.Zone != nil { if m.Zone != nil {
i -= len(*m.Zone) i -= len(*m.Zone)
copy(dAtA[i:], *m.Zone) copy(dAtA[i:], *m.Zone)
@ -407,6 +481,43 @@ func (m *EndpointConditions) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *EndpointHints) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *EndpointHints) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *EndpointHints) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.ForZones) > 0 {
for iNdEx := len(m.ForZones) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.ForZones[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
}
}
return len(dAtA) - i, nil
}
func (m *EndpointPort) Marshal() (dAtA []byte, err error) { func (m *EndpointPort) Marshal() (dAtA []byte, err error) {
size := m.Size() size := m.Size()
dAtA = make([]byte, size) dAtA = make([]byte, size)
@ -569,6 +680,34 @@ func (m *EndpointSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *ForZone) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ForZone) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *ForZone) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
offset -= sovGenerated(v) offset -= sovGenerated(v)
base := offset base := offset
@ -618,6 +757,10 @@ func (m *Endpoint) Size() (n int) {
l = len(*m.Zone) l = len(*m.Zone)
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))
} }
if m.Hints != nil {
l = m.Hints.Size()
n += 1 + l + sovGenerated(uint64(l))
}
return n return n
} }
@ -639,6 +782,21 @@ func (m *EndpointConditions) Size() (n int) {
return n return n
} }
func (m *EndpointHints) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.ForZones) > 0 {
for _, e := range m.ForZones {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
func (m *EndpointPort) Size() (n int) { func (m *EndpointPort) Size() (n int) {
if m == nil { if m == nil {
return 0 return 0
@ -705,6 +863,17 @@ func (m *EndpointSliceList) Size() (n int) {
return n return n
} }
func (m *ForZone) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
n += 1 + l + sovGenerated(uint64(l))
return n
}
func sovGenerated(x uint64) (n int) { func sovGenerated(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7 return (math_bits.Len64(x|1) + 6) / 7
} }
@ -733,6 +902,7 @@ func (this *Endpoint) String() string {
`DeprecatedTopology:` + mapStringForDeprecatedTopology + `,`, `DeprecatedTopology:` + mapStringForDeprecatedTopology + `,`,
`NodeName:` + valueToStringGenerated(this.NodeName) + `,`, `NodeName:` + valueToStringGenerated(this.NodeName) + `,`,
`Zone:` + valueToStringGenerated(this.Zone) + `,`, `Zone:` + valueToStringGenerated(this.Zone) + `,`,
`Hints:` + strings.Replace(this.Hints.String(), "EndpointHints", "EndpointHints", 1) + `,`,
`}`, `}`,
}, "") }, "")
return s return s
@ -749,6 +919,21 @@ func (this *EndpointConditions) String() string {
}, "") }, "")
return s return s
} }
func (this *EndpointHints) String() string {
if this == nil {
return "nil"
}
repeatedStringForForZones := "[]ForZone{"
for _, f := range this.ForZones {
repeatedStringForForZones += strings.Replace(strings.Replace(f.String(), "ForZone", "ForZone", 1), `&`, ``, 1) + ","
}
repeatedStringForForZones += "}"
s := strings.Join([]string{`&EndpointHints{`,
`ForZones:` + repeatedStringForForZones + `,`,
`}`,
}, "")
return s
}
func (this *EndpointPort) String() string { func (this *EndpointPort) String() string {
if this == nil { if this == nil {
return "nil" return "nil"
@ -801,6 +986,16 @@ func (this *EndpointSliceList) String() string {
}, "") }, "")
return s return s
} }
func (this *ForZone) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&ForZone{`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`}`,
}, "")
return s
}
func valueToStringGenerated(v interface{}) string { func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.IsNil() { if rv.IsNil() {
@ -1165,6 +1360,42 @@ func (m *Endpoint) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex]) s := string(dAtA[iNdEx:postIndex])
m.Zone = &s m.Zone = &s
iNdEx = postIndex iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Hints", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Hints == nil {
m.Hints = &EndpointHints{}
}
if err := m.Hints.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:]) skippy, err := skipGenerated(dAtA[iNdEx:])
@ -1299,6 +1530,90 @@ func (m *EndpointConditions) Unmarshal(dAtA []byte) error {
} }
return nil return nil
} }
func (m *EndpointHints) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: EndpointHints: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: EndpointHints: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ForZones", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ForZones = append(m.ForZones, ForZone{})
if err := m.ForZones[len(m.ForZones)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *EndpointPort) Unmarshal(dAtA []byte) error { func (m *EndpointPort) Unmarshal(dAtA []byte) error {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0
@ -1768,6 +2083,88 @@ func (m *EndpointSliceList) Unmarshal(dAtA []byte) error {
} }
return nil return nil
} }
func (m *ForZone) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ForZone: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ForZone: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGenerated(dAtA []byte) (n int, err error) { func skipGenerated(dAtA []byte) (n int, err error) {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0

View File

@ -73,6 +73,11 @@ message Endpoint {
// zone is the name of the Zone this endpoint exists in. // zone is the name of the Zone this endpoint exists in.
// +optional // +optional
optional string zone = 7; optional string zone = 7;
// hints contains information associated with how an endpoint should be
// consumed.
// +optional
optional EndpointHints hints = 8;
} }
// EndpointConditions represents the current condition of an endpoint. // EndpointConditions represents the current condition of an endpoint.
@ -101,6 +106,14 @@ message EndpointConditions {
optional bool terminating = 3; optional bool terminating = 3;
} }
// EndpointHints provides hints describing how an endpoint should be consumed.
message EndpointHints {
// forZones indicates the zone(s) this endpoint should be consumed by to
// enable topology aware routing.
// +listType=atomic
repeated ForZone forZones = 1;
}
// EndpointPort represents a Port used by an EndpointSlice // EndpointPort represents a Port used by an EndpointSlice
message EndpointPort { message EndpointPort {
// The name of this port. All ports in an EndpointSlice must have a unique // The name of this port. All ports in an EndpointSlice must have a unique
@ -175,3 +188,9 @@ message EndpointSliceList {
repeated EndpointSlice items = 2; repeated EndpointSlice items = 2;
} }
// ForZone provides information about which zones should consume this endpoint.
message ForZone {
// name represents the name of the zone.
optional string name = 1;
}

View File

@ -106,6 +106,10 @@ type Endpoint struct {
// zone is the name of the Zone this endpoint exists in. // zone is the name of the Zone this endpoint exists in.
// +optional // +optional
Zone *string `json:"zone,omitempty" protobuf:"bytes,7,opt,name=zone"` Zone *string `json:"zone,omitempty" protobuf:"bytes,7,opt,name=zone"`
// hints contains information associated with how an endpoint should be
// consumed.
// +optional
Hints *EndpointHints `json:"hints,omitempty" protobuf:"bytes,8,opt,name=hints"`
} }
// EndpointConditions represents the current condition of an endpoint. // EndpointConditions represents the current condition of an endpoint.
@ -134,6 +138,20 @@ type EndpointConditions struct {
Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"` Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"`
} }
// 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.
// +listType=atomic
ForZones []ForZone `json:"forZones,omitempty" protobuf:"bytes,1,name=forZones"`
}
// ForZone provides information about which zones should consume this endpoint.
type ForZone struct {
// name represents the name of the zone.
Name string `json:"name" protobuf:"bytes,1,name=name"`
}
// EndpointPort represents a Port used by an EndpointSlice // EndpointPort represents a Port used by an EndpointSlice
type EndpointPort struct { type EndpointPort struct {
// The name of this port. All ports in an EndpointSlice must have a unique // The name of this port. All ports in an EndpointSlice must have a unique

View File

@ -36,6 +36,7 @@ var map_Endpoint = map[string]string{
"deprecatedTopology": "deprecatedTopology contains topology information part of the v1beta1 API. This field is deprecated, and will be removed when the v1beta1 API is removed (no sooner than kubernetes v1.24). While this field can hold values, it is not writable through the v1 API, and any attempts to write to it will be silently ignored. Topology information can be found in the zone and nodeName fields instead.", "deprecatedTopology": "deprecatedTopology contains topology information part of the v1beta1 API. This field is deprecated, and will be removed when the v1beta1 API is removed (no sooner than kubernetes v1.24). While this field can hold values, it is not writable through the v1 API, and any attempts to write to it will be silently ignored. Topology information can be found in the zone and nodeName fields instead.",
"nodeName": "nodeName represents the name of the Node hosting this endpoint. This can be used to determine endpoints local to a Node. This field can be enabled with the EndpointSliceNodeName feature gate.", "nodeName": "nodeName represents the name of the Node hosting this endpoint. This can be used to determine endpoints local to a Node. This field can be enabled with the EndpointSliceNodeName feature gate.",
"zone": "zone is the name of the Zone this endpoint exists in.", "zone": "zone is the name of the Zone this endpoint exists in.",
"hints": "hints contains information associated with how an endpoint should be consumed.",
} }
func (Endpoint) SwaggerDoc() map[string]string { func (Endpoint) SwaggerDoc() map[string]string {
@ -53,6 +54,15 @@ func (EndpointConditions) SwaggerDoc() map[string]string {
return map_EndpointConditions return map_EndpointConditions
} }
var map_EndpointHints = map[string]string{
"": "EndpointHints provides hints describing how an endpoint should be consumed.",
"forZones": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing.",
}
func (EndpointHints) SwaggerDoc() map[string]string {
return map_EndpointHints
}
var map_EndpointPort = map[string]string{ var map_EndpointPort = map[string]string{
"": "EndpointPort represents a Port used by an EndpointSlice", "": "EndpointPort represents a Port used by an EndpointSlice",
"name": "The name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is dervied from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.", "name": "The name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is dervied from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.",
@ -87,4 +97,13 @@ func (EndpointSliceList) SwaggerDoc() map[string]string {
return map_EndpointSliceList return map_EndpointSliceList
} }
var map_ForZone = map[string]string{
"": "ForZone provides information about which zones should consume this endpoint.",
"name": "name represents the name of the zone.",
}
func (ForZone) SwaggerDoc() map[string]string {
return map_ForZone
}
// AUTO-GENERATED FUNCTIONS END HERE // AUTO-GENERATED FUNCTIONS END HERE

View File

@ -61,6 +61,11 @@ func (in *Endpoint) DeepCopyInto(out *Endpoint) {
*out = new(string) *out = new(string)
**out = **in **out = **in
} }
if in.Hints != nil {
in, out := &in.Hints, &out.Hints
*out = new(EndpointHints)
(*in).DeepCopyInto(*out)
}
return return
} }
@ -105,6 +110,27 @@ func (in *EndpointConditions) DeepCopy() *EndpointConditions {
return out 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) { func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
*out = *in *out = *in
@ -213,3 +239,19 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object {
} }
return nil 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
}

View File

@ -102,10 +102,38 @@ func (m *EndpointConditions) XXX_DiscardUnknown() {
var xxx_messageInfo_EndpointConditions proto.InternalMessageInfo var xxx_messageInfo_EndpointConditions proto.InternalMessageInfo
func (m *EndpointHints) Reset() { *m = EndpointHints{} }
func (*EndpointHints) ProtoMessage() {}
func (*EndpointHints) Descriptor() ([]byte, []int) {
return fileDescriptor_ece80bbc872d519b, []int{2}
}
func (m *EndpointHints) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *EndpointHints) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *EndpointHints) XXX_Merge(src proto.Message) {
xxx_messageInfo_EndpointHints.Merge(m, src)
}
func (m *EndpointHints) XXX_Size() int {
return m.Size()
}
func (m *EndpointHints) XXX_DiscardUnknown() {
xxx_messageInfo_EndpointHints.DiscardUnknown(m)
}
var xxx_messageInfo_EndpointHints proto.InternalMessageInfo
func (m *EndpointPort) Reset() { *m = EndpointPort{} } func (m *EndpointPort) Reset() { *m = EndpointPort{} }
func (*EndpointPort) ProtoMessage() {} func (*EndpointPort) ProtoMessage() {}
func (*EndpointPort) Descriptor() ([]byte, []int) { func (*EndpointPort) Descriptor() ([]byte, []int) {
return fileDescriptor_ece80bbc872d519b, []int{2} return fileDescriptor_ece80bbc872d519b, []int{3}
} }
func (m *EndpointPort) XXX_Unmarshal(b []byte) error { func (m *EndpointPort) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -133,7 +161,7 @@ var xxx_messageInfo_EndpointPort proto.InternalMessageInfo
func (m *EndpointSlice) Reset() { *m = EndpointSlice{} } func (m *EndpointSlice) Reset() { *m = EndpointSlice{} }
func (*EndpointSlice) ProtoMessage() {} func (*EndpointSlice) ProtoMessage() {}
func (*EndpointSlice) Descriptor() ([]byte, []int) { func (*EndpointSlice) Descriptor() ([]byte, []int) {
return fileDescriptor_ece80bbc872d519b, []int{3} return fileDescriptor_ece80bbc872d519b, []int{4}
} }
func (m *EndpointSlice) XXX_Unmarshal(b []byte) error { func (m *EndpointSlice) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -161,7 +189,7 @@ var xxx_messageInfo_EndpointSlice proto.InternalMessageInfo
func (m *EndpointSliceList) Reset() { *m = EndpointSliceList{} } func (m *EndpointSliceList) Reset() { *m = EndpointSliceList{} }
func (*EndpointSliceList) ProtoMessage() {} func (*EndpointSliceList) ProtoMessage() {}
func (*EndpointSliceList) Descriptor() ([]byte, []int) { func (*EndpointSliceList) Descriptor() ([]byte, []int) {
return fileDescriptor_ece80bbc872d519b, []int{4} return fileDescriptor_ece80bbc872d519b, []int{5}
} }
func (m *EndpointSliceList) XXX_Unmarshal(b []byte) error { func (m *EndpointSliceList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -186,13 +214,43 @@ func (m *EndpointSliceList) XXX_DiscardUnknown() {
var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo
func (m *ForZone) Reset() { *m = ForZone{} }
func (*ForZone) ProtoMessage() {}
func (*ForZone) Descriptor() ([]byte, []int) {
return fileDescriptor_ece80bbc872d519b, []int{6}
}
func (m *ForZone) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ForZone) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *ForZone) XXX_Merge(src proto.Message) {
xxx_messageInfo_ForZone.Merge(m, src)
}
func (m *ForZone) XXX_Size() int {
return m.Size()
}
func (m *ForZone) XXX_DiscardUnknown() {
xxx_messageInfo_ForZone.DiscardUnknown(m)
}
var xxx_messageInfo_ForZone proto.InternalMessageInfo
func init() { func init() {
proto.RegisterType((*Endpoint)(nil), "k8s.io.api.discovery.v1beta1.Endpoint") proto.RegisterType((*Endpoint)(nil), "k8s.io.api.discovery.v1beta1.Endpoint")
proto.RegisterMapType((map[string]string)(nil), "k8s.io.api.discovery.v1beta1.Endpoint.TopologyEntry") proto.RegisterMapType((map[string]string)(nil), "k8s.io.api.discovery.v1beta1.Endpoint.TopologyEntry")
proto.RegisterType((*EndpointConditions)(nil), "k8s.io.api.discovery.v1beta1.EndpointConditions") proto.RegisterType((*EndpointConditions)(nil), "k8s.io.api.discovery.v1beta1.EndpointConditions")
proto.RegisterType((*EndpointHints)(nil), "k8s.io.api.discovery.v1beta1.EndpointHints")
proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1beta1.EndpointPort") proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1beta1.EndpointPort")
proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1beta1.EndpointSlice") proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1beta1.EndpointSlice")
proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1beta1.EndpointSliceList") proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1beta1.EndpointSliceList")
proto.RegisterType((*ForZone)(nil), "k8s.io.api.discovery.v1beta1.ForZone")
} }
func init() { func init() {
@ -200,57 +258,62 @@ func init() {
} }
var fileDescriptor_ece80bbc872d519b = []byte{ var fileDescriptor_ece80bbc872d519b = []byte{
// 798 bytes of a gzipped FileDescriptorProto // 870 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x8f, 0xe3, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x41, 0x8f, 0xe3, 0x34,
0x10, 0x8d, 0x27, 0x63, 0xc6, 0xee, 0xec, 0x88, 0xdd, 0x16, 0x87, 0x68, 0x58, 0xd9, 0xa3, 0x20, 0x14, 0x6e, 0xa6, 0x53, 0x9a, 0xb8, 0x33, 0x62, 0xd7, 0xe2, 0x50, 0x0d, 0xab, 0xa4, 0x0a, 0x5a,
0x50, 0xc4, 0x68, 0x6d, 0x66, 0xb5, 0x42, 0x2b, 0x38, 0x8d, 0x61, 0x04, 0x48, 0xb0, 0x1b, 0xf5, 0x54, 0x31, 0xda, 0x84, 0x19, 0xad, 0xd0, 0x0a, 0x4e, 0x13, 0x18, 0x58, 0xa4, 0x65, 0x77, 0xe4,
0x46, 0x42, 0x42, 0x1c, 0xe8, 0xd8, 0xb5, 0x8e, 0x49, 0xec, 0xb6, 0xba, 0x3b, 0x91, 0x72, 0xe3, 0x19, 0x09, 0x69, 0xc5, 0x01, 0x37, 0xf1, 0xa4, 0xa1, 0x53, 0x3b, 0xb2, 0xdd, 0x4a, 0xbd, 0xf1,
0x1f, 0xc0, 0x7f, 0x42, 0x42, 0x73, 0xdc, 0xe3, 0x9e, 0x2c, 0x62, 0xf8, 0x15, 0x7b, 0x42, 0xdd, 0x0f, 0xe0, 0xb7, 0xf0, 0x17, 0x90, 0xd0, 0x1c, 0xf7, 0xb8, 0xa7, 0x88, 0x09, 0xff, 0x62, 0x4f,
0xfe, 0x4a, 0x08, 0x1f, 0xb9, 0x75, 0xbf, 0xaa, 0xf7, 0xaa, 0x5e, 0x75, 0x17, 0xba, 0x5d, 0x3c, 0xc8, 0x8e, 0x93, 0xb4, 0x14, 0x86, 0xde, 0xec, 0xcf, 0xef, 0xfb, 0xde, 0x7b, 0xdf, 0xb3, 0x0d,
0x15, 0x5e, 0xc2, 0xfc, 0xc5, 0x6a, 0x06, 0x3c, 0x03, 0x09, 0xc2, 0x5f, 0x43, 0x16, 0x31, 0xee, 0xce, 0x67, 0xcf, 0x44, 0x90, 0xb1, 0x70, 0xb6, 0x98, 0x10, 0x4e, 0x89, 0x24, 0x22, 0x5c, 0x12,
0xd7, 0x01, 0x9a, 0x27, 0x7e, 0x94, 0x88, 0x90, 0xad, 0x81, 0x6f, 0xfc, 0xf5, 0xf5, 0x0c, 0x24, 0x9a, 0x30, 0x1e, 0x9a, 0x03, 0x9c, 0x67, 0x61, 0x92, 0x89, 0x98, 0x2d, 0x09, 0x5f, 0x85, 0xcb,
0xbd, 0xf6, 0x63, 0xc8, 0x80, 0x53, 0x09, 0x91, 0x97, 0x73, 0x26, 0x19, 0x7e, 0x58, 0x65, 0x7b, 0x93, 0x09, 0x91, 0xf8, 0x24, 0x4c, 0x09, 0x25, 0x1c, 0x4b, 0x92, 0x04, 0x39, 0x67, 0x92, 0xc1,
0x34, 0x4f, 0xbc, 0x36, 0xdb, 0xab, 0xb3, 0x2f, 0x1e, 0xc5, 0x89, 0x9c, 0xaf, 0x66, 0x5e, 0xc8, 0x47, 0x55, 0x74, 0x80, 0xf3, 0x2c, 0x68, 0xa2, 0x03, 0x13, 0x7d, 0xf4, 0x24, 0xcd, 0xe4, 0x74,
0x52, 0x3f, 0x66, 0x31, 0xf3, 0x35, 0x69, 0xb6, 0x7a, 0xa9, 0x6f, 0xfa, 0xa2, 0x4f, 0x95, 0xd8, 0x31, 0x09, 0x62, 0x36, 0x0f, 0x53, 0x96, 0xb2, 0x50, 0x93, 0x26, 0x8b, 0x6b, 0xbd, 0xd3, 0x1b,
0xc5, 0x68, 0xa7, 0x74, 0xc8, 0x38, 0xf8, 0xeb, 0x83, 0x82, 0x17, 0x4f, 0xba, 0x9c, 0x94, 0x86, 0xbd, 0xaa, 0xc4, 0x8e, 0xfc, 0xb5, 0xd4, 0x31, 0xe3, 0x24, 0x5c, 0x6e, 0x25, 0x3c, 0x7a, 0xda,
0xf3, 0x24, 0x53, 0xdd, 0xe5, 0x8b, 0x58, 0x01, 0xc2, 0x4f, 0x41, 0xd2, 0x7f, 0x62, 0xf9, 0xff, 0xc6, 0xcc, 0x71, 0x3c, 0xcd, 0xa8, 0xaa, 0x2e, 0x9f, 0xa5, 0x0a, 0x10, 0xe1, 0x9c, 0x48, 0xfc,
0xc6, 0xe2, 0xab, 0x4c, 0x26, 0x29, 0x1c, 0x10, 0x3e, 0xfe, 0x3f, 0x82, 0x08, 0xe7, 0x90, 0xd2, 0x6f, 0xac, 0xf0, 0xbf, 0x58, 0x7c, 0x41, 0x65, 0x36, 0x27, 0x5b, 0x84, 0xcf, 0xfe, 0x8f, 0x20,
0xbf, 0xf3, 0x46, 0x7f, 0xf6, 0x91, 0x75, 0x9b, 0x45, 0x39, 0x4b, 0x32, 0x89, 0xaf, 0x90, 0x4d, 0xe2, 0x29, 0x99, 0xe3, 0x7f, 0xf2, 0xfc, 0xdf, 0xf6, 0x81, 0x7d, 0x4e, 0x93, 0x9c, 0x65, 0x54,
0xa3, 0x88, 0x83, 0x10, 0x20, 0x86, 0xc6, 0x65, 0x7f, 0x6c, 0x07, 0xe7, 0x65, 0xe1, 0xda, 0x37, 0xc2, 0x63, 0xe0, 0xe0, 0x24, 0xe1, 0x44, 0x08, 0x22, 0x86, 0xd6, 0xa8, 0x3b, 0x76, 0xa2, 0xc3,
0x0d, 0x48, 0xba, 0x38, 0x8e, 0x10, 0x0a, 0x59, 0x16, 0x25, 0x32, 0x61, 0x99, 0x18, 0x9e, 0x5c, 0xb2, 0xf0, 0x9c, 0xb3, 0x1a, 0x44, 0xed, 0x39, 0x4c, 0x00, 0x88, 0x19, 0x4d, 0x32, 0x99, 0x31,
0x1a, 0xe3, 0xc1, 0xe3, 0x8f, 0xbc, 0xff, 0x1a, 0xaf, 0xd7, 0x14, 0xfa, 0xac, 0xe5, 0x05, 0xf8, 0x2a, 0x86, 0x7b, 0x23, 0x6b, 0x3c, 0x38, 0xfd, 0x34, 0xb8, 0xcf, 0xde, 0xa0, 0x4e, 0xf4, 0x65,
0xae, 0x70, 0x7b, 0x65, 0xe1, 0xa2, 0x0e, 0x23, 0x3b, 0xba, 0x78, 0x8c, 0xac, 0x39, 0x13, 0x32, 0xc3, 0x8b, 0xe0, 0x6d, 0xe1, 0x75, 0xca, 0xc2, 0x03, 0x2d, 0x86, 0xd6, 0x74, 0xe1, 0x18, 0xd8,
0xa3, 0x29, 0x0c, 0xfb, 0x97, 0xc6, 0xd8, 0x0e, 0xee, 0x95, 0x85, 0x6b, 0x7d, 0x59, 0x63, 0xa4, 0x53, 0x26, 0x24, 0xc5, 0x73, 0x32, 0xec, 0x8e, 0xac, 0xb1, 0x13, 0x1d, 0x94, 0x85, 0x67, 0x3f,
0x8d, 0xe2, 0x09, 0xb2, 0x25, 0xe5, 0x31, 0x48, 0x02, 0x2f, 0x87, 0xa7, 0xba, 0x9d, 0xf7, 0x76, 0x37, 0x18, 0x6a, 0x4e, 0xe1, 0x05, 0x70, 0x24, 0xe6, 0x29, 0x91, 0x88, 0x5c, 0x0f, 0xf7, 0x75,
0xdb, 0x51, 0x0f, 0xe4, 0xad, 0xaf, 0xbd, 0xe7, 0xb3, 0x1f, 0x21, 0x54, 0x49, 0xc0, 0x21, 0x0b, 0x39, 0x1f, 0xad, 0x97, 0xa3, 0x06, 0x14, 0x2c, 0x4f, 0x82, 0x57, 0x93, 0x9f, 0x48, 0xac, 0x82,
0xa1, 0x72, 0x38, 0x6d, 0x98, 0xa4, 0x13, 0xc1, 0x33, 0x64, 0x49, 0x96, 0xb3, 0x25, 0x8b, 0x37, 0x08, 0x27, 0x34, 0x26, 0x55, 0x87, 0x57, 0x35, 0x13, 0xb5, 0x22, 0x70, 0x02, 0x6c, 0xc9, 0x72,
0x43, 0xf3, 0xb2, 0x3f, 0x1e, 0x3c, 0x7e, 0x72, 0x9c, 0x3f, 0x6f, 0x5a, 0xd3, 0x6e, 0x33, 0xc9, 0x76, 0xc3, 0xd2, 0xd5, 0xb0, 0x37, 0xea, 0x8e, 0x07, 0xa7, 0x4f, 0x77, 0xeb, 0x2f, 0xb8, 0x32,
0x37, 0xc1, 0xfd, 0xda, 0xa3, 0xd5, 0xc0, 0xa4, 0xd5, 0x55, 0xfe, 0x32, 0x16, 0xc1, 0x33, 0xe5, 0xb4, 0x73, 0x2a, 0xf9, 0x2a, 0x7a, 0x60, 0x7a, 0xb4, 0x6b, 0x18, 0x35, 0xba, 0xaa, 0x3f, 0xca,
0xef, 0xad, 0xce, 0xdf, 0xb3, 0x1a, 0x23, 0x6d, 0xf4, 0xe2, 0x53, 0x74, 0xbe, 0x27, 0x8b, 0xef, 0x12, 0xf2, 0x52, 0xf5, 0xf7, 0x5e, 0xdb, 0xdf, 0x4b, 0x83, 0xa1, 0xe6, 0x14, 0xbe, 0x00, 0xbd,
0xa3, 0xfe, 0x02, 0x36, 0x43, 0x43, 0xb1, 0x88, 0x3a, 0xe2, 0x77, 0x90, 0xb9, 0xa6, 0xcb, 0x15, 0x69, 0x46, 0xa5, 0x18, 0xf6, 0x75, 0x6f, 0xc7, 0xbb, 0x95, 0xf2, 0x5c, 0x51, 0x22, 0xa7, 0x2c,
0xe8, 0xd7, 0xb0, 0x49, 0x75, 0xf9, 0xe4, 0xe4, 0xa9, 0x31, 0xfa, 0xd9, 0x40, 0xf8, 0x70, 0xfa, 0xbc, 0x9e, 0x5e, 0xa2, 0x4a, 0xe4, 0xe8, 0x0b, 0x70, 0xb8, 0x51, 0x24, 0x7c, 0x00, 0xba, 0x33,
0xd8, 0x45, 0x26, 0x07, 0x1a, 0x55, 0x22, 0x56, 0x60, 0x97, 0x85, 0x6b, 0x12, 0x05, 0x90, 0x0a, 0xb2, 0x1a, 0x5a, 0xaa, 0x06, 0xa4, 0x96, 0xf0, 0x03, 0xd0, 0x5b, 0xe2, 0x9b, 0x05, 0xd1, 0xb3,
0xc7, 0xef, 0xa3, 0x33, 0x01, 0x7c, 0x9d, 0x64, 0xb1, 0xd6, 0xb4, 0x82, 0x41, 0x59, 0xb8, 0x67, 0x75, 0x50, 0xb5, 0xf9, 0x7c, 0xef, 0x99, 0xe5, 0xff, 0x62, 0x01, 0xb8, 0x3d, 0x4b, 0xe8, 0x81,
0x2f, 0x2a, 0x88, 0x34, 0x31, 0x7c, 0x8d, 0x06, 0x12, 0x78, 0x9a, 0x64, 0x54, 0xaa, 0xd4, 0xbe, 0x1e, 0x27, 0x38, 0xa9, 0x44, 0xec, 0x2a, 0x29, 0x52, 0x00, 0xaa, 0x70, 0xf8, 0x18, 0xf4, 0x05,
0x4e, 0x7d, 0xbb, 0x2c, 0xdc, 0xc1, 0xb4, 0x83, 0xc9, 0x6e, 0xce, 0xe8, 0x37, 0x03, 0xdd, 0x6b, 0xe1, 0xcb, 0x8c, 0xa6, 0x5a, 0xd3, 0x8e, 0x06, 0x65, 0xe1, 0xf5, 0x2f, 0x2b, 0x08, 0xd5, 0x67,
0x3a, 0x9a, 0x30, 0x2e, 0xf1, 0x43, 0x74, 0xaa, 0x5f, 0x59, 0xfb, 0x09, 0xac, 0xb2, 0x70, 0x4f, 0xf0, 0x04, 0x0c, 0x24, 0xe1, 0xf3, 0x8c, 0x62, 0xa9, 0x42, 0xbb, 0x3a, 0xf4, 0xfd, 0xb2, 0xf0,
0xf5, 0x04, 0x34, 0x8a, 0xbf, 0x40, 0x96, 0xfe, 0xb0, 0x21, 0x5b, 0x56, 0xee, 0x82, 0x2b, 0x35, 0x06, 0x57, 0x2d, 0x8c, 0xd6, 0x63, 0xfc, 0x04, 0x1c, 0x6e, 0x74, 0x0c, 0x2f, 0x81, 0x7d, 0xcd,
0xa7, 0x49, 0x8d, 0xbd, 0x29, 0xdc, 0x77, 0x0f, 0x97, 0xd1, 0x6b, 0xc2, 0xa4, 0x25, 0xab, 0x32, 0xf8, 0x6b, 0x46, 0xcd, 0x4d, 0x1e, 0x9c, 0x3e, 0xbe, 0xdf, 0xb0, 0xaf, 0xab, 0xe8, 0x76, 0x58,
0x39, 0xe3, 0x52, 0xf7, 0x68, 0x56, 0x65, 0x54, 0x79, 0xa2, 0x51, 0x65, 0x84, 0xe6, 0x79, 0x43, 0x06, 0x10, 0xa8, 0x11, 0xf2, 0xff, 0xb0, 0xc0, 0x41, 0x9d, 0xe6, 0x82, 0x71, 0x09, 0x1f, 0x81,
0xd3, 0xdf, 0xc8, 0xae, 0x8c, 0xdc, 0x74, 0x30, 0xd9, 0xcd, 0x19, 0x6d, 0x4f, 0xd0, 0x79, 0x63, 0x7d, 0x7d, 0x33, 0xb5, 0x6b, 0x91, 0x5d, 0x16, 0xde, 0xbe, 0x9e, 0x9a, 0x46, 0xe1, 0x37, 0xc0,
0xe4, 0xc5, 0x32, 0x09, 0x01, 0xff, 0x80, 0x2c, 0xb5, 0xd7, 0x11, 0x95, 0x54, 0xbb, 0xd9, 0xdf, 0xd6, 0x8f, 0x2c, 0x66, 0x37, 0x95, 0x87, 0xd1, 0xb1, 0x12, 0xbe, 0x30, 0xd8, 0xbb, 0xc2, 0xfb,
0x8b, 0x76, 0x3d, 0xbd, 0x7c, 0x11, 0x2b, 0x40, 0x78, 0x2a, 0xbb, 0xfb, 0x9a, 0xdf, 0x80, 0xa4, 0x70, 0xfb, 0x03, 0x09, 0xea, 0x63, 0xd4, 0x90, 0x55, 0x9a, 0x9c, 0x71, 0xa9, 0x9d, 0xe8, 0x55,
0xdd, 0x5e, 0x74, 0x18, 0x69, 0x55, 0xf1, 0xe7, 0x68, 0x50, 0x2f, 0xe2, 0x74, 0x93, 0x43, 0xdd, 0x69, 0x54, 0x7a, 0xa4, 0x51, 0x65, 0x17, 0xce, 0xf3, 0x9a, 0xa6, 0xaf, 0xbe, 0x53, 0xd9, 0x75,
0xe6, 0xa8, 0xa6, 0x0c, 0x6e, 0xba, 0xd0, 0x9b, 0xfd, 0x2b, 0xd9, 0xa5, 0xe1, 0x6f, 0x91, 0x0d, 0xd6, 0xc2, 0x68, 0x3d, 0xc6, 0xbf, 0xdb, 0x6b, 0xfd, 0xba, 0xbc, 0xc9, 0x62, 0x02, 0x7f, 0x04,
0x75, 0xe3, 0x6a, 0x81, 0xd5, 0x07, 0xff, 0xe0, 0xb8, 0x0f, 0x1e, 0x3c, 0xa8, 0x6b, 0xd9, 0x0d, 0xb6, 0xfa, 0x8b, 0x12, 0x2c, 0xb1, 0xee, 0x66, 0xf3, 0x2d, 0x37, 0x5f, 0x4a, 0x90, 0xcf, 0x52,
0x22, 0x48, 0xa7, 0x85, 0x9f, 0x23, 0x53, 0x4d, 0x53, 0x0c, 0xfb, 0x5a, 0xf4, 0xc3, 0xe3, 0x44, 0x05, 0x88, 0x40, 0x45, 0xb7, 0xcf, 0xe9, 0x3b, 0x22, 0x71, 0xfb, 0x96, 0x5b, 0x0c, 0x35, 0xaa,
0xd5, 0x33, 0x04, 0xe7, 0xb5, 0xb0, 0xa9, 0x6e, 0x82, 0x54, 0x3a, 0xa3, 0x5f, 0x0d, 0xf4, 0x60, 0xf0, 0x2b, 0x30, 0x30, 0x9f, 0xc7, 0xd5, 0x2a, 0x27, 0xa6, 0x4c, 0xdf, 0x50, 0x06, 0x67, 0xed,
0x6f, 0xc6, 0x5f, 0x27, 0x42, 0xe2, 0xef, 0x0f, 0xe6, 0xec, 0x1d, 0x37, 0x67, 0xc5, 0xd6, 0x53, 0xd1, 0xbb, 0xcd, 0x2d, 0x5a, 0xa7, 0xc1, 0xef, 0x81, 0x43, 0x4c, 0xe1, 0xea, 0xd3, 0x51, 0x83,
0x6e, 0x37, 0xb3, 0x41, 0x76, 0x66, 0x3c, 0x41, 0x66, 0x22, 0x21, 0x6d, 0x26, 0x73, 0x75, 0x9c, 0xfd, 0x78, 0xb7, 0x97, 0x10, 0x3d, 0x34, 0xb9, 0x9c, 0x1a, 0x11, 0xa8, 0xd5, 0x82, 0xaf, 0x40,
0x09, 0xdd, 0x5d, 0xe7, 0xe2, 0x2b, 0xa5, 0x40, 0x2a, 0xa1, 0xe0, 0xd1, 0xdd, 0xd6, 0xe9, 0xbd, 0x4f, 0xb9, 0x29, 0x86, 0x5d, 0x2d, 0xfa, 0xc9, 0x6e, 0xa2, 0x6a, 0x0c, 0xd1, 0xa1, 0x11, 0xee,
0xda, 0x3a, 0xbd, 0xd7, 0x5b, 0xa7, 0xf7, 0x53, 0xe9, 0x18, 0x77, 0xa5, 0x63, 0xbc, 0x2a, 0x1d, 0xa9, 0x9d, 0x40, 0x95, 0x8e, 0xff, 0xbb, 0x05, 0x1e, 0x6e, 0x78, 0xfc, 0x22, 0x13, 0x12, 0xfe,
0xe3, 0x75, 0xe9, 0x18, 0xbf, 0x97, 0x8e, 0xf1, 0xcb, 0x1f, 0x4e, 0xef, 0xbb, 0xb3, 0x5a, 0xf2, 0xb0, 0xe5, 0x73, 0xb0, 0x9b, 0xcf, 0x8a, 0xad, 0x5d, 0x6e, 0x2e, 0x68, 0x8d, 0xac, 0x79, 0x7c,
0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x35, 0xe6, 0xf5, 0xf2, 0x06, 0x00, 0x00, 0x01, 0x7a, 0x99, 0x24, 0xf3, 0xda, 0x99, 0x1d, 0xff, 0x08, 0x5d, 0x5d, 0xdb, 0xc5, 0xb7, 0x4a,
0x01, 0x55, 0x42, 0xfe, 0x31, 0xe8, 0x9b, 0x87, 0x00, 0x47, 0x1b, 0x97, 0xfd, 0xc0, 0x84, 0xaf,
0x5d, 0xf8, 0xe8, 0xc9, 0xed, 0x9d, 0xdb, 0x79, 0x73, 0xe7, 0x76, 0xde, 0xde, 0xb9, 0x9d, 0x9f,
0x4b, 0xd7, 0xba, 0x2d, 0x5d, 0xeb, 0x4d, 0xe9, 0x5a, 0x6f, 0x4b, 0xd7, 0xfa, 0xb3, 0x74, 0xad,
0x5f, 0xff, 0x72, 0x3b, 0xaf, 0xfb, 0x26, 0xff, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x0d,
0x6f, 0x98, 0xd3, 0x07, 0x00, 0x00,
} }
func (m *Endpoint) Marshal() (dAtA []byte, err error) { func (m *Endpoint) Marshal() (dAtA []byte, err error) {
@ -273,6 +336,18 @@ func (m *Endpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
if m.Hints != nil {
{
size, err := m.Hints.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x3a
}
if m.NodeName != nil { if m.NodeName != nil {
i -= len(*m.NodeName) i -= len(*m.NodeName)
copy(dAtA[i:], *m.NodeName) copy(dAtA[i:], *m.NodeName)
@ -398,6 +473,43 @@ func (m *EndpointConditions) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *EndpointHints) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *EndpointHints) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *EndpointHints) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.ForZones) > 0 {
for iNdEx := len(m.ForZones) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.ForZones[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
}
}
return len(dAtA) - i, nil
}
func (m *EndpointPort) Marshal() (dAtA []byte, err error) { func (m *EndpointPort) Marshal() (dAtA []byte, err error) {
size := m.Size() size := m.Size()
dAtA = make([]byte, size) dAtA = make([]byte, size)
@ -560,6 +672,34 @@ func (m *EndpointSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *ForZone) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ForZone) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *ForZone) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
offset -= sovGenerated(v) offset -= sovGenerated(v)
base := offset base := offset
@ -605,6 +745,10 @@ func (m *Endpoint) Size() (n int) {
l = len(*m.NodeName) l = len(*m.NodeName)
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))
} }
if m.Hints != nil {
l = m.Hints.Size()
n += 1 + l + sovGenerated(uint64(l))
}
return n return n
} }
@ -626,6 +770,21 @@ func (m *EndpointConditions) Size() (n int) {
return n return n
} }
func (m *EndpointHints) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.ForZones) > 0 {
for _, e := range m.ForZones {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
func (m *EndpointPort) Size() (n int) { func (m *EndpointPort) Size() (n int) {
if m == nil { if m == nil {
return 0 return 0
@ -692,6 +851,17 @@ func (m *EndpointSliceList) Size() (n int) {
return n return n
} }
func (m *ForZone) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
n += 1 + l + sovGenerated(uint64(l))
return n
}
func sovGenerated(x uint64) (n int) { func sovGenerated(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7 return (math_bits.Len64(x|1) + 6) / 7
} }
@ -719,6 +889,7 @@ func (this *Endpoint) String() string {
`TargetRef:` + strings.Replace(fmt.Sprintf("%v", this.TargetRef), "ObjectReference", "v1.ObjectReference", 1) + `,`, `TargetRef:` + strings.Replace(fmt.Sprintf("%v", this.TargetRef), "ObjectReference", "v1.ObjectReference", 1) + `,`,
`Topology:` + mapStringForTopology + `,`, `Topology:` + mapStringForTopology + `,`,
`NodeName:` + valueToStringGenerated(this.NodeName) + `,`, `NodeName:` + valueToStringGenerated(this.NodeName) + `,`,
`Hints:` + strings.Replace(this.Hints.String(), "EndpointHints", "EndpointHints", 1) + `,`,
`}`, `}`,
}, "") }, "")
return s return s
@ -735,6 +906,21 @@ func (this *EndpointConditions) String() string {
}, "") }, "")
return s return s
} }
func (this *EndpointHints) String() string {
if this == nil {
return "nil"
}
repeatedStringForForZones := "[]ForZone{"
for _, f := range this.ForZones {
repeatedStringForForZones += strings.Replace(strings.Replace(f.String(), "ForZone", "ForZone", 1), `&`, ``, 1) + ","
}
repeatedStringForForZones += "}"
s := strings.Join([]string{`&EndpointHints{`,
`ForZones:` + repeatedStringForForZones + `,`,
`}`,
}, "")
return s
}
func (this *EndpointPort) String() string { func (this *EndpointPort) String() string {
if this == nil { if this == nil {
return "nil" return "nil"
@ -787,6 +973,16 @@ func (this *EndpointSliceList) String() string {
}, "") }, "")
return s return s
} }
func (this *ForZone) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&ForZone{`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`}`,
}, "")
return s
}
func valueToStringGenerated(v interface{}) string { func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.IsNil() { if rv.IsNil() {
@ -1118,6 +1314,42 @@ func (m *Endpoint) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex]) s := string(dAtA[iNdEx:postIndex])
m.NodeName = &s m.NodeName = &s
iNdEx = postIndex iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Hints", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Hints == nil {
m.Hints = &EndpointHints{}
}
if err := m.Hints.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:]) skippy, err := skipGenerated(dAtA[iNdEx:])
@ -1252,6 +1484,90 @@ func (m *EndpointConditions) Unmarshal(dAtA []byte) error {
} }
return nil return nil
} }
func (m *EndpointHints) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: EndpointHints: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: EndpointHints: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ForZones", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ForZones = append(m.ForZones, ForZone{})
if err := m.ForZones[len(m.ForZones)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *EndpointPort) Unmarshal(dAtA []byte) error { func (m *EndpointPort) Unmarshal(dAtA []byte) error {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0
@ -1721,6 +2037,88 @@ func (m *EndpointSliceList) Unmarshal(dAtA []byte) error {
} }
return nil return nil
} }
func (m *ForZone) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ForZone: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ForZone: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGenerated(dAtA []byte) (n int, err error) { func skipGenerated(dAtA []byte) (n int, err error) {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0

View File

@ -76,6 +76,12 @@ message Endpoint {
// with the EndpointSliceNodeName feature gate. // with the EndpointSliceNodeName feature gate.
// +optional // +optional
optional string nodeName = 6; optional string nodeName = 6;
// hints contains information associated with how an endpoint should be
// consumed.
// +featureGate=TopologyAwareHints
// +optional
optional EndpointHints hints = 7;
} }
// EndpointConditions represents the current condition of an endpoint. // EndpointConditions represents the current condition of an endpoint.
@ -104,6 +110,14 @@ message EndpointConditions {
optional bool terminating = 3; optional bool terminating = 3;
} }
// EndpointHints provides hints describing how an endpoint should be consumed.
message EndpointHints {
// forZones indicates the zone(s) this endpoint should be consumed by to
// enable topology aware routing. May contain a maximum of 8 entries.
// +listType=atomic
repeated ForZone forZones = 1;
}
// EndpointPort represents a Port used by an EndpointSlice // EndpointPort represents a Port used by an EndpointSlice
message EndpointPort { message EndpointPort {
// The name of this port. All ports in an EndpointSlice must have a unique // The name of this port. All ports in an EndpointSlice must have a unique
@ -178,3 +192,9 @@ message EndpointSliceList {
repeated EndpointSlice items = 2; repeated EndpointSlice items = 2;
} }
// ForZone provides information about which zones should consume this endpoint.
message ForZone {
// name represents the name of the zone.
optional string name = 1;
}

View File

@ -110,6 +110,11 @@ type Endpoint struct {
// with the EndpointSliceNodeName feature gate. // with the EndpointSliceNodeName feature gate.
// +optional // +optional
NodeName *string `json:"nodeName,omitempty" protobuf:"bytes,6,opt,name=nodeName"` NodeName *string `json:"nodeName,omitempty" protobuf:"bytes,6,opt,name=nodeName"`
// hints contains information associated with how an endpoint should be
// consumed.
// +featureGate=TopologyAwareHints
// +optional
Hints *EndpointHints `json:"hints,omitempty" protobuf:"bytes,7,opt,name=hints"`
} }
// EndpointConditions represents the current condition of an endpoint. // EndpointConditions represents the current condition of an endpoint.
@ -138,6 +143,20 @@ type EndpointConditions struct {
Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"` Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"`
} }
// 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.
// +listType=atomic
ForZones []ForZone `json:"forZones,omitempty" protobuf:"bytes,1,name=forZones"`
}
// ForZone provides information about which zones should consume this endpoint.
type ForZone struct {
// name represents the name of the zone.
Name string `json:"name" protobuf:"bytes,1,name=name"`
}
// EndpointPort represents a Port used by an EndpointSlice // EndpointPort represents a Port used by an EndpointSlice
type EndpointPort struct { type EndpointPort struct {
// The name of this port. All ports in an EndpointSlice must have a unique // The name of this port. All ports in an EndpointSlice must have a unique

View File

@ -35,6 +35,7 @@ var map_Endpoint = map[string]string{
"targetRef": "targetRef is a reference to a Kubernetes object that represents this endpoint.", "targetRef": "targetRef is a reference to a Kubernetes object that represents this endpoint.",
"topology": "topology contains arbitrary topology information associated with the endpoint. These key/value pairs must conform with the label format. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels Topology may include a maximum of 16 key/value pairs. This includes, but is not limited to the following well known keys: * kubernetes.io/hostname: the value indicates the hostname of the node\n where the endpoint is located. This should match the corresponding\n node label.\n* topology.kubernetes.io/zone: the value indicates the zone where the\n endpoint is located. This should match the corresponding node label.\n* topology.kubernetes.io/region: the value indicates the region where the\n endpoint is located. This should match the corresponding node label.\nThis field is deprecated and will be removed in future api versions.", "topology": "topology contains arbitrary topology information associated with the endpoint. These key/value pairs must conform with the label format. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels Topology may include a maximum of 16 key/value pairs. This includes, but is not limited to the following well known keys: * kubernetes.io/hostname: the value indicates the hostname of the node\n where the endpoint is located. This should match the corresponding\n node label.\n* topology.kubernetes.io/zone: the value indicates the zone where the\n endpoint is located. This should match the corresponding node label.\n* topology.kubernetes.io/region: the value indicates the region where the\n endpoint is located. This should match the corresponding node label.\nThis field is deprecated and will be removed in future api versions.",
"nodeName": "nodeName represents the name of the Node hosting this endpoint. This can be used to determine endpoints local to a Node. This field can be enabled with the EndpointSliceNodeName feature gate.", "nodeName": "nodeName represents the name of the Node hosting this endpoint. This can be used to determine endpoints local to a Node. This field can be enabled with the EndpointSliceNodeName feature gate.",
"hints": "hints contains information associated with how an endpoint should be consumed.",
} }
func (Endpoint) SwaggerDoc() map[string]string { func (Endpoint) SwaggerDoc() map[string]string {
@ -52,6 +53,15 @@ func (EndpointConditions) SwaggerDoc() map[string]string {
return map_EndpointConditions return map_EndpointConditions
} }
var map_EndpointHints = map[string]string{
"": "EndpointHints provides hints describing how an endpoint should be consumed.",
"forZones": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing. May contain a maximum of 8 entries.",
}
func (EndpointHints) SwaggerDoc() map[string]string {
return map_EndpointHints
}
var map_EndpointPort = map[string]string{ var map_EndpointPort = map[string]string{
"": "EndpointPort represents a Port used by an EndpointSlice", "": "EndpointPort represents a Port used by an EndpointSlice",
"name": "The name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is dervied from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.", "name": "The name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is dervied from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.",
@ -86,4 +96,13 @@ func (EndpointSliceList) SwaggerDoc() map[string]string {
return map_EndpointSliceList return map_EndpointSliceList
} }
var map_ForZone = map[string]string{
"": "ForZone provides information about which zones should consume this endpoint.",
"name": "name represents the name of the zone.",
}
func (ForZone) SwaggerDoc() map[string]string {
return map_ForZone
}
// AUTO-GENERATED FUNCTIONS END HERE // AUTO-GENERATED FUNCTIONS END HERE

View File

@ -56,6 +56,11 @@ func (in *Endpoint) DeepCopyInto(out *Endpoint) {
*out = new(string) *out = new(string)
**out = **in **out = **in
} }
if in.Hints != nil {
in, out := &in.Hints, &out.Hints
*out = new(EndpointHints)
(*in).DeepCopyInto(*out)
}
return return
} }
@ -100,6 +105,27 @@ func (in *EndpointConditions) DeepCopy() *EndpointConditions {
return out 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) { func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
*out = *in *out = *in
@ -208,3 +234,19 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object {
} }
return nil 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
}

View File

@ -65,15 +65,22 @@
"27": "28" "27": "28"
}, },
"nodeName": "29", "nodeName": "29",
"zone": "30" "zone": "30",
"hints": {
"forZones": [
{
"name": "31"
}
]
}
} }
], ],
"ports": [ "ports": [
{ {
"name": "31", "name": "32",
"protocol": "脽ěĂ凗蓏Ŋ蛊ĉy緅縕", "protocol": "ěĂ凗蓏Ŋ蛊ĉy緅縕",
"port": -591435092, "port": -591435092,
"appProtocol": "32" "appProtocol": "33"
} }
] ]
} }

View File

@ -9,6 +9,9 @@ endpoints:
terminating: false terminating: false
deprecatedTopology: deprecatedTopology:
"27": "28" "27": "28"
hints:
forZones:
- name: "31"
hostname: "20" hostname: "20"
nodeName: "29" nodeName: "29"
targetRef: targetRef:
@ -51,7 +54,7 @@ metadata:
selfLink: "5" selfLink: "5"
uid: "7" uid: "7"
ports: ports:
- appProtocol: "32" - appProtocol: "33"
name: "31" name: "32"
port: -591435092 port: -591435092
protocol: ěĂ凗蓏Ŋ蛊ĉy緅縕 protocol: ěĂ凗蓏Ŋ蛊ĉy緅縕

View File

@ -64,15 +64,22 @@
"topology": { "topology": {
"27": "28" "27": "28"
}, },
"nodeName": "29" "nodeName": "29",
"hints": {
"forZones": [
{
"name": "30"
}
]
}
} }
], ],
"ports": [ "ports": [
{ {
"name": "30", "name": "31",
"protocol": "脽ěĂ凗蓏Ŋ蛊ĉy緅縕", "protocol": "ěĂ凗蓏Ŋ蛊ĉy緅縕",
"port": -591435092, "port": -591435092,
"appProtocol": "31" "appProtocol": "32"
} }
] ]
} }

View File

@ -7,6 +7,9 @@ endpoints:
ready: false ready: false
serving: false serving: false
terminating: false terminating: false
hints:
forZones:
- name: "30"
hostname: "20" hostname: "20"
nodeName: "29" nodeName: "29"
targetRef: targetRef:
@ -50,7 +53,7 @@ metadata:
selfLink: "5" selfLink: "5"
uid: "7" uid: "7"
ports: ports:
- appProtocol: "31" - appProtocol: "32"
name: "30" name: "31"
port: -591435092 port: -591435092
protocol: ěĂ凗蓏Ŋ蛊ĉy緅縕 protocol: ěĂ凗蓏Ŋ蛊ĉy緅縕

View File

@ -32,6 +32,7 @@ type EndpointApplyConfiguration struct {
DeprecatedTopology map[string]string `json:"deprecatedTopology,omitempty"` DeprecatedTopology map[string]string `json:"deprecatedTopology,omitempty"`
NodeName *string `json:"nodeName,omitempty"` NodeName *string `json:"nodeName,omitempty"`
Zone *string `json:"zone,omitempty"` Zone *string `json:"zone,omitempty"`
Hints *EndpointHintsApplyConfiguration `json:"hints,omitempty"`
} }
// EndpointApplyConfiguration constructs an declarative configuration of the Endpoint type for use with // EndpointApplyConfiguration constructs an declarative configuration of the Endpoint type for use with
@ -103,3 +104,11 @@ func (b *EndpointApplyConfiguration) WithZone(value string) *EndpointApplyConfig
b.Zone = &value b.Zone = &value
return b return b
} }
// WithHints sets the Hints field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Hints field is set to the value of the last call.
func (b *EndpointApplyConfiguration) WithHints(value *EndpointHintsApplyConfiguration) *EndpointApplyConfiguration {
b.Hints = value
return b
}

View File

@ -0,0 +1,44 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
// EndpointHintsApplyConfiguration represents an declarative configuration of the EndpointHints type for use
// with apply.
type EndpointHintsApplyConfiguration struct {
ForZones []ForZoneApplyConfiguration `json:"forZones,omitempty"`
}
// EndpointHintsApplyConfiguration constructs an declarative configuration of the EndpointHints type for use with
// apply.
func EndpointHints() *EndpointHintsApplyConfiguration {
return &EndpointHintsApplyConfiguration{}
}
// WithForZones adds the given value to the ForZones field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the ForZones field.
func (b *EndpointHintsApplyConfiguration) WithForZones(values ...*ForZoneApplyConfiguration) *EndpointHintsApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithForZones")
}
b.ForZones = append(b.ForZones, *values[i])
}
return b
}

View File

@ -0,0 +1,39 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
// ForZoneApplyConfiguration represents an declarative configuration of the ForZone type for use
// with apply.
type ForZoneApplyConfiguration struct {
Name *string `json:"name,omitempty"`
}
// ForZoneApplyConfiguration constructs an declarative configuration of the ForZone type for use with
// apply.
func ForZone() *ForZoneApplyConfiguration {
return &ForZoneApplyConfiguration{}
}
// WithName sets the Name field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Name field is set to the value of the last call.
func (b *ForZoneApplyConfiguration) WithName(value string) *ForZoneApplyConfiguration {
b.Name = &value
return b
}

View File

@ -31,6 +31,7 @@ type EndpointApplyConfiguration struct {
TargetRef *v1.ObjectReferenceApplyConfiguration `json:"targetRef,omitempty"` TargetRef *v1.ObjectReferenceApplyConfiguration `json:"targetRef,omitempty"`
Topology map[string]string `json:"topology,omitempty"` Topology map[string]string `json:"topology,omitempty"`
NodeName *string `json:"nodeName,omitempty"` NodeName *string `json:"nodeName,omitempty"`
Hints *EndpointHintsApplyConfiguration `json:"hints,omitempty"`
} }
// EndpointApplyConfiguration constructs an declarative configuration of the Endpoint type for use with // EndpointApplyConfiguration constructs an declarative configuration of the Endpoint type for use with
@ -94,3 +95,11 @@ func (b *EndpointApplyConfiguration) WithNodeName(value string) *EndpointApplyCo
b.NodeName = &value b.NodeName = &value
return b return b
} }
// WithHints sets the Hints field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Hints field is set to the value of the last call.
func (b *EndpointApplyConfiguration) WithHints(value *EndpointHintsApplyConfiguration) *EndpointApplyConfiguration {
b.Hints = value
return b
}

View File

@ -0,0 +1,44 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1beta1
// EndpointHintsApplyConfiguration represents an declarative configuration of the EndpointHints type for use
// with apply.
type EndpointHintsApplyConfiguration struct {
ForZones []ForZoneApplyConfiguration `json:"forZones,omitempty"`
}
// EndpointHintsApplyConfiguration constructs an declarative configuration of the EndpointHints type for use with
// apply.
func EndpointHints() *EndpointHintsApplyConfiguration {
return &EndpointHintsApplyConfiguration{}
}
// WithForZones adds the given value to the ForZones field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the ForZones field.
func (b *EndpointHintsApplyConfiguration) WithForZones(values ...*ForZoneApplyConfiguration) *EndpointHintsApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithForZones")
}
b.ForZones = append(b.ForZones, *values[i])
}
return b
}

View File

@ -0,0 +1,39 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1beta1
// ForZoneApplyConfiguration represents an declarative configuration of the ForZone type for use
// with apply.
type ForZoneApplyConfiguration struct {
Name *string `json:"name,omitempty"`
}
// ForZoneApplyConfiguration constructs an declarative configuration of the ForZone type for use with
// apply.
func ForZone() *ForZoneApplyConfiguration {
return &ForZoneApplyConfiguration{}
}
// WithName sets the Name field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Name field is set to the value of the last call.
func (b *ForZoneApplyConfiguration) WithName(value string) *ForZoneApplyConfiguration {
b.Name = &value
return b
}

View File

@ -777,20 +777,28 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &applyconfigurationsdiscoveryv1.EndpointApplyConfiguration{} return &applyconfigurationsdiscoveryv1.EndpointApplyConfiguration{}
case discoveryv1.SchemeGroupVersion.WithKind("EndpointConditions"): case discoveryv1.SchemeGroupVersion.WithKind("EndpointConditions"):
return &applyconfigurationsdiscoveryv1.EndpointConditionsApplyConfiguration{} return &applyconfigurationsdiscoveryv1.EndpointConditionsApplyConfiguration{}
case discoveryv1.SchemeGroupVersion.WithKind("EndpointHints"):
return &applyconfigurationsdiscoveryv1.EndpointHintsApplyConfiguration{}
case discoveryv1.SchemeGroupVersion.WithKind("EndpointPort"): case discoveryv1.SchemeGroupVersion.WithKind("EndpointPort"):
return &applyconfigurationsdiscoveryv1.EndpointPortApplyConfiguration{} return &applyconfigurationsdiscoveryv1.EndpointPortApplyConfiguration{}
case discoveryv1.SchemeGroupVersion.WithKind("EndpointSlice"): case discoveryv1.SchemeGroupVersion.WithKind("EndpointSlice"):
return &applyconfigurationsdiscoveryv1.EndpointSliceApplyConfiguration{} return &applyconfigurationsdiscoveryv1.EndpointSliceApplyConfiguration{}
case discoveryv1.SchemeGroupVersion.WithKind("ForZone"):
return &applyconfigurationsdiscoveryv1.ForZoneApplyConfiguration{}
// Group=discovery.k8s.io, Version=v1beta1 // Group=discovery.k8s.io, Version=v1beta1
case discoveryv1beta1.SchemeGroupVersion.WithKind("Endpoint"): case discoveryv1beta1.SchemeGroupVersion.WithKind("Endpoint"):
return &applyconfigurationsdiscoveryv1beta1.EndpointApplyConfiguration{} return &applyconfigurationsdiscoveryv1beta1.EndpointApplyConfiguration{}
case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointConditions"): case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointConditions"):
return &applyconfigurationsdiscoveryv1beta1.EndpointConditionsApplyConfiguration{} return &applyconfigurationsdiscoveryv1beta1.EndpointConditionsApplyConfiguration{}
case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointHints"):
return &applyconfigurationsdiscoveryv1beta1.EndpointHintsApplyConfiguration{}
case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointPort"): case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointPort"):
return &applyconfigurationsdiscoveryv1beta1.EndpointPortApplyConfiguration{} return &applyconfigurationsdiscoveryv1beta1.EndpointPortApplyConfiguration{}
case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointSlice"): case discoveryv1beta1.SchemeGroupVersion.WithKind("EndpointSlice"):
return &applyconfigurationsdiscoveryv1beta1.EndpointSliceApplyConfiguration{} return &applyconfigurationsdiscoveryv1beta1.EndpointSliceApplyConfiguration{}
case discoveryv1beta1.SchemeGroupVersion.WithKind("ForZone"):
return &applyconfigurationsdiscoveryv1beta1.ForZoneApplyConfiguration{}
// Group=events.k8s.io, Version=v1 // Group=events.k8s.io, Version=v1
case eventsv1.SchemeGroupVersion.WithKind("Event"): case eventsv1.SchemeGroupVersion.WithKind("Event"):