Merge pull request #113351 from andrewsykim/endpointslice-terminating-ga

Promote EndpointSliceTerminatingCondition to GA
This commit is contained in:
Kubernetes Prow Robot 2022-11-04 09:36:39 -07:00 committed by GitHub
commit b20ddbd75a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 286 additions and 968 deletions

View File

@ -9629,11 +9629,11 @@
"type": "boolean" "type": "boolean"
}, },
"serving": { "serving": {
"description": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "description": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
"type": "boolean" "type": "boolean"
}, },
"terminating": { "terminating": {
"description": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "description": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
"type": "boolean" "type": "boolean"
} }
}, },

View File

@ -107,11 +107,11 @@
"type": "boolean" "type": "boolean"
}, },
"serving": { "serving": {
"description": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "description": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
"type": "boolean" "type": "boolean"
}, },
"terminating": { "terminating": {
"description": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "description": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
"type": "boolean" "type": "boolean"
} }
}, },

View File

@ -118,15 +118,13 @@ type EndpointConditions struct {
// serving is identical to ready except that it is set regardless of the // serving is identical to ready except that it is set regardless of the
// terminating state of endpoints. This condition should be set to true for // terminating state of endpoints. This condition should be set to true for
// a ready endpoint that is terminating. If nil, consumers should defer to // a ready endpoint that is terminating. If nil, consumers should defer to
// the ready condition. This field can be enabled with the // the ready condition.
// EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Serving *bool Serving *bool
// terminating indicates that this endpoint is terminating. A nil value // terminating indicates that this endpoint is terminating. A nil value
// indicates an unknown state. Consumers should interpret this unknown state // indicates an unknown state. Consumers should interpret this unknown state
// to mean that the endpoint is not terminating. This field can be enabled // to mean that the endpoint is not terminating.
// with the EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Terminating *bool Terminating *bool
} }

View File

@ -35,17 +35,14 @@ import (
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing" k8stesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache" "k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice" endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -418,12 +415,11 @@ func TestSyncService(t *testing.T) {
deletionTimestamp := metav1.Now() deletionTimestamp := metav1.Now()
testcases := []struct { testcases := []struct {
name string name string
service *v1.Service service *v1.Service
pods []*v1.Pod pods []*v1.Pod
expectedEndpointPorts []discovery.EndpointPort expectedEndpointPorts []discovery.EndpointPort
expectedEndpoints []discovery.Endpoint expectedEndpoints []discovery.Endpoint
terminatingGateEnabled bool
}{ }{
{ {
name: "pods with multiple IPs and Service with ipFamilies=ipv4", name: "pods with multiple IPs and Service with ipFamilies=ipv4",
@ -522,7 +518,9 @@ func TestSyncService(t *testing.T) {
expectedEndpoints: []discovery.Endpoint{ expectedEndpoints: []discovery.Endpoint{
{ {
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true), Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
}, },
Addresses: []string{"10.0.0.1"}, Addresses: []string{"10.0.0.1"},
TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod0"}, TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod0"},
@ -530,7 +528,9 @@ func TestSyncService(t *testing.T) {
}, },
{ {
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true), Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
}, },
Addresses: []string{"10.0.0.2"}, Addresses: []string{"10.0.0.2"},
TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"}, TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"},
@ -635,7 +635,9 @@ func TestSyncService(t *testing.T) {
expectedEndpoints: []discovery.Endpoint{ expectedEndpoints: []discovery.Endpoint{
{ {
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true), Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
}, },
Addresses: []string{"fd08::5678:0000:0000:9abc:def0"}, Addresses: []string{"fd08::5678:0000:0000:9abc:def0"},
TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"}, TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"},
@ -644,7 +646,7 @@ func TestSyncService(t *testing.T) {
}, },
}, },
{ {
name: "Terminating pods with EndpointSliceTerminatingCondition enabled", name: "Terminating pods",
service: &v1.Service{ service: &v1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "foobar", Name: "foobar",
@ -757,114 +759,9 @@ func TestSyncService(t *testing.T) {
NodeName: utilpointer.StringPtr("node-1"), NodeName: utilpointer.StringPtr("node-1"),
}, },
}, },
terminatingGateEnabled: true,
}, },
{ {
name: "Terminating pods with EndpointSliceTerminatingCondition disabled", name: "Not ready terminating pods",
service: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar",
Namespace: "default",
CreationTimestamp: creationTimestamp,
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{Name: "tcp-example", TargetPort: intstr.FromInt(80), Protocol: v1.ProtocolTCP},
{Name: "udp-example", TargetPort: intstr.FromInt(161), Protocol: v1.ProtocolUDP},
{Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP},
},
Selector: map[string]string{"foo": "bar"},
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
},
},
pods: []*v1.Pod{
{
// one ready pod for comparison
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pod0",
Labels: map[string]string{"foo": "bar"},
DeletionTimestamp: nil,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "container-1",
}},
NodeName: "node-1",
},
Status: v1.PodStatus{
PodIP: "10.0.0.1",
PodIPs: []v1.PodIP{{
IP: "10.0.0.1",
}},
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pod1",
Labels: map[string]string{"foo": "bar"},
DeletionTimestamp: &deletionTimestamp,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "container-1",
}},
NodeName: "node-1",
},
Status: v1.PodStatus{
PodIP: "10.0.0.2",
PodIPs: []v1.PodIP{
{
IP: "10.0.0.2",
},
},
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
},
},
},
expectedEndpointPorts: []discovery.EndpointPort{
{
Name: utilpointer.StringPtr("sctp-example"),
Protocol: protoPtr(v1.ProtocolSCTP),
Port: utilpointer.Int32Ptr(int32(3456)),
},
{
Name: utilpointer.StringPtr("udp-example"),
Protocol: protoPtr(v1.ProtocolUDP),
Port: utilpointer.Int32Ptr(int32(161)),
},
{
Name: utilpointer.StringPtr("tcp-example"),
Protocol: protoPtr(v1.ProtocolTCP),
Port: utilpointer.Int32Ptr(int32(80)),
},
},
expectedEndpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
},
Addresses: []string{"10.0.0.1"},
TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod0"},
NodeName: utilpointer.StringPtr("node-1"),
},
},
terminatingGateEnabled: false,
},
{
name: "Not ready terminating pods with EndpointSliceTerminatingCondition enabled",
service: &v1.Service{ service: &v1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "foobar", Name: "foobar",
@ -977,118 +874,11 @@ func TestSyncService(t *testing.T) {
NodeName: utilpointer.StringPtr("node-1"), NodeName: utilpointer.StringPtr("node-1"),
}, },
}, },
terminatingGateEnabled: true,
},
{
name: "Not ready terminating pods with EndpointSliceTerminatingCondition disabled",
service: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar",
Namespace: "default",
CreationTimestamp: creationTimestamp,
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{Name: "tcp-example", TargetPort: intstr.FromInt(80), Protocol: v1.ProtocolTCP},
{Name: "udp-example", TargetPort: intstr.FromInt(161), Protocol: v1.ProtocolUDP},
{Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP},
},
Selector: map[string]string{"foo": "bar"},
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
},
},
pods: []*v1.Pod{
{
// one ready pod for comparison
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pod0",
Labels: map[string]string{"foo": "bar"},
DeletionTimestamp: nil,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "container-1",
}},
NodeName: "node-1",
},
Status: v1.PodStatus{
PodIP: "10.0.0.1",
PodIPs: []v1.PodIP{{
IP: "10.0.0.1",
}},
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pod1",
Labels: map[string]string{"foo": "bar"},
DeletionTimestamp: &deletionTimestamp,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "container-1",
}},
NodeName: "node-1",
},
Status: v1.PodStatus{
PodIP: "10.0.0.2",
PodIPs: []v1.PodIP{
{
IP: "10.0.0.2",
},
},
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionFalse,
},
},
},
},
},
expectedEndpointPorts: []discovery.EndpointPort{
{
Name: utilpointer.StringPtr("sctp-example"),
Protocol: protoPtr(v1.ProtocolSCTP),
Port: utilpointer.Int32Ptr(int32(3456)),
},
{
Name: utilpointer.StringPtr("udp-example"),
Protocol: protoPtr(v1.ProtocolUDP),
Port: utilpointer.Int32Ptr(int32(161)),
},
{
Name: utilpointer.StringPtr("tcp-example"),
Protocol: protoPtr(v1.ProtocolTCP),
Port: utilpointer.Int32Ptr(int32(80)),
},
},
expectedEndpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
},
Addresses: []string{"10.0.0.1"},
TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod0"},
NodeName: utilpointer.StringPtr("node-1"),
},
},
terminatingGateEnabled: false,
}, },
} }
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)()
client, esController := newController([]string{"node-1"}, time.Duration(0)) client, esController := newController([]string{"node-1"}, time.Duration(0))
for _, pod := range testcase.pods { for _, pod := range testcase.pods {

View File

@ -30,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
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/klog/v2"
@ -38,7 +37,6 @@ import (
"k8s.io/kubernetes/pkg/controller/endpointslice/topologycache" "k8s.io/kubernetes/pkg/controller/endpointslice/topologycache"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice" endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice"
"k8s.io/kubernetes/pkg/features"
) )
// reconciler is responsible for transforming current EndpointSlice state into // reconciler is responsible for transforming current EndpointSlice state into
@ -154,8 +152,7 @@ func (r *reconciler) reconcileByAddressType(service *corev1.Service, pods []*cor
desiredEndpointsByPortMap := map[endpointutil.PortMapKey]endpointsliceutil.EndpointSet{} desiredEndpointsByPortMap := map[endpointutil.PortMapKey]endpointsliceutil.EndpointSet{}
for _, pod := range pods { for _, pod := range pods {
includeTerminating := service.Spec.PublishNotReadyAddresses || utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceTerminatingCondition) if !endpointutil.ShouldPodBeInEndpoints(pod, true) {
if !endpointutil.ShouldPodBeInEndpoints(pod, includeTerminating) {
continue continue
} }

View File

@ -32,18 +32,15 @@ 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"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
k8stesting "k8s.io/client-go/testing" k8stesting "k8s.io/client-go/testing"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"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/controller/endpointslice/topologycache"
endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice" endpointsliceutil "k8s.io/kubernetes/pkg/controller/util/endpointslice"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -119,17 +116,20 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpoint discovery.Endpoint expectedEndpoint discovery.Endpoint
expectedLabels map[string]string expectedLabels map[string]string
expectedEndpointPerSlice map[discovery.AddressType][]discovery.Endpoint expectedEndpointPerSlice map[discovery.AddressType][]discovery.Endpoint
terminatingGateEnabled bool
}{ }{
"no-family-service": { "no-family-service": {
service: noFamilyService, service: noFamilyService,
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
{ {
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -144,29 +144,6 @@ func TestReconcile1Pod(t *testing.T) {
}, },
}, },
"ipv4": { "ipv4": {
service: svcv4,
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: {
{
Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{
Kind: "Pod",
Namespace: namespace,
Name: "pod1",
},
},
},
},
expectedLabels: map[string]string{
discovery.LabelManagedBy: controllerName,
discovery.LabelServiceName: "foo",
corev1.IsHeadlessService: "",
},
},
"ipv4-with-terminating-gate-enabled": {
service: svcv4, service: svcv4,
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
@ -192,17 +169,20 @@ func TestReconcile1Pod(t *testing.T) {
discovery.LabelServiceName: "foo", discovery.LabelServiceName: "foo",
corev1.IsHeadlessService: "", corev1.IsHeadlessService: "",
}, },
terminatingGateEnabled: true,
}, },
"ipv4-clusterip": { "ipv4-clusterip": {
service: svcv4ClusterIP, service: svcv4ClusterIP,
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
{ {
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -213,10 +193,14 @@ func TestReconcile1Pod(t *testing.T) {
}, },
expectedAddressType: discovery.AddressTypeIPv4, expectedAddressType: discovery.AddressTypeIPv4,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -233,10 +217,14 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
{ {
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -247,10 +235,14 @@ func TestReconcile1Pod(t *testing.T) {
}, },
expectedAddressType: discovery.AddressTypeIPv4, expectedAddressType: discovery.AddressTypeIPv4,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -269,10 +261,14 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
{ {
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -283,10 +279,14 @@ func TestReconcile1Pod(t *testing.T) {
}, },
expectedAddressType: discovery.AddressTypeIPv4, expectedAddressType: discovery.AddressTypeIPv4,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -305,10 +305,14 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv6: { discovery.AddressTypeIPv6: {
{ {
Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, Addresses: []string{"1234::5678:0000:0000:9abc:def0"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -329,10 +333,14 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv6: { discovery.AddressTypeIPv6: {
{ {
Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, Addresses: []string{"1234::5678:0000:0000:9abc:def0"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -352,10 +360,14 @@ func TestReconcile1Pod(t *testing.T) {
expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{ expectedEndpointPerSlice: map[discovery.AddressType][]discovery.Endpoint{
discovery.AddressTypeIPv6: { discovery.AddressTypeIPv6: {
{ {
Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, Addresses: []string{"1234::5678:0000:0000:9abc:def0"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -365,10 +377,14 @@ func TestReconcile1Pod(t *testing.T) {
}, },
discovery.AddressTypeIPv4: { discovery.AddressTypeIPv4: {
{ {
Addresses: []string{"1.2.3.4"}, Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Conditions: discovery.EndpointConditions{
Zone: utilpointer.StringPtr("us-central1-a"), Ready: utilpointer.BoolPtr(true),
NodeName: utilpointer.StringPtr("node-1"), Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &corev1.ObjectReference{ TargetRef: &corev1.ObjectReference{
Kind: "Pod", Kind: "Pod",
Namespace: namespace, Namespace: namespace,
@ -386,8 +402,6 @@ func TestReconcile1Pod(t *testing.T) {
for name, testCase := range testCases { for name, testCase := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testCase.terminatingGateEnabled)()
client := newClientset() client := newClientset()
setupMetrics() setupMetrics()
triggerTime := time.Now().UTC() triggerTime := time.Now().UTC()

View File

@ -20,14 +20,13 @@ import (
"fmt" "fmt"
"time" "time"
"k8s.io/api/core/v1" v1 "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"
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" "k8s.io/apimachinery/pkg/util/sets"
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"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
@ -35,7 +34,6 @@ import (
"k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/apis/discovery/validation" "k8s.io/kubernetes/pkg/apis/discovery/validation"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features"
utilnet "k8s.io/utils/net" utilnet "k8s.io/utils/net"
) )
@ -49,7 +47,9 @@ func podToEndpoint(pod *v1.Pod, node *v1.Node, service *v1.Service, addressType
ep := discovery.Endpoint{ ep := discovery.Endpoint{
Addresses: getEndpointAddresses(pod.Status, service, addressType), Addresses: getEndpointAddresses(pod.Status, service, addressType),
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
Ready: &ready, Ready: &ready,
Serving: &serving,
Terminating: &terminating,
}, },
TargetRef: &v1.ObjectReference{ TargetRef: &v1.ObjectReference{
Kind: "Pod", Kind: "Pod",
@ -59,11 +59,6 @@ func podToEndpoint(pod *v1.Pod, node *v1.Node, service *v1.Service, addressType
}, },
} }
if utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceTerminatingCondition) {
ep.Conditions.Serving = &serving
ep.Conditions.Terminating = &terminating
}
if pod.Spec.NodeName != "" { if pod.Spec.NodeName != "" {
ep.NodeName = &pod.Spec.NodeName ep.NodeName = &pod.Spec.NodeName
} }

View File

@ -31,11 +31,8 @@ import (
"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/rand" "k8s.io/apimachinery/pkg/util/rand"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing" k8stesting "k8s.io/client-go/testing"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -251,131 +248,11 @@ func TestPodToEndpoint(t *testing.T) {
svc *v1.Service svc *v1.Service
expectedEndpoint discovery.Endpoint expectedEndpoint discovery.Endpoint
publishNotReadyAddresses bool publishNotReadyAddresses bool
terminatingGateEnabled bool
}{ }{
{ {
name: "Ready pod", name: "Ready pod",
pod: readyPod, pod: readyPod,
svc: &svc, svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready pod + publishNotReadyAddresses",
pod: readyPod,
svc: &svcPublishNotReady,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Unready pod",
pod: unreadyPod,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Unready pod + publishNotReadyAddresses",
pod: unreadyPod,
svc: &svcPublishNotReady,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready pod + node labels",
pod: readyPod,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Multi IP Ready pod + node labels",
pod: multiIPPod,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready pod + hostname",
pod: readyPodHostname,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
Hostname: &readyPodHostname.Spec.Hostname,
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPodHostname.Name,
UID: readyPodHostname.UID,
},
},
},
{
name: "Ready pod, terminating gate enabled",
pod: readyPod,
svc: &svc,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"}, Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
@ -391,16 +268,17 @@ func TestPodToEndpoint(t *testing.T) {
UID: readyPod.UID, UID: readyPod.UID,
}, },
}, },
terminatingGateEnabled: true,
}, },
{ {
name: "Ready terminating pod, terminating gate disabled", name: "Ready pod + publishNotReadyAddresses",
pod: readyTerminatingPod, pod: readyPod,
svc: &svc, svc: &svcPublishNotReady,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"}, Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{ Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(false), Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
}, },
NodeName: utilpointer.StringPtr("node-1"), NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{ TargetRef: &v1.ObjectReference{
@ -410,10 +288,136 @@ func TestPodToEndpoint(t *testing.T) {
UID: readyPod.UID, UID: readyPod.UID,
}, },
}, },
terminatingGateEnabled: false,
}, },
{ {
name: "Ready terminating pod, terminating gate enabled", name: "Unready pod",
pod: unreadyPod,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(false),
Serving: utilpointer.BoolPtr(false),
Terminating: utilpointer.BoolPtr(false),
},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Unready pod + publishNotReadyAddresses",
pod: unreadyPod,
svc: &svcPublishNotReady,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(false),
Terminating: utilpointer.BoolPtr(false),
},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready pod + node labels",
pod: readyPod,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Multi IP Ready pod + node labels",
pod: multiIPPod,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.4"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready pod + hostname",
pod: readyPodHostname,
node: node1,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
Hostname: &readyPodHostname.Spec.Hostname,
Zone: utilpointer.StringPtr("us-central1-a"),
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPodHostname.Name,
UID: readyPodHostname.UID,
},
},
},
{
name: "Ready pod",
pod: readyPod,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(true),
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
},
{
name: "Ready terminating pod",
pod: readyTerminatingPod, pod: readyTerminatingPod,
svc: &svc, svc: &svc,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
@ -431,29 +435,9 @@ func TestPodToEndpoint(t *testing.T) {
UID: readyPod.UID, UID: readyPod.UID,
}, },
}, },
terminatingGateEnabled: true,
}, },
{ {
name: "Not ready terminating pod, terminating gate disabled", name: "Not ready terminating pod",
pod: unreadyTerminatingPod,
svc: &svc,
expectedEndpoint: discovery.Endpoint{
Addresses: []string{"1.2.3.5"},
Conditions: discovery.EndpointConditions{
Ready: utilpointer.BoolPtr(false),
},
NodeName: utilpointer.StringPtr("node-1"),
TargetRef: &v1.ObjectReference{
Kind: "Pod",
Namespace: ns,
Name: readyPod.Name,
UID: readyPod.UID,
},
},
terminatingGateEnabled: false,
},
{
name: "Not ready terminating pod, terminating gate enabled",
pod: unreadyTerminatingPod, pod: unreadyTerminatingPod,
svc: &svc, svc: &svc,
expectedEndpoint: discovery.Endpoint{ expectedEndpoint: discovery.Endpoint{
@ -471,14 +455,11 @@ func TestPodToEndpoint(t *testing.T) {
UID: readyPod.UID, UID: readyPod.UID,
}, },
}, },
terminatingGateEnabled: true,
}, },
} }
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)()
endpoint := podToEndpoint(testCase.pod, testCase.node, testCase.svc, discovery.AddressTypeIPv4) endpoint := podToEndpoint(testCase.pod, testCase.node, testCase.svc, discovery.AddressTypeIPv4)
if !reflect.DeepEqual(testCase.expectedEndpoint, endpoint) { if !reflect.DeepEqual(testCase.expectedEndpoint, endpoint) {
t.Errorf("Expected endpoint: %+v, got: %+v", testCase.expectedEndpoint, endpoint) t.Errorf("Expected endpoint: %+v, got: %+v", testCase.expectedEndpoint, endpoint)

View File

@ -306,16 +306,11 @@ func EndpointsEqualBeyondHash(ep1, ep2 *discovery.Endpoint) bool {
return false return false
} }
// Serving and Terminating will only be set when the EndpointSliceTerminatingCondition feature is on. if boolPtrChanged(ep1.Conditions.Serving, ep2.Conditions.Serving) {
// Ignore their difference if the expected or actual value is nil, which means the feature enablement is changed.
// Otherwise all EndpointSlices in the system would be updated on the first controller-manager restart even without
// actual changes, leading to delay in processing legitimate updates.
// Its value will be set to the expected one when there is an actual change triggering update of this EndpointSlice.
if ep1.Conditions.Serving != nil && ep2.Conditions.Serving != nil && *ep1.Conditions.Serving != *ep2.Conditions.Serving {
return false return false
} }
if ep1.Conditions.Terminating != nil && ep2.Conditions.Terminating != nil && *ep1.Conditions.Terminating != *ep2.Conditions.Terminating { if boolPtrChanged(ep1.Conditions.Terminating, ep2.Conditions.Terminating) {
return false return false
} }

View File

@ -776,7 +776,7 @@ func TestEndpointsEqualBeyondHash(t *testing.T) {
Zone: utilpointer.StringPtr("zone-1"), Zone: utilpointer.StringPtr("zone-1"),
NodeName: utilpointer.StringPtr("node-1"), NodeName: utilpointer.StringPtr("node-1"),
}, },
expected: true, expected: false,
}, },
{ {
name: "Serving condition changed from false to true", name: "Serving condition changed from false to true",

View File

@ -269,6 +269,7 @@ const (
// kep: https://kep.k8s.io/1672 // kep: https://kep.k8s.io/1672
// alpha: v1.20 // alpha: v1.20
// beta: v1.22 // beta: v1.22
// GA: v1.26
// //
// Enable Terminating condition in Endpoint Slices. // Enable Terminating condition in Endpoint Slices.
EndpointSliceTerminatingCondition featuregate.Feature = "EndpointSliceTerminatingCondition" EndpointSliceTerminatingCondition featuregate.Feature = "EndpointSliceTerminatingCondition"
@ -899,7 +900,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
DownwardAPIHugePages: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22 DownwardAPIHugePages: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22
EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta}, EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in v1.28
EphemeralContainers: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27 EphemeralContainers: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27

View File

@ -27012,14 +27012,14 @@ func schema_k8sio_api_discovery_v1_EndpointConditions(ref common.ReferenceCallba
}, },
"serving": { "serving": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", Description: "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
Type: []string{"boolean"}, Type: []string{"boolean"},
Format: "", Format: "",
}, },
}, },
"terminating": { "terminating": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", Description: "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
Type: []string{"boolean"}, Type: []string{"boolean"},
Format: "", Format: "",
}, },
@ -27367,14 +27367,14 @@ func schema_k8sio_api_discovery_v1beta1_EndpointConditions(ref common.ReferenceC
}, },
"serving": { "serving": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", Description: "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
Type: []string{"boolean"}, Type: []string{"boolean"},
Format: "", Format: "",
}, },
}, },
"terminating": { "terminating": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", Description: "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
Type: []string{"boolean"}, Type: []string{"boolean"},
Format: "", Format: "",
}, },

View File

@ -123,15 +123,10 @@ 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)
dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints)
if dropHints || dropTerminating { if dropHints {
for i := range endpointSlice.Endpoints { for i := range endpointSlice.Endpoints {
if dropTerminating {
endpointSlice.Endpoints[i].Conditions.Serving = nil
endpointSlice.Endpoints[i].Conditions.Terminating = nil
}
if dropHints { if dropHints {
endpointSlice.Endpoints[i].Hints = nil endpointSlice.Endpoints[i].Hints = nil
} }
@ -142,16 +137,6 @@ func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) {
// dropDisabledFieldsOnUpdate will drop any disable fields that have not already // dropDisabledFieldsOnUpdate will drop any disable fields that have not already
// been set on the EndpointSlice. // been set on the EndpointSlice.
func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) { func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) {
dropTerminating := !utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceTerminatingCondition)
if dropTerminating {
for _, ep := range oldEPS.Endpoints {
if ep.Conditions.Serving != nil || ep.Conditions.Terminating != nil {
dropTerminating = false
break
}
}
}
dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints)
if dropHints { if dropHints {
for _, ep := range oldEPS.Endpoints { for _, ep := range oldEPS.Endpoints {
@ -162,12 +147,8 @@ func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) {
} }
} }
if dropHints || dropTerminating { if dropHints {
for i := range newEPS.Endpoints { for i := range newEPS.Endpoints {
if dropTerminating {
newEPS.Endpoints[i].Conditions.Serving = nil
newEPS.Endpoints[i].Conditions.Terminating = nil
}
if dropHints { if dropHints {
newEPS.Endpoints[i].Hints = nil newEPS.Endpoints[i].Hints = nil
} }

View File

@ -34,108 +34,11 @@ import (
func Test_dropDisabledFieldsOnCreate(t *testing.T) { func Test_dropDisabledFieldsOnCreate(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
terminatingGateEnabled bool hintsGateEnabled bool
hintsGateEnabled bool eps *discovery.EndpointSlice
eps *discovery.EndpointSlice expectedEPS *discovery.EndpointSlice
expectedEPS *discovery.EndpointSlice
}{ }{
{
name: "terminating gate enabled, field should be allowed",
terminatingGateEnabled: true,
eps: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
},
{
name: "terminating gate disabled, field should be set to nil",
terminatingGateEnabled: false,
eps: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
},
{ {
name: "node name gate enabled, field should be allowed", name: "node name gate enabled, field should be allowed",
eps: &discovery.EndpointSlice{ eps: &discovery.EndpointSlice{
@ -163,7 +66,6 @@ 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.TopologyAwareHints, testcase.hintsGateEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, testcase.hintsGateEnabled)()
dropDisabledFieldsOnCreate(testcase.eps) dropDisabledFieldsOnCreate(testcase.eps)
@ -178,290 +80,12 @@ func Test_dropDisabledFieldsOnCreate(t *testing.T) {
func Test_dropDisabledFieldsOnUpdate(t *testing.T) { func Test_dropDisabledFieldsOnUpdate(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
terminatingGateEnabled bool hintsGateEnabled bool
hintsGateEnabled bool oldEPS *discovery.EndpointSlice
oldEPS *discovery.EndpointSlice newEPS *discovery.EndpointSlice
newEPS *discovery.EndpointSlice expectedEPS *discovery.EndpointSlice
expectedEPS *discovery.EndpointSlice
}{ }{
{
name: "terminating gate enabled, field should be allowed",
terminatingGateEnabled: true,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
},
{
name: "terminating gate disabled, and not set on existing EPS",
terminatingGateEnabled: false,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
},
{
name: "terminating gate disabled, and set on existing EPS",
terminatingGateEnabled: false,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: nil,
Terminating: nil,
},
},
},
},
},
{
name: "terminating gate disabled, and set on existing EPS with new values",
terminatingGateEnabled: false,
oldEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(false),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Terminating: nil,
},
},
},
},
newEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(false),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Terminating: utilpointer.BoolPtr(false),
},
},
},
},
expectedEPS: &discovery.EndpointSlice{
Endpoints: []discovery.Endpoint{
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(true),
Terminating: utilpointer.BoolPtr(true),
},
},
{
Conditions: discovery.EndpointConditions{
Serving: utilpointer.BoolPtr(false),
Terminating: utilpointer.BoolPtr(false),
},
},
{
Conditions: discovery.EndpointConditions{
Terminating: utilpointer.BoolPtr(false),
},
},
},
},
},
{ {
name: "node name gate enabled, set on new EPS", name: "node name gate enabled, set on new EPS",
oldEPS: &discovery.EndpointSlice{ oldEPS: &discovery.EndpointSlice{
@ -658,7 +282,6 @@ func Test_dropDisabledFieldsOnUpdate(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.TopologyAwareHints, testcase.hintsGateEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, testcase.hintsGateEnabled)()
dropDisabledFieldsOnUpdate(testcase.oldEPS, testcase.newEPS) dropDisabledFieldsOnUpdate(testcase.oldEPS, testcase.newEPS)

View File

@ -93,15 +93,13 @@ message EndpointConditions {
// serving is identical to ready except that it is set regardless of the // serving is identical to ready except that it is set regardless of the
// terminating state of endpoints. This condition should be set to true for // terminating state of endpoints. This condition should be set to true for
// a ready endpoint that is terminating. If nil, consumers should defer to // a ready endpoint that is terminating. If nil, consumers should defer to
// the ready condition. This field can be enabled with the // the ready condition.
// EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
optional bool serving = 2; optional bool serving = 2;
// terminating indicates that this endpoint is terminating. A nil value // terminating indicates that this endpoint is terminating. A nil value
// indicates an unknown state. Consumers should interpret this unknown state // indicates an unknown state. Consumers should interpret this unknown state
// to mean that the endpoint is not terminating. This field can be enabled // to mean that the endpoint is not terminating.
// with the EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
optional bool terminating = 3; optional bool terminating = 3;
} }

View File

@ -126,15 +126,13 @@ type EndpointConditions struct {
// serving is identical to ready except that it is set regardless of the // serving is identical to ready except that it is set regardless of the
// terminating state of endpoints. This condition should be set to true for // terminating state of endpoints. This condition should be set to true for
// a ready endpoint that is terminating. If nil, consumers should defer to // a ready endpoint that is terminating. If nil, consumers should defer to
// the ready condition. This field can be enabled with the // the ready condition.
// EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Serving *bool `json:"serving,omitempty" protobuf:"bytes,2,name=serving"` Serving *bool `json:"serving,omitempty" protobuf:"bytes,2,name=serving"`
// terminating indicates that this endpoint is terminating. A nil value // terminating indicates that this endpoint is terminating. A nil value
// indicates an unknown state. Consumers should interpret this unknown state // indicates an unknown state. Consumers should interpret this unknown state
// to mean that the endpoint is not terminating. This field can be enabled // to mean that the endpoint is not terminating.
// with the EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"` Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"`
} }

View File

@ -46,8 +46,8 @@ func (Endpoint) SwaggerDoc() map[string]string {
var map_EndpointConditions = map[string]string{ var map_EndpointConditions = map[string]string{
"": "EndpointConditions represents the current condition of an endpoint.", "": "EndpointConditions represents the current condition of an endpoint.",
"ready": "ready indicates that this endpoint is prepared to receive traffic, according to whatever system is managing the endpoint. A nil value indicates an unknown state. In most cases consumers should interpret this unknown state as ready. For compatibility reasons, ready should never be \"true\" for terminating endpoints.", "ready": "ready indicates that this endpoint is prepared to receive traffic, according to whatever system is managing the endpoint. A nil value indicates an unknown state. In most cases consumers should interpret this unknown state as ready. For compatibility reasons, ready should never be \"true\" for terminating endpoints.",
"serving": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "serving": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
"terminating": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "terminating": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
} }
func (EndpointConditions) SwaggerDoc() map[string]string { func (EndpointConditions) SwaggerDoc() map[string]string {

View File

@ -97,15 +97,13 @@ message EndpointConditions {
// serving is identical to ready except that it is set regardless of the // serving is identical to ready except that it is set regardless of the
// terminating state of endpoints. This condition should be set to true for // terminating state of endpoints. This condition should be set to true for
// a ready endpoint that is terminating. If nil, consumers should defer to // a ready endpoint that is terminating. If nil, consumers should defer to
// the ready condition. This field can be enabled with the // the ready condition.
// EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
optional bool serving = 2; optional bool serving = 2;
// terminating indicates that this endpoint is terminating. A nil value // terminating indicates that this endpoint is terminating. A nil value
// indicates an unknown state. Consumers should interpret this unknown state // indicates an unknown state. Consumers should interpret this unknown state
// to mean that the endpoint is not terminating. This field can be enabled // to mean that the endpoint is not terminating.
// with the EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
optional bool terminating = 3; optional bool terminating = 3;
} }

View File

@ -132,15 +132,13 @@ type EndpointConditions struct {
// serving is identical to ready except that it is set regardless of the // serving is identical to ready except that it is set regardless of the
// terminating state of endpoints. This condition should be set to true for // terminating state of endpoints. This condition should be set to true for
// a ready endpoint that is terminating. If nil, consumers should defer to // a ready endpoint that is terminating. If nil, consumers should defer to
// the ready condition. This field can be enabled with the // the ready condition.
// EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Serving *bool `json:"serving,omitempty" protobuf:"bytes,2,name=serving"` Serving *bool `json:"serving,omitempty" protobuf:"bytes,2,name=serving"`
// terminating indicates that this endpoint is terminating. A nil value // terminating indicates that this endpoint is terminating. A nil value
// indicates an unknown state. Consumers should interpret this unknown state // indicates an unknown state. Consumers should interpret this unknown state
// to mean that the endpoint is not terminating. This field can be enabled // to mean that the endpoint is not terminating.
// with the EndpointSliceTerminatingCondition feature gate.
// +optional // +optional
Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"` Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"`
} }

View File

@ -45,8 +45,8 @@ func (Endpoint) SwaggerDoc() map[string]string {
var map_EndpointConditions = map[string]string{ var map_EndpointConditions = map[string]string{
"": "EndpointConditions represents the current condition of an endpoint.", "": "EndpointConditions represents the current condition of an endpoint.",
"ready": "ready indicates that this endpoint is prepared to receive traffic, according to whatever system is managing the endpoint. A nil value indicates an unknown state. In most cases consumers should interpret this unknown state as ready. For compatibility reasons, ready should never be \"true\" for terminating endpoints.", "ready": "ready indicates that this endpoint is prepared to receive traffic, according to whatever system is managing the endpoint. A nil value indicates an unknown state. In most cases consumers should interpret this unknown state as ready. For compatibility reasons, ready should never be \"true\" for terminating endpoints.",
"serving": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "serving": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.",
"terminating": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating. This field can be enabled with the EndpointSliceTerminatingCondition feature gate.", "terminating": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.",
} }
func (EndpointConditions) SwaggerDoc() map[string]string { func (EndpointConditions) SwaggerDoc() map[string]string {

View File

@ -27,69 +27,24 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
featuregatetesting "k8s.io/component-base/featuregate/testing"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/pkg/controller/endpointslice" "k8s.io/kubernetes/pkg/controller/endpointslice"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
// TestEndpointSliceTerminating tests that terminating pods are NOT included in EndpointSlice when // TestEndpointSliceTerminating tests that terminating endpoints are included with the
// the feature gate EndpointSliceTerminatingCondition is off. If the gate is on, it tests that // correct conditions set for ready, serving and terminating.
// terminating endpoints are included but with the correct conditions set for ready, serving and terminating.
func TestEndpointSliceTerminating(t *testing.T) { func TestEndpointSliceTerminating(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
podStatus corev1.PodStatus podStatus corev1.PodStatus
expectedEndpoints []discovery.Endpoint expectedEndpoints []discovery.Endpoint
terminatingGate bool
}{ }{
{ {
name: "ready terminating pods not included, terminating gate off", name: "ready terminating pods",
podStatus: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
},
},
PodIP: "10.0.0.1",
PodIPs: []corev1.PodIP{
{
IP: "10.0.0.1",
},
},
},
expectedEndpoints: []discovery.Endpoint{},
terminatingGate: false,
},
{
name: "not ready terminating pods not included, terminating gate off",
podStatus: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionFalse,
},
},
PodIP: "10.0.0.1",
PodIPs: []corev1.PodIP{
{
IP: "10.0.0.1",
},
},
},
expectedEndpoints: []discovery.Endpoint{},
terminatingGate: false,
},
{
name: "ready terminating pods included, terminating gate on",
podStatus: corev1.PodStatus{ podStatus: corev1.PodStatus{
Phase: corev1.PodRunning, Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{ Conditions: []corev1.PodCondition{
@ -115,10 +70,9 @@ func TestEndpointSliceTerminating(t *testing.T) {
}, },
}, },
}, },
terminatingGate: true,
}, },
{ {
name: "not ready terminating pods included, terminating gate on", name: "not ready terminating pods",
podStatus: corev1.PodStatus{ podStatus: corev1.PodStatus{
Phase: corev1.PodRunning, Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{ Conditions: []corev1.PodCondition{
@ -144,14 +98,11 @@ func TestEndpointSliceTerminating(t *testing.T) {
}, },
}, },
}, },
terminatingGate: true,
}, },
} }
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.terminatingGate)()
// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
defer server.TearDownFn() defer server.TearDownFn()